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 | The destination returned an error, or the request could not be completed. |
503 | A feature the request uses, such as XHR capture, is at capacity or temporarily unavailable. Retry, after Retry-After seconds when that header is present. |
504 | The destination did not respond in time. See Failure classes. |
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.
502and504mean the request failed upstream of us: the destination answered with an error, could not be reached, or did not respond in time.500means 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": "The request to the destination failed"
}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. A hostname that does not resolve is
never a 403: it returns 502 with errorType: "TargetHostNotFoundError".
Failure classes
errorType names the upstream failure when we can tell what it was. Use it to distinguish failure
classes that share the same HTTP status code.
errorType | What happened | What to do |
|---|---|---|
TargetCertificateError | The destination's TLS certificate cannot be verified. | Sent only when verification is on; 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. |
TargetTimeoutError | The destination did not respond within our deadline. Sent with HTTP 504. | Retry later. If you set executeJS, try without it: a plain fetch needs less from a slow site. |
TargetUnreachableError | We could not connect to the destination, or it dropped the connection before answering. The message names the network error, such as ERR_NAME_NOT_RESOLVED or ERR_CONNECTION_REFUSED. | Check the URL and that the site is up. Retry later for a transient error; a hostname that does not resolve will keep failing. |
TargetHostNotFoundError | The destination hostname has no DNS record. Sent with HTTP 502. | Check the hostname for typos; do not retry until it resolves. |
{
"error": "Request failed",
"message": "The destination did not respond within 60s",
"errorType": "TargetTimeoutError"
}TargetTimeoutError, TargetUnreachableError and TargetHostNotFoundError never carry statusCode,
because the destination never sent a response.
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.