Error Handling
Read APIFuse error responses to tell retryable failures apart from requests the upstream service refused.
Error Handling
Every APIFuse error response tells you three things: what happened (code, message), who stopped the request (source), and whether an identical retry can succeed (retryable). Use these fields instead of pattern-matching HTTP status codes.
Error response shape
{
"error": {
"code": "UPSTREAM_REJECTED",
"message": "The requested time slot overlaps an existing reservation.",
"retryable": false,
"source": "upstream_rule",
"fix": "Pick a slot that does not overlap the account's existing reservations.",
"requestId": "req_1a2b3c"
}
}| Field | Meaning |
|---|---|
code | Stable machine-readable code. Operation-specific codes are listed on each operation's reference page. |
message | Human-readable summary. |
retryable | false means an identical retry cannot succeed — change the request instead. true means a retry later can succeed. |
source | Who stopped the request (see below). |
fix | What to change to make the request succeed, when known. |
requestId | Include this when contacting support. |
Some error responses may omit source and retryable; when they are absent, fall back to the status classes below.
Platform-level errors — an invalid connection ID, a timeout while reaching the provider's service, a cancelled request — use a flat body carrying the same fields at the top level: {"error", "code", "message", "action", "retryable", "source", "request_id"}. The official SDKs read both shapes.
Who stopped the request
source | Meaning | What to do |
|---|---|---|
client | The request itself needs a change (invalid input, missing or expired Connection). | Fix the request or reconnect, then call again. |
upstream_rule | The provider's service understood the request and refused it under its own rules — a sold-out item, a conflicting reservation, an account-state restriction. | Show the reason to your user or change the request. Retrying identically returns the same answer. |
upstream_failure | The provider's service malfunctioned or did not respond. | Retry with backoff. |
apifuse | APIFuse could not complete the request. | Retry with backoff; contact support with requestId if it persists. |
Status classes
| Status | Class | Typical source |
|---|---|---|
400, 401, 404 | Request needs a change | client |
409, 410, 422 | The upstream service refused the request under its own rules | upstream_rule |
429 | Rate limited — honor Retry-After | upstream_rule |
502, 504 | Upstream malfunction or timeout | upstream_failure |
500, 503 | APIFuse-side fault | apifuse |
A 409 is not an outage: the upstream service is healthy and gave a definitive answer. Treat it as application data (for example, show "this slot is already booked"), not as an error to retry.
Retrying safely
- Never auto-retry a response with
retryable: false. - Retry
429after theRetry-Afterinterval, and502/503/504with exponential backoff. - The official TypeScript and Python SDKs follow these rules automatically and expose
error.retryableanderror.sourceon thrown errors.
import { ApiFuseClient, ApiFuseError } from "@apifuse/sdk";
const client = new ApiFuseClient({ apiKey: process.env.APIFUSE_API_KEY! });
async function reserve(input: Record<string, unknown>) {
try {
return await client.call.invoke("catchtable", "reserve", input);
} catch (error) {
if (
error instanceof ApiFuseError &&
error.source === "upstream_rule" &&
error.retryable === false
) {
// Definitive refusal — show it to the user instead of retrying.
// (A 429 rate limit also carries source "upstream_rule" but stays
// retryable — the guard above keeps it on the retry path.)
return { refused: true, reason: error.message, suggestion: error.fix };
}
throw error;
}
}