String API
API Reference

Errors

Status codes and error response shapes.

Status codes

StatusMeaning
400Validation error — invalid input, or a path that requires KYC.
401Missing or invalid Authorization header.
402Insufficient account balance.
403Destination domain not enabled for your organization.
429Rate limit exceeded.
502The destination returned an error, or the request could not be completed.
503A feature the request uses, such as XHR capture, is at capacity or temporarily unavailable. Retry, after Retry-After seconds when that header is present.
504The destination did not respond in time. See Failure classes.
500Internal 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" }]
}
FieldDescription
errorError type identifier.
messageHuman-readable error message.
statusCodeThe status the destination returned, when the request reached it. Not the HTTP status of this response — read that from the response line.
errorTypeMachine-readable failure class. See Failure classes.
traceIdOptional trace identifier.
issuesPer-field validation issues (field, message).
dataThe destination's own response body, on the one failure class where that body is the answer. See Terminal origin failures.
headersThe 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. 502 and 504 mean the request failed upstream of us: the destination answered with an error, could not be reached, or did not respond in time. 500 means something went wrong on our side.
  • statusCode in the body is what the destination itself returned. It is also on the x-status-code response 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", not 0.
{
  "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.

errorTypeWhat happenedWhat to do
TargetCertificateErrorThe destination's TLS certificate cannot be verified.Sent only when verification is on; see ignoreCertificateErrors.
TooManyRedirectsErrorThe destination's redirect chain does not terminate.The destination has to fix it; do not retry.
TerminalOriginErrorThe destination's own configuration produced the answer.Do not retry. Read data — see Terminal origin failures.
TargetTimeoutErrorThe 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.
TargetUnreachableErrorWe 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.
TargetHostNotFoundErrorThe 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, 1016 and 1018 (DNS and hostname misconfiguration), and 525/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.