Errors
One error envelope, one code vocabulary, and what each code means.
Every failure — bad input, a missing operation, a rate limit, a dead upstream — comes back in the same shape:
{
"error": {
"code": "INVALID_INPUT",
"message": "language: String must contain at most 30 character(s)"
}
}code is a stable, machine-readable identifier: branch on it. message is
written for a human reading a log and may change at any time, so do not parse
it. For a marketplace operation the HTTP status matches the code, which means a
classified failure reaches you as the status it deserves rather than a blanket 502. Hosted functions are the exception, in
both the status and the shape — see below.
Every response, successful or not, also carries an x-request-id header. Quote
it when reporting a problem — it is how we find your exact call.
Codes
| Code | HTTP | Meaning |
|---|---|---|
CALLER_PROXY_AUTH_REJECTED | 400 | The proxy you supplied (proxyUrl or x-proxy-url) refused our connection with HTTP 407. Check its credentials and whether your provider allow-lists the connecting IP. Distinct from PROXY_UNAVAILABLE, which is about our own pool. |
INVALID_INPUT | 400 | The input failed the operation's schema validation. |
INVALID_JSON | 400 | The request body was not parseable JSON. |
INVALID_PATH | 400 | The request path is not a valid operation route. |
MEMORY_EXCEEDED | 400 | A hosted function exceeded the memory allowed by your plan. Delivered as a 422, see below. |
MISSING_FUNCTION_ID | 400 | The request did not identify an operation to run. |
AUTH_EXPIRED | 401 | The supplied upstream credentials or session are no longer valid. |
AUTH_REQUIRED | 401 | This operation requires credentials that were not supplied. |
UNAUTHORIZED | 401 | The x-api-key header is missing, unknown, or disabled. |
FORBIDDEN | 403 | Authenticated, but your plan or organization may not perform this action. |
NETWORK_DENIED | 403 | An outbound request was blocked by the SSRF guard (private, link-local, or loopback address). |
NOT_FOUND | 404 | No operation is published at this slug for your organization. |
CHECKPOINT_REQUIRED | 409 | The upstream site stopped the operation at a security checkpoint a person must clear (an in-app approval, an authenticator prompt, or an identity check). Retrying will not help. |
CLIENT_REJECTED | 409 | The upstream site rejected our client itself — a request signature, a device attestation, or an app-integrity check — rather than your input. Retrying, waiting, or routing through a different network will not change the answer. |
IDEMPOTENCY_IN_FLIGHT | 409 | The first request with this Idempotency-Key is still running. Retry to receive its result. |
IDEMPOTENCY_KEY_REUSED | 409 | This Idempotency-Key was already used for a different request. Use a new key. |
NO_MOBILE_EXIT | 409 | You asked a proxy lease for a mobile-carrier exit and none of the exits probed was cellular. Retrying with the same inputs gives the same answer: try a different mobileCountry, or drop the requirement and accept a residential exit. |
PHONE_VERIFICATION_REQUIRED | 409 | The upstream site stopped the operation and demanded a phone number. A person with a number is the only thing that clears it, so retrying will not help. |
QUOTA_EXCEEDED | 429 | A budget is spent: the month's weighted units, a trial's unit allowance, or your plan's hosted-function compute seconds. |
RATE_LIMITED | 429 | You exceeded the per-minute burst limit. It refills on its own within 60s. |
SERVICE_BUSY | 429 | Execution capacity is momentarily saturated. Retry with backoff. |
INTERNAL_ERROR | 500 | An unexpected failure on our side. Report it with the x-request-id. |
MISSING_HANDLER | 500 | The operation is registered without a runnable handler. |
OPERATION_ERROR | 500 | The operation raised an unclassified error while running. |
NOT_IMPLEMENTED | 501 | The operation exists but this code path is not implemented. |
CAPTCHA_FAILED | 502 | The upstream site presented a challenge that could not be solved. |
INVALID_OUTPUT | 502 | The operation's result did not match its declared output schema. |
INVOCATION_FAILED | 502 | The operation could not be dispatched to a worker. |
PROXY_UNAVAILABLE | 502 | No healthy proxy was available for an operation that requires one. |
UPSTREAM_ERROR | 502 | The third-party service returned an error or blocked the request. |
UPSTREAM_INVALID_RESPONSE | 502 | The third-party service answered in a shape we could not parse. |
UPSTREAM_UNREACHABLE | 502 | The third-party service could not be contacted at all. |
AUTH_UNAVAILABLE | 503 | A session surface could not reach our authentication infrastructure. Keyed calls see SERVICE_UNAVAILABLE for the same outage — retry shortly. |
SERVICE_UNAVAILABLE | 503 | A dependency of the platform is currently unavailable. |
OPERATION_TIMEOUT | 504 | The operation exceeded its own timeout budget. |
UPSTREAM_TIMEOUT | 504 | The third-party service the operation calls did not answer in time. |
Semantics worth knowing
These are the cases where the obvious reading of a status is wrong.
404 does not distinguish "missing" from "not yours"
An unknown slug and an operation that exists but is not published to your
organization both return 404 NOT_FOUND, with the same message. This is
deliberate: probing the gateway must not reveal which private operations exist.
If you expected an operation to work and get a 404, check the marketplace
listing rather than assuming a typo.
Hosted-function failures arrive as a 4xx, whatever the code says
Cloudflare replaces any origin 5xx body with its own opaque error page, which would destroy the envelope. A hosted function's failures are attributable to your code, so the gateway deliberately returns them with an edge-safe status instead of the code's canonical one:
| Code | Table says | You receive |
|---|---|---|
OPERATION_TIMEOUT | 504 | 408 |
SERVICE_BUSY | 429 | 429 |
MEMORY_EXCEEDED, INVALID_OUTPUT, OPERATION_ERROR, … | 400 / 502 | 422 |
The canonical status is preserved in an X-Upapi-Execution-Status header if you
need it. This remapping applies only to hosted functions; marketplace
operations return the status in the table above.
Hosted functions also use a different envelope
A failure raised inside the isolate comes back as {"error": "MEMORY_EXCEEDED", "message": "…"} —
error is a string, not the { code, message } object. Read both shapes if you call hosted
functions, exactly as the gateway itself does.
MEMORY_EXCEEDED is a client error, and always a 422
Exceeding the memory your plan allows is treated as a problem with the request —
you asked for more than you bought — so it is a client error rather than a 500.
Only a hosted function can raise it, so on the wire it is always a 422 by the
remapping above, never the 400 the code table lists. Retrying the same call
unchanged will fail the same way; either shrink the workload or move up a plan.
AUTH_UNAVAILABLE is a 503, not a 401 — on session surfaces
This code means our authentication infrastructure could not be reached, so your credentials were never checked. It is an outage signal, not a credential signal. Retry with backoff; do not rotate keys and do not surface "invalid credentials" to your users.
It is emitted by the surfaces that authenticate a session: the dashboard,
the marketplace try-it runner, and the hosted MCP endpoint. A call authenticated
with an API key gets 503 SERVICE_UNAVAILABLE for the same outage — a different
code, the same status and the same response. Handle both.
429 arrives with a reason
Three codes share the 429 status, and the code — not the status, and not the
size of Retry-After — is what tells you whether waiting will help:
RATE_LIMITED— you exceeded the per-minute burst limit. This is the one 429 that clears itself: the window refills within 60 seconds and nothing has been spent. Wait and retry.QUOTA_EXCEEDED— a budget is spent. That is the month's weighted units, a trial's included units, or your plan's compute seconds for hosted functions. Retrying is pointless until the budget refills;Retry-After, where present, points at the moment it actually does — the next UTC month, or the end of your trial. Upgrading is the other fix.SERVICE_BUSY— execution capacity is momentarily saturated. Retry with backoff.
This split changed
A monthly-quota or trial-cap rejection used to be RATE_LIMITED as well, leaving Retry-After's
magnitude as the only clue. If your client branches on RATE_LIMITED alone, it now retries only
the burst window — which is what it should always have done. Handle QUOTA_EXCEEDED as a stop.
See Rate limits for the headers that tell you which is which before you hit them.
The two 409s are about idempotency, and only one is your mistake
Both are raised by Idempotency-Key handling:
IDEMPOTENCY_KEY_REUSED— you sent a key that a different request already used. Nothing ran. Two requests may not share a key; generate a new one.IDEMPOTENCY_IN_FLIGHT— you sent a key whose first request is still running. Nothing ran twice, either. Wait forRetry-After(one second) and send the same request again; you will get the first one's stored response.
The two proxy codes name whose proxy failed
Both mean a proxy refused to carry the request, and the fix is on opposite sides:
PROXY_UNAVAILABLE(502) — our exit pool. No proxy was available, or the one we leased stopped working mid-call. Nothing about your request caused it and nothing in your request can fix it. Retry: a later call leases a different exit.CALLER_PROXY_AUTH_REJECTED(400) — the proxy you supplied, through an operation'sproxyUrl, thex-proxy-urlheader, or your organization's configured proxy list, answered our connection withHTTP 407. Check its credentials, and whether your provider requires allow-listing the IP that connects to it. Retrying unchanged fails identically.
5xx codes name the layer that failed
UPSTREAM_* codes mean the third-party service an operation wraps misbehaved —
the platform worked. OPERATION_ERROR and INVALID_OUTPUT point at the
operation itself. INTERNAL_ERROR is ours; report it with the x-request-id.
Retrying
Retry SERVICE_BUSY, SERVICE_UNAVAILABLE, AUTH_UNAVAILABLE,
IDEMPOTENCY_IN_FLIGHT, the UPSTREAM_* family and RATE_LIMITED — with
exponential backoff, and always respecting Retry-After when it is present.
Do not retry INVALID_INPUT, INVALID_JSON, INVALID_PATH, NOT_FOUND,
UNAUTHORIZED, FORBIDDEN, MEMORY_EXCEEDED, IDEMPOTENCY_KEY_REUSED,
CALLER_PROXY_AUTH_REJECTED or QUOTA_EXCEEDED without changing something
first; the same request will fail identically.
Because an operation is a POST with real upstream side effects, a retry that is
safe is not automatically a retry that is free — send an
Idempotency-Key so a retried call replays the first
result instead of running and billing a second time.
The TypeScript SDK implements exactly this policy, idempotency key included.
One envelope, one exception
A request body over 5 MB is rejected at the edge, before it reaches the gateway, with 413 and
the code PAYLOAD_TOO_LARGE. That code is not part of the operation vocabulary above — treat any
oversized-body handling as a separate branch.