Errors and retries
ErrorResponse envelopes, typed SDK errors, safe retries, idempotency, and timeouts.
Non-success HTTP responses return a JSON ErrorResponse envelope:
{
"error": {
"code": "limit_exceeded",
"message": "source exceeds 50 MiB admission limit",
"request_id": "req_01h2example",
"retryable": false,
"details": {
"limit": "max_input_bytes",
"max_value": 52428800
}
}
}Include request_id when contacting support. When retryable is false, fix the request before sending it again.
Common errors
| Code | Typical HTTP | Meaning |
|---|---|---|
idempotency_conflict | 409 | The key was already used with different input. |
limit_exceeded | 413 | A file, rendered page, or batch exceeds a limit. |
unsupported_media_type | 415 | The upload is not a supported PDF, PNG, or JPEG. |
unsupported_ocr_model | 422 | ocr_model is unknown. |
unsupported_llm_model | 422 | llm_model is unknown or deprecated. |
pipeline_name_conflict | 409 | The pipeline name or slug already exists. |
Synchronous parse and extract requests also return 422 if the job reaches failed during the wait window and 504 if it reaches indeterminate.
Admission limits
| Limit | Value |
|---|---|
| File uploaded for parse or extract | 50 MiB |
Reusable file uploaded to POST /files | 100 MiB |
| Rendered pixels per page | 100 million |
| Aggregate batch input | 100 MiB |
| Child jobs per batch | 100 |
There is no page-count limit. OpenParser accepts PDF, PNG, and JPEG; encrypted PDFs and source URLs are not supported.
Typed errors
The TypeScript and Python SDKs map HTTP status codes to typed subclasses of OpenParserError. Each carries status, code, requestId / request_id, retryable, and the parsed API body. curl returns the response body directly. The CLI writes a concise error to stderr and exits non-zero; use --json for structured successful output.
| HTTP | Class |
|---|---|
| 400 | OpenParserValidationError |
| 401 | OpenParserAuthError |
| 402 | OpenParserPaymentRequiredError |
| 403 | OpenParserForbiddenError |
| 404 | OpenParserNotFoundError |
| 409 | OpenParserConflictError |
| 413 | OpenParserLimitExceededError |
| 415 | OpenParserUnsupportedMediaError |
| 422 | OpenParserUnprocessableError |
| 429 | OpenParserRateLimitError (retryAfter / retry_after) |
| 503 | OpenParserServiceUnavailableError (retryAfter / retry_after) |
| 504 | OpenParserGatewayTimeoutError |
| 5xx | OpenParserServerError |
| 0 | OpenParserTimeoutError (client-side deadline, not an API response) |
429 and 503 also expose the Retry-After response header on the exception when the server sends it.
curl -sS -w '\n%{http_code}\n' ... | tee /tmp/op-response.json
jq '.error' /tmp/op-response.jsonif ! openparser jobs get "$JOB_ID" --json; then
echo "OpenParser request failed" >&2
exit 1
fiimport {
OpenParserRateLimitError,
OpenParserTimeoutError,
} from '@openparser/sdk';
try {
await client.parse.sync({ ocr_model: 'paddleocr-vl-1.6' }, file);
} catch (err) {
if (err instanceof OpenParserRateLimitError) {
const waitSeconds = err.retryAfter ?? 60;
// ...
}
if (err instanceof OpenParserTimeoutError) {
// Per-request deadline (default 300_000 ms) — not retried
}
throw err;
}from pathlib import Path
from openparser.errors import OpenParserRateLimitError, OpenParserTimeoutError
try:
client.parse.sync(
{"ocr_model": "paddleocr-vl-1.6"},
file=Path("document.pdf"),
)
except OpenParserRateLimitError as err:
wait_seconds = err.retry_after or 60
except OpenParserTimeoutError:
# Per-request deadline (default 300 s) or wait_for_job timeout
raiseSafe retries
OpenParser does not silently retry work server-side. Clients may retry only when replaying the same request is safe.
Retriable HTTP statuses: 429 and 5xx. Other 4xx responses fail fast — fix the input or credentials before trying again.
Retriable requests:
| Request | Retries on 429 / 5xx / transport error? |
|---|---|
GET, HEAD (jobs, models, pipelines) | Yes |
Parse/extract admission (POST with Idempotency-Key) | Yes |
| File upload, pipeline create/update/delete, suggest-schema | No |
The TypeScript and Python SDKs retry eligible requests up to maxRetries / max_retries (default 3, so up to four attempts including the first). Between attempts they wait for Retry-After when the header is present and parseable; otherwise they use exponential backoff starting at 250 ms (250 × 2^attempt).
Client-side request timeouts (OpenParserTimeoutError, status = 0) are not retried in the TypeScript SDK. In Python, an httpx timeout on a retriable request may still be retried until attempts are exhausted.
Idempotency
Parse and extract admission (POST /parse, /parse/async, /parse/batch, /extract, /extract/async, /extract/batch) requires an Idempotency-Key header (1–256 characters).
- Generate a new key for each distinct request body.
- Reuse the same key only when retrying the exact same admission after a network error, ambiguous timeout, or other unclear outcome.
- Reusing a key with different input returns
409withidempotency_conflict.
Helper routes (GET, file pool, pipelines) do not use idempotency keys.
curl -X POST 'https://api.openparser.dev/parse' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \
-F 'request={"ocr_model":"paddleocr-vl-1.6","output_format":"openparser@1"};type=application/json' \
-F 'file=@./document.pdf'openparser parse sync ./invoice.pdf --idempotency-key "invoice-2026-04-01"const key = 'invoice-2026-04-01';
const retryingClient = new OpenParserClient({ maxRetries: 3 });
const parsed = await retryingClient.parse.sync(
{ ocr_model: 'paddleocr-vl-1.6' },
file,
{ idempotencyKey: key },
);key = "invoice-2026-04-01"
retrying_client = OpenParserClient(max_retries=3)
parsed = retrying_client.parse.sync(
{"ocr_model": "paddleocr-vl-1.6"},
file=Path("invoice.pdf"),
idempotency_key=key,
)Retry-After
For 429 Too Many Requests and 503 Service Unavailable, the API may send a Retry-After header with the number of seconds to wait before trying again. A temporary lack of OCR capacity does not reject a valid job — it remains queued.
When retrying admission after rate limiting or overload:
- Wait for
Retry-After(or theretryAfter/retry_afterfield on the SDK exception). - Reuse the same
Idempotency-Keyand request body.
The TypeScript SDK parses Retry-After as a non-negative delta in seconds. The Python SDK also accepts an HTTP-date. Malformed or missing values fall back to exponential backoff.
Timeouts
Two different timeout concepts apply:
| Kind | HTTP status | Meaning |
|---|---|---|
| Client deadline | 0 (OpenParserTimeoutError) | The SDK or HTTP client stopped waiting (default 300 s per request). |
| Sync gateway timeout | 504 (OpenParserGatewayTimeoutError) | The server returned before the job reached a terminal state during a synchronous wait (indeterminate). |
Defaults: timeoutMs: 300_000 (TypeScript) and timeout_seconds: 300.0 (Python). Set timeoutMs <= 0 in TypeScript to disable the per-request deadline.
Disconnecting does not cancel an admitted job. OpenParser API v1 has no job-cancellation endpoint, and a result published after you stop waiting is still billable.
curl --max-time 300 ...openparser parse sync ./document.pdf --jsonconst client = new OpenParserClient({
timeoutMs: 300_000, // default; 0 disables
maxRetries: 3,
});client = OpenParserClient(timeout_seconds=300.0, max_retries=3)
# Poll async jobs (separate from per-request HTTP timeout)
job = client.wait_for_job(job_id, timeout_seconds=300.0, poll_interval_seconds=2.0)