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.
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 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) and resets every minute, carrying Retry-After (seconds) and 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, is checked after the rate-limit gate, and additionally 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 Retry-After (seconds until that reset) and X-Quota-Limit/X-Quota-Remaining/X-Quota-Reset headers, plus the X-RateLimit-* triad — the rate check runs first, so its 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 — deliberately not the typed {"error": {"code": …}} envelope above, since 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.
typed-exceptions
Both official SDKs map every code above to its own exception class (e.g. TooLargeError, UnsupportedFormatError) deriving 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.
Stop parsing. Start shipping.
Create an account and get an API key in minutes — the free Hobby plan needs no card.
Get started →