Errors
Every failure returns the same shape, with a stable code to switch on and a
message written for a person to read.
{
"error": {
"code": "no_text_layer",
"message": "This PDF is images, not text. Reading it requires OCR.",
"needsOcr": true
}
}
Rows marked ▪ carry needsOcr: true.
| Status | Code | Meaning |
|---|---|---|
| 400 | empty_body | The request carried no document at all. |
| 400 | missing_key | A JSON body arrived without the object key naming what to convert. |
| 401 | unauthorized | Absent, malformed, unknown, or revoked key. All four answer the same, so probing learns nothing. |
| 402 | ocr_quota_exceeded | Refused before any OCR ran, so nothing was billed. The page count is known in advance. |
| 402 | quota_exceeded | The document allowance for this period is spent. |
| 404 | not_found | The key does not exist, or belongs to another organization. Both answer the same. |
| 413 | resource_limit | Usually a decompression bomb rather than a real document. |
| 413 | too_large | Over the 100MB ceiling. |
| 415 | unknown_format | Neither the content signature nor the extension named a supported format. |
| 422 | encrypted | Remove the password and send it again. |
| 422 | incomplete | Re-export it from the application that produced it. |
| 422 | malformed | Structurally unusable, often truncated. |
| 422 | no_text_layer▪ | Every page is a scan. Carries `needsOcr: true`, this is the paid path, not a mistake you made. |
| 422 | unsupported | A format anydoc recognises but cannot read. |
| 500 | conversion_failed | Nothing more specific was known. Worth reporting. |
| 503 | uploads_unavailable | The presigned upload path is down. Smaller documents still convert. |
The one worth handling specially
no_text_layer is not a mistake you made. It means every page of the PDF is a
scan, so there is nothing to extract and the document needs OCR, which is the
paid path rather than a fault. It is the only error that carries needsOcr, so
it can be branched on directly.
if (!response.ok) {
const { error } = await response.json();
if (error.needsOcr) {
// Every page is a scan. Retry on a plan with OCR pages available.
}
if (error.code === 'ocr_quota_exceeded') {
// Refused before any OCR ran, so nothing was billed for this attempt.
}
throw new Error(error.message);
}
Nothing failed is ever billed
Usage is recorded after a conversion succeeds, never before. A refusal costs
nothing, whether it was for quota, for size, or for an unreadable file. And
ocr_quota_exceeded is decided from the page count, before any OCR model is
called at all.
Authentication answers uniformly
An absent key, a malformed one, one that never existed and one that was revoked
all return the same 401 with the same message. Distinguishing them would only
help someone working out which keys are real.