Errors and limits
The error envelope
Every error from the parse API is a flat JSON object with two keys, and sometimes a third:
{"error": "You do not have any more credits. Please contact sales@affinda.com", "code": "no_credits"}
erroris a message that is safe to show a person. It can change without notice.codeis a stable machine-readable identifier. Branch on this, never on the message or on the status alone.retryAfterSecondsappears when waiting is the right response. TheRetry-AfterHTTP header carries the same number.
This envelope belongs to the parse API. The console's own endpoints, and the wider Affinda
platform API at app.affinda.com, use a different and more nested error format. If you use both,
do not share error-handling code between them.
Errors raised by the parser itself, rather than by the API in front of it, are relayed to you
unchanged with their own status. That is why the code values below come from two families.
Status codes
| Status | code |
What happened |
|---|---|---|
| 400 | missing_file |
No file in the file field, no usable raw body, or a zero-byte upload. A body sent as application/x-www-form-urlencoded lands here too, because it carries no file. |
| 400 | organization_required |
Your account has more than one Resume Parser organization. Pass ?organization= to say which one pays. |
| 400 | invalid_request |
The request was malformed in some other way. |
| 401 | unauthorized |
Missing, unknown, expired or wrong-region API key, or an IP address your organization does not allow. See Authentication. |
| 402 | no_credits |
No credits left, or credits expired. Nothing is parsed and nothing is charged. |
| 403 | forbidden |
Multi-factor authentication is required on your account and not set up. Finish setting it up in the console. |
| 404 | organization_not_found |
The organization parameter names an organization that is not one of your Resume Parser organizations. |
| 405 | method_not_allowed |
Only POST is implemented on this path. |
| 413 | file_too_large |
The file is over 20 MB. Checked before the parser is called, so it costs nothing. |
| 413 | payload_too_large |
The parser's own size rejection. You see this only if a document gets past the first check. |
| 415 | unsupported_format |
The document is not a format the parser reads. See below. |
| 422 | password_protected_document |
The document is encrypted. Remove the password and retry. |
| 422 | unparseable_document |
The file was read but nothing could be extracted from it. |
| 422 | missing_file_part |
The parser received a multipart request with no file part. |
| 429 | rate_limited |
You are over the per-minute limit. See below. |
| 429 | queue_full, queue_timeout, ocr_queue_full |
The parser is at capacity. Wait retryAfterSeconds and retry. |
| 500 | internal_error, internal |
Something failed unexpectedly. The detail is recorded on our side. |
| 502 | parser_unavailable |
The parser could not be reached. The request is retried once on a fresh connection first. No credit is charged. |
| 503 | shutting_down |
The parser instance is shutting down. Retry. |
| 504 | parser_timeout, deadline |
The parse did not finish in time. The API waits 30 seconds and the parser applies its own 28 second deadline, so deadline is the one you usually see. No credit is charged. |
Retry 429, 502, 503 and 504. Do not retry 4xx codes that describe the request itself:
the same request will fail the same way.
What the parser accepts
The cloud API is deliberately lenient. If a file might hold text, it is attempted.
| Accepted | Notes |
|---|---|
| Including scanned PDFs, which are read with OCR. | |
| DOCX, ODT, XLSX, ODS | |
| DOC, XLS | The legacy Microsoft formats. DOC extraction is more approximate than DOCX. |
| RTF, HTML, SVG | |
| Plain text | UTF-8 and UTF-16. |
| PNG, JPEG, GIF, TIFF, BMP, WEBP | Read with OCR. |
Three things are rejected rather than attempted:
- Encrypted documents, with
422 password_protected_document. Nobody can read them, including us. - Empty files. A zero-byte upload is answered
400 missing_file, and a file that holds no readable text at all is answered422 unparseable_document. - Legacy PowerPoint (
.ppt), with415 unsupported_format.
Anything else is attempted. A file that turns out to hold no usable text still returns 200, with
very little in data and a low meta.document.extractionQuality.band. Checking that band is how
you tell a thin result from a good one. A self-hosted container with OCR switched off also
rejects images, with 415 unsupported_format, because there is nothing to read them with.
Two limits that truncate quietly rather than failing:
- Only the first 20 pages of a document contribute fields.
- OCR reads the first 3 pages. When there were more,
meta.ocr.truncatedistrueandmeta.document.warningscarriesocr_truncated.
Size limit
A file may be up to 20 MB. Larger files are rejected with 413 file_too_large before the parser
is called, so they cost nothing. The size is checked against the declared Content-Length,
against the multipart part, and again against the bytes actually received.
Rate limits
| Limit | Value |
|---|---|
| Per organization | 6,000 parses per minute |
| Per API key | 6,000 parses per minute |
These are an abuse floor rather than a quota: normal use does not come near them. Going over
gives 429 rate_limited. If you need more, contact sales@affinda.com.
The free tool on our website at https://www.affinda.com/free-resume-parser has its own generous daily fair-use cap, per IP address. It is a tool for people, not an API to build on.
Credits
One successful parse costs one credit, however many pages the document has. A parse that fails costs nothing: the credit is charged after the parser returns, and every error path above either never reaches the parser or never charges.
New accounts start with 1,000 free credits, valid for 14 days. Credits you buy are separate and get their own expiry at purchase.
The X-Credits-Remaining response header carries your balance after the parse was charged. It is
omitted if the balance could not be read, so treat its absence as "unknown" rather than "zero".
When credits run out, or when they expire, parses stop with 402 no_credits. Nothing is parsed
and nothing is charged. Buy more on the console's Billing screen at /billing.
Response headers
| Header | On | Meaning |
|---|---|---|
X-Credits-Remaining |
Successful parses | Your credit balance after this parse. |
X-Parser-Capacity-State |
Every response | How busy the parser fleet is. A steering hint. |
X-Parser-Fair-Share |
Every response | How many concurrent parses you are currently allowed, or unlimited. |
X-Parser-In-Flight |
Every response | How many parses you currently have in flight. |
Retry-After |
Some 429s and 503s | Seconds to wait before retrying. |
The three X-Parser-* headers are hints for a client that wants to pace itself. They are not
promises, and they can change from one response to the next.