> Source: https://txtfetch.com/docs/errors > Plain-text twin — every page on txtfetch.com has one. https://txtfetch.com/text --- # 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](https://txtfetch.com/fixes) 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. | code | HTTP status | cause | remediation | | --- | --- | --- | --- | | `bad_request` | 400 | The 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_failed` | 400 | The 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_found` | 404 | The 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_large` | 413 | The 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_format` | 415 | The 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. | | `encrypted` | 422 | The document is encrypted or password-protected, so Tika can't open it. | Remove the password/encryption before submitting, or supply an already-decrypted copy. | | `extraction_failed` | 422 | Extraction ran but produced no text — e.g. a blank scan, a corrupt file, or an image with no recoverable content. | Confirm the source actually contains extractable text or legible imagery. Failed extractions don't count against your quota, so retries are free. | | `abuse_detected` | 429 | This 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_exceeded` | 429 | This 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_limited` | 429 | This 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_implemented` | 501 | The 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. | | `timeout` | 504 | Extraction 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. | rate\_limited-vs-quota Both are `429`s, 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 `429`s 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](https://app.txtfetch.com/signup). 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](https://txtfetch.com/docs/quickstarts) for the full class list. machine-readable-spec This table is generated at build time from the published [OpenAPI 3.1 spec](https://txtfetch.com/openapi.json)'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 →](https://txtfetch.com/playground) [Get an API key →](https://app.txtfetch.com/signup)