Skip to content

Document Conversion — Errors and Limits

Applies to /api/v1/pdf_handler/. See Errors for the platform-wide status reference.

Status codes to handle

POST /convert/ and POST /convert/json/

Status error Cause Retry?
202 Accepted; poll GET /conversion/{id}/.
400 target_formats is required No target_formats and no options. Fix the request.
400 unsupported_output_format An output format the service does not produce. formats lists the offenders. Fix the request.
400 unsupported_conversion The pair is not available. input_format and output_formats name it. Fix the request.
400 Unsupported input file format: … The input extension is not accepted. Fix the request.
400 unknown_engine The engine you forced does not exist. Fix the request.
400 Provide either a file, an s3_key, or a url No input source. Fix the request.
400 Provide only one of file, s3_key, or url More than one input source. Fix the request.
400 file_content_mismatch The bytes do not match the extension. claimed and detected say what was found. Fix the file.
402 anonymous_quota_exceeded Daily free allowance used up. limit, used_today, and remaining are included. Tomorrow, or sign in.
402 Insufficient balance / Insufficient credit Signed in, no credits left. After topping up.
413 File size exceeds the NMB limit for … Upload over the cap for your tier. No — send a smaller file.
503 quota_check_failed The quota service could not be reached; the request was refused rather than risk a free ride. Yes, shortly.

A rejected request never creates a conversion and never spends a credit.

GET /conversion/{id}/

Status Cause
200 Status returned. Read status, then download_url.
404 Unknown or removed conversion id.

A failed conversion is still a 200 — the failure is in the body (status: "failed", error_message), not in the status code. Check both.

GET /{id}/download/

Status Cause
200 The file, streamed, with a Content-Disposition filename.
302 Redirect to the file at your cloud destination. Follow it.
400 file not ready — the conversion has produced no output yet.
404 Unknown, removed, or purged conversion.
500 Storage read failed. Safe to retry.

POST /save/

Status Cause
201 Saved and delivered.
400 file_or_file_base64_required, invalid_base64, or missing_<provider>_token.

Idempotency

POST /convert/ and POST /convert/json/ accept an optional Idempotency-Key header. Send a UUID v4 that stays stable across retries of the same intent:

Idempotency-Key: 3f8a1c22-7d4e-4b91-a0f5-9c2e6b7d1a44
Situation Result
First request with a key 202 — the conversion is created as normal
Retry with the same key and the same payload 202 — returns the original conversion, no second job, no second charge
Same key, different payload 422 Idempotency-Key reused with a different payload
No header at all Unchanged behaviour — every call creates a new conversion

The header is optional. If you omit it, a blind retry after a timeout can create a second conversion, so either send a key or poll GET /history/ before resubmitting.

Keys are scoped to the caller, so two different accounts may use the same key value without colliding.

File size

Caller Maximum upload to /convert/
Anonymous (free tier) 5 MB
Signed in 25 MB

These are the platform defaults and can be raised by the operator, up to 500 MB. Over the limit you get 413 with the effective limit stated in the message; nothing is stored and no credit is spent.

The cap applies to the multipart upload itself. A url or s3_key input is not measured against it.

Credits

  • Anonymous callers get 10 credits per day, counted per device. Each requested output format costs one credit, so a single pdf,docx request costs two.
  • Signed-in callers spend from their balance, one credit per requested format by default. See Billing.

Retention

Converted and saved files are deleted from storage on a schedule. The history row — filename, formats, status, timestamps — is kept, so an expired conversion still appears in GET /history/, but downloading it no longer returns a file.

Caller Files kept
Anonymous (free tier) 24 hours after the conversion reaches a terminal state
Signed in 180 days after the conversion was created

The purge runs hourly, so deletion happens shortly after the window closes, not to the second. These are the default windows and are operator-configurable — check your plan if you depend on a specific figure. Download anything you need to keep, or send it straight to a cloud destination, where indox's retention does not apply.