Document Conversion API¶
Base path: /api/v1/pdf_handler/
| Method | Path | Purpose |
|---|---|---|
POST |
/convert/ |
Submit a conversion (multipart upload) → 202 |
POST |
/convert/json/ |
Submit a conversion (JSON body, URL or stored object) → 202 |
GET |
/conversion/{id}/ |
Poll conversion status |
GET |
/{id}/download/ |
Download the result |
GET |
/history/ |
List your recent conversions |
GET |
/formats/ |
Supported formats |
GET |
/operations/ |
Engines and options for one format pair |
POST |
/save/ |
Save an edited PDF |
Conversions are asynchronous: submit, poll, then download.
1. Submit¶
POST /convert/ takes multipart/form-data. Give exactly one input source,
a file, a URL, or the key of an object already in indox storage.
curl -X POST https://indox.org/api/v1/pdf_handler/convert/ \
-H "Authorization: Bearer $INDOX_TOKEN" \
-F "[email protected]" \
-F "target_formats=pdf,txt"
{
"id": "6f1d8f0c-4c1f-4a1e-9a1b-0f2c5d6e7a8b",
"status": "pending",
"destination": "aws",
"requested_formats": ["pdf", "txt"],
"target_formats": ["pdf", "txt"],
"service": "doc"
}
The status code is 202 Accepted. Keep id, every later call needs it.
Note
The generated OpenAPI schema for this route names the identifier job_id.
The field the API actually returns is id. Read id.
Request fields¶
| Field | Type | Notes |
|---|---|---|
file |
file | Multipart upload. One source only. |
file_url |
string | Fetch the input from a public URL. On /convert/json/ this field is named url. |
s3_key |
string | Key of an object already in indox storage. |
target_formats |
string | Comma-separated (pdf,docx) or a JSON list (["pdf","docx"]). Required, unless options is present. |
destination |
enum | aws (default), google_drive, dropbox, box, onedrive. |
folder_path |
string | Destination folder for Dropbox / OneDrive. |
box_folder_id |
string | Destination folder for Box. |
redirect_url |
string | Notified with the result when the job finishes. Must be a public URL. |
engine |
string | Force a converter (e.g. libreoffice, calibre, pandoc). Optional, the router picks one for you. |
options |
JSON object | PDF operations. See Editing a PDF. |
What a request costs is on the rate card; the free tier's daily allowance is in Limits & errors.
POST /convert/json/ accepts the same fields as a JSON body, for url or
s3_key inputs. File uploads must use /convert/.
Repeated POST /convert/ calls create new conversions unless you send an
Idempotency-Key header, with one, a retry returns the original conversion
instead of starting a second. See Limits & errors.
2. Poll¶
{
"conversion_id": "6f1d8f0c-4c1f-4a1e-9a1b-0f2c5d6e7a8b",
"status": "completed",
"requested_formats": ["pdf", "txt"],
"successful_formats": ["pdf", "txt"],
"failed_formats": [],
"error_message": null,
"warnings": [],
"output": { "pdf": "/api/v1/pdf_handler/6f1d…/download/" },
"outputs": [
{
"format": "pdf",
"success": true,
"file_size_bytes": 184320,
"download_url": "/api/v1/pdf_handler/6f1d…/download/",
"is_bundle": false
}
],
"download_url": "/api/v1/pdf_handler/6f1d…/download/",
"bundle_download_url": null,
"file_size_bytes": 184320,
"created_at": "2026-08-04T09:12:03.114Z",
"updated_at": "2026-08-04T09:12:11.902Z",
"service": "doc"
}
Statuses¶
| Status | Terminal | Meaning |
|---|---|---|
pending |
no | Queued, not started. |
processing |
no | A worker is on it. |
completed |
yes | Every requested format succeeded. |
partial |
yes | Some formats succeeded, read successful_formats / failed_formats. |
failed |
yes | Nothing was produced; error_message says why. |
Stop polling on completed, partial, or failed. Poll every 2–5 s and back
off exponentially past 60 s. download_url is null while there is no output,
so treat its presence, not the status alone, as the signal to download.
When you request several formats, outputs may carry an extra entry with
is_bundle: true: a ZIP of all results, linked from bundle_download_url.
3. Download¶
curl -L https://indox.org/api/v1/pdf_handler/$ID/download/ \
-H "Authorization: Bearer $INDOX_TOKEN" \
-o report.pdf
With destination=aws the endpoint streams the file back with a
Content-Disposition filename. With a cloud destination it answers with a
redirect to the provider's link, so follow redirects (-L) either way.
400 file not ready means the conversion has no output yet, keep polling.
Listing past conversions¶
GET /history/?page=1&page_size=20 returns your own conversions, newest first.
page_size is clamped to 1–200. The response carries conversions,
total_count, page, page_size, and has_next.
See Errors and limits for status codes, size caps, and how long results are kept.