Every error, one table.

status is always "error"; error.code is stable and safe to match on; error.message is for humans, not code. Derived from the published OpenAPI spec. See it match, byte for byte, at /openapi.json.

These are API errors: status: "error", a rejected request. For a response that came back status: "success" with text that's still wrong (mojibake, empty, scrambled columns), see fixes for broken extracted text instead.

Every non-success response has the same shape:

{
  "status": "error",
  "error": {
    "code": "too_large",
    "message": "upload exceeds max size of 10485760 bytes; use ?url= for larger documents"
  }
}

Match on error.code. It's stable across releases. error.message is human-readable detail and can change. Don't parse it.

codeHTTP statuscauseremediation
bad_request400The request was malformed — no file part or ?url= was given, or a retried request under a live Idempotency-Key had a different body/target than the original.Provide exactly one of a multipart file or ?url=. If you're reusing an Idempotency-Key, send the exact same request you sent the first time.
fetch_failed400The server-side ?url= fetch failed: a blocked/private address (SSRF-guarded), a connection error, a non-2xx response, or a timeout reaching the source.Confirm the URL is publicly reachable over https:// and returns a 2xx. txtfetch refuses to fetch private/internal/link-local addresses by design.
not_found404The job_id in GET /v1/extract/{job_id} is malformed, unknown, or has expired.Double-check the job_id from the 202 response. If it's expired, re-submit the original extraction to get a new one.
too_large413The direct upload exceeds the maximum accepted size for the multipart file path.Use ?url= instead of a direct upload for large documents — txtfetch fetches it server-side with a much higher size ceiling.
unsupported_format415The input's real, byte-detected type isn't a supported document format.Check /formats for the current list. txtfetch sniffs the actual bytes, not the file extension, so a mislabeled file won't help.
encrypted422The PDF is encrypted or password-protected, so the API refuses it. This includes a PDF with only an owner password, which opens in a viewer with no password. A locked Word or Excel file does not return this code. It returns extraction_failed.Remove the password/encryption before submitting, or supply an already-decrypted copy. /docs/password-protected shows how.
extraction_failed422Extraction ran but produced no text — e.g. a blank scan, a corrupt file, or an image with no recoverable content. A password-protected Word or Excel file also lands here, with the message "tika returned 422".Confirm the source actually contains extractable text or legible imagery. Failed extractions don't count against your quota, so retries are free.
abuse_detected429This API key's abuse circuit breaker has tripped: a burst of abusive outcomes — repeated malformed input, and especially oversized/body-bomb rejections — not raw request volume. Distinct from rate_limited (per-minute volume) and quota_exceeded (monthly successes); legitimate throttling and successes never contribute to this.Stop sending the offending requests and back off until the Retry-After delta-seconds elapse. If this is unexpected, check your integration isn't retrying oversized/malformed payloads in a loop.
quota_exceeded429This API key's monthly document quota is exhausted for the current billing period. This counts only successful extractions and is distinct from the per-minute rate_limited check — failed extractions don't count against it.Wait for the monthly reset (X-Quota-Reset unix seconds, or the Retry-After delta-seconds), or upgrade to a larger plan in your dashboard (app.txtfetch.com).
rate_limited429This API key's per-minute request rate was exceeded. This counts every accepted request (even ones that go on to fail) and is unrelated to your monthly document quota (see quota_exceeded).Back off until the Retry-After delta-seconds elapse, or until X-RateLimit-Reset (unix seconds) passes, then retry.
not_implemented501The request asked for a valid but not-yet-shipped response mode — a format/quality combination other than the default text/standard.Omit format/quality overrides for now; only the default text extraction mode is implemented.
timeout504Extraction did not complete within the processing budget (large/complex documents, slow OCR).Retry the request, or force the async path with ?async=true so it isn't bound by the synchronous request timeout.

Locked files: a locked PDF returns encrypted, and a locked Word or Excel file returns extraction_failed. See Password-protected files for real output and how to unlock a file first.

rate_limited-vs-quota

Both are 429s, but they mean different things. rate_limited is your API key's per-minute request rate. It counts every accepted request, including ones that go on to fail. It resets every minute. It carries a Retry-After header (in seconds) plus X-RateLimit-Limit/X-RateLimit-Remaining/X-RateLimit-Reset headers, so you can back off precisely. quota_exceeded is your plan's monthly document quota. It counts only successful extractions. It's checked after the rate-limit gate, and it also carries X-Quota-Limit/X-Quota-Remaining/X-Quota-Reset. Wait for the reset or upgrade your plan.

quota_exceeded (429) is your plan's monthly document quota. It counts only successful extractions, so failed requests never burn it. It resets once per UTC month. The response carries a Retry-After header (seconds until that reset) and X-Quota-Limit/X-Quota-Remaining/X-Quota-Reset headers. It also carries the X-RateLimit-* triad, since the rate check runs first. Those headers are always present too. Match on error.code to tell the two 429s apart, not on the status alone. X-Quota-* headers also ride on every successful 200/202 response, so you can budget ahead of time instead of waiting to hit the limit.

authentication-failures

A missing Authorization header returns 401; an invalid or revoked key returns 403. Both come back with API Gateway's own {"message": "…"} body. This is deliberately not the typed {"error": {"code": …}} envelope above. That's a platform limit of the HTTP-API authorizer, not something this API controls. Integrators should match on HTTP status for auth failures, never on a code field. A revoked key can take up to 60 seconds to stop working (the authorizer caches its allow/deny decision for that long). Create a key in your dashboard.

the-actual-thresholds

too_large, timeout, fetch_failed, and rate_limited each name a rule, not a number. The current byte and time thresholds behind all four, and everything else the API enforces, are on the limits page →.

typed-exceptions

Both official SDKs map every code above to its own exception class (for example, TooLargeError, UnsupportedFormatError). Each class derives from a common TxtfetchError. See the quickstarts for the full class list.

machine-readable-spec

This table is generated at build time from the published OpenAPI 3.1 spec's ErrorCode enum and per-code HTTP status. The build fails if this page's editorial guidance ever falls out of sync with the contract.

Watch it run, then take a key.

The playground replays a real recorded response for every sample document.

Open the playground →

Get an API key →