Skip to content

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

curl https://indox.org/api/v1/pdf_handler/conversion/$ID/ \
  -H "Authorization: Bearer $INDOX_TOKEN"
{
  "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.