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. |
Each requested format is charged separately, so pdf,txt costs two credits.
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.