Errors
Status codes and error response shapes.
Status codes
| Status | Meaning |
|---|---|
400 | Validation error — invalid input, or a path that requires KYC. |
401 | Missing or invalid Authorization header. |
402 | Insufficient account balance. |
403 | Destination domain not enabled for your organization. |
429 | Rate limit exceeded. |
502 | Destination or upstream fetch provider returned an error. |
500 | Internal server error. |
One failure class does not appear in that table because it has no fixed status of its own: a
terminal origin failure is returned under whatever status the
destination itself answered with, including ones you will not otherwise see from us, such as 530.
Error response shape
Most errors return an ErrorResponse object:
{
"error": "Validation error",
"message": "URL protocol must be http or https",
"statusCode": 404,
"traceId": "…",
"issues": [{ "field": "url", "message": "URL protocol must be http or https" }]
}| Field | Description |
|---|---|
error | Error type identifier. |
message | Human-readable error message. |
statusCode | The status the destination returned, when the request reached it. Not the HTTP status of this response — read that from the response line. |
errorType | Machine-readable failure class. See Failure classes. |
traceId | Optional trace identifier. |
issues | Per-field validation issues (field, message). |
data | The destination's own response body, on the one failure class where that body is the answer. See Terminal origin failures. |
headers | The destination's own response headers. Present exactly when data is. |
Reading a failed fetch
Read two things, and do not confuse them:
- The HTTP status of our response says whose fault it is.
502means the request failed upstream of us.500means we have no destination response to report — either the request never reached the destination (proxy failure, DNS, TLS) or something went wrong on our side. statusCodein the body is what the destination itself returned. It is also on thex-status-coderesponse header, the same header a successful fetch sets to the same value, so one read works either way. It is absent when we never got a response out of the destination — absent means "no response", not0.
{
"error": "Request failed",
"statusCode": 403,
"message": "Upstream service error"
}Sent with HTTP 502: the destination answered 403, and we could not return that answer to you.
Note that statusCode can be a success status. A destination that serves an anti-bot
challenge or a login wall as a 200 will report statusCode: 200 on a 502 — the response
came back, it just was not the content you asked for.
For a 4xx on the terminal-origin path, statusCode is also passed through as our HTTP status.
Key off statusCode and errorType, never off our HTTP status alone, which collapses distinct
upstream faults onto 502.
A 403 has two unrelated meanings
A body with reason means the destination domain is not enabled for your organization — see Access control & KYC.
Anything else is the destination's own 403, passed through.
Failure classes
errorType is present only for failures where retrying the same request is not the remedy. Key off
it rather than the status code, which collapses every upstream fault to 502.
errorType | What happened | What to do |
|---|---|---|
TargetCertificateError | The destination's TLS certificate cannot be verified. | See ignoreCertificateErrors. |
TooManyRedirectsError | The destination's redirect chain does not terminate. | The destination has to fix it; do not retry. |
TerminalOriginError | The destination's own configuration produced the answer. | Do not retry. Read data — see Terminal origin failures. |
Terminal origin failures
TerminalOriginError marks a response the destination's own configuration decided. A private object,
a hostname whose DNS no longer resolves, a certificate the origin never finished installing — no
retry, exit rotation or fingerprint reaches a different answer, because none of those is what the
destination is objecting to.
Two families produce it today:
- Object-store denials. A private object, or a signed URL whose signature has expired or was
built wrong, answering
AccessDenied. - Cloudflare origin errors. The edge is up and the origin behind it is not:
1000–1004,1013,1016and1018(DNS and hostname misconfiguration), and525/526(the origin's TLS).
This is the one failure class whose shape differs from the rest of this page. Our HTTP status is
the destination's own status rather than 502, and the body carries the destination's response
verbatim:
{
"error": "Request failed",
"statusCode": 403,
"message": "The destination answered in a way that will not change on retry; its response is included.",
"errorType": "TerminalOriginError",
"data": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>AccessDenied</Code><Message>Access Denied</Message></Error>",
"headers": { "content-type": "application/xml", "server": "AmazonS3" }
}Sent with HTTP 403 — the destination's status, also on x-status-code. A Cloudflare 1016 arrives
the same way under HTTP 530, carrying that error page in data.
So the rule higher up this page — that our HTTP status only tells you whose fault it is — has one
exception here, and it is the useful kind: the status is the destination's, and data is its own
explanation of why. Read data and act on what the destination said. The remedy is almost always on
its side: the object is private, the signed URL expired, the domain no longer resolves.
Always a JSON envelope
Like every error on this page, this one is returned as JSON whatever format you requested, so a
format: "raw" request receives this envelope rather than raw bytes.
Common cases
400 — validation
{
"error": "Validation error",
"message": "GET requests cannot have a body",
"issues": [{ "field": "body", "message": "GET requests cannot have a body" }]
}401 — authentication
{ "error": "Missing or invalid Authorization header" }402 — insufficient balance
{ "error": "Insufficient balance" }403 — destination not enabled
{ "reason": "Requests to example.com are not allowed. Contact [email protected] for access." }See Access control & KYC for the 400 vs 403 distinction.