KolmoPDF API Guide

Base URL: https://www.kolmopdf.com

This guide covers the public Jobs API for PDF parsing, layout-preserving PDF translation, and format conversion. You can read it yourself or paste it into an AI assistant.

How it works

Long jobs do not block the HTTP request. You create a job, get a job id back right away, then check progress or wait for a webhook. When the job succeeds, download the result.

Typical flow:

  1. Create a job with your API key and a file. Response is 202 Accepted with an id like job_01H....
  2. Track the job with status GET, SSE, or webhook.
  3. Download when status is succeeded.

You can close the client after create. Keep the job id (or your own client_reference_id) and come back later.

Result files are kept for 7 days. If parse uses images_as_url=true, image URLs last 30 days.

Auth

Create keys on the site API Management page. Free accounts get 1 limited-time key; Go and Plus get 1 permanent key; Pro gets up to 10 keys.

Send the key in a header:

httpClick to Copy
Authorization: Bearer sk-xxx

or:

httpClick to Copy
X-API-Key: sk-xxx

?api_key=sk-xxx also works for quick tests. Prefer headers in production.

Points for a key = the lower of account balance and that key’s remaining limit. Account points and API points share the same balance.

Common rules

Max file size: 300 MB.
PDF parse / PDF translation: max 800 pages.
Each API key: up to 3 jobs running at once; more jobs wait in queue.

Optional header on create: Idempotency-Key. Same key + same request returns the same job and does not charge twice.

Optional form fields on create:

  • client_reference_id — your own id so you can find the job later
  • webhook_url — we POST when the job finishes (optional)
  • webhook_secret — optional secret for webhook signature

Job status

Use:

httpClick to Copy
GET /api/v1/jobs/{id}

{id} is the job_... value from create.

status can be: queued, processing, succeeded, failed, cancelled.

The body may also include phase, progress (0–100 or null), message, and links.

List jobs:

httpClick to Copy
GET /api/v1/jobs?status=processing&client_reference_id=order_123&limit=20

Cancel:

httpClick to Copy
POST /api/v1/jobs/{id}/cancel

Balance:

httpClick to Copy
GET /api/v1/balance

Download (only after success):

httpClick to Copy
GET  /api/v1/jobs/{id}/download
HEAD /api/v1/jobs/{id}/download

Do not guess the extension. When status is succeeded, result tells you the real file:

jsonClick to Copy
"result": {
  "filename": "paper.zip",
  "kind": "zip",
  "content_type": "application/zip",
  "bytes": 1843200,
  "sha256": "…",
  "files": [
    { "name": "paper.md", "kind": "markdown" },
    { "name": "outline.md", "kind": "markdown" }
  ],
  "download_url": "/api/v1/jobs/job_01H.../download"
}

kind is one of: zip, pdf, markdown, docx, html, latex, binary.

Save with result.filename. Download headers match: Content-Type, Content-Disposition (filename + filename*), X-Kolmo-Result-Kind, optional Content-Digest / X-Kolmo-Result-Sha256. HEAD returns the same headers without the body.

If a PDF viewer says the file is corrupt, check the first bytes: PK is a ZIP (rename to .zip); %PDF is a PDF. The job did not fail — the extension was wrong.

Wait for completion (SSE first)

SSE (preferred for CLIs / agents): GET /api/v1/jobs/{id}/events with Accept: text/event-stream. Use curl -N (no buffer). Events use id: + event: + data:. Stop on job.succeeded / job.failed / job.cancelled. Resume with Last-Event-ID. Terminal data includes the same result object as GET job.

Webhook: pass webhook_url when you create the job (your own HTTPS endpoint). We POST job.succeeded / job.failed. The payload includes data.result. Always GET the job again before treating it as final. Deliveries may retry; dedupe on the event id. Agents without a public URL should use SSE, not invent a webhook.

Poll (fallback): GET /api/v1/jobs/{id} every few seconds with backoff until a terminal status.

1. PDF parsing

Create:

httpClick to Copy
POST /api/v1/jobs/parse

Content type: multipart form. Field file = PDF.

Useful form fields:

  • table_modemarkdown (default) or image
  • formula_formatdollar (default) or bracket
  • enable_translationtrue / false
  • target_languagezh en ja ko fr de es ru (when translation is on)
  • output_options — comma list: original, translated, bilingual
  • images_as_url — when enrichment is off, true returns a single Markdown file with public image URLs (30-day). When enrichment sidecars are produced, download is still a ZIP whose primary entry is that Markdown (plus outline.md / summary.md / …). Clients must detect ZIP vs raw markdown (magic PK or Content-Type).
  • skip_rotation_detection, enable_cross_page_mergetrue / false
  • enrichment — optional AI reading aids attached after parse (does not rewrite the primary Markdown body).
    • Omit → default outline,summary when the server has ENRICHMENT_LLM_API_KEY configured.
    • none → disable aids; download shape matches pre-enrichment behaviour.
    • Examples: outline, outline,summary, outline,summary,verification, tables.
    • If the source text is longer than 600,000 characters, AI aids are skipped; parse still succeeds and download is unchanged.
    • When aids are produced, download is a ZIP: original parse output plus sidecars (outline.md, summary.md, enrichment_meta.json, …).

Points: 2 / page parse only; 3 / page with translation. Enrichment sidecars do not cost extra points.

Example:

bashClick to Copy
curl -X POST 'https://www.kolmopdf.com/api/v1/jobs/parse' \
  -H 'Authorization: Bearer sk-xxx' \
  -H 'Idempotency-Key: demo-parse-1' \
  -F 'file=@document.pdf' \
  -F 'table_mode=markdown' \
  -F 'enable_translation=false'

Example create response (202):

jsonClick to Copy
{
  "id": "job_01H...",
  "object": "job",
  "type": "pdf-to-markdown",
  "status": "queued",
  "phase": "queued",
  "progress": 0,
  "points_deducted": 20,
  "remaining_points": 80,
  "links": {
    "self": "/api/v1/jobs/job_01H...",
    "events": "/api/v1/jobs/job_01H.../events",
    "cancel": "/api/v1/jobs/job_01H.../cancel",
    "download": null
  },
  "created_at": "2026-08-06T12:00:00.000Z"
}

Then wait (SSE) and save using the filename from GET job — never hard-code .zip / .pdf / .md:

bashClick to Copy
# Wait until event: job.succeeded | job.failed | job.cancelled
curl -N -sS -H 'Authorization: Bearer sk-xxx' \
  -H 'Accept: text/event-stream' \
  'https://www.kolmopdf.com/api/v1/jobs/job_01H.../events'

# Read result.filename (and result.kind) from GET, then:
NAME=$(curl -sS -H 'Authorization: Bearer sk-xxx' \
  'https://www.kolmopdf.com/api/v1/jobs/job_01H...' | jq -r '.result.filename')
curl -L -H 'Authorization: Bearer sk-xxx' \
  'https://www.kolmopdf.com/api/v1/jobs/job_01H.../download' \
  -o "$NAME"

Parse download shape

Conditionresult.kind
Default parse (md + images)zip
images_as_url=true and enrichment off / skippedmarkdown
Enrichment sidecars produced (default outline,summary)zip even if images_as_url=true
enrichment=nonesame as pre-enrichment

Do not assume images_as_url=true is a raw .md file. Always use result.kind / magic PK.

2. PDF layout translation

Create:

httpClick to Copy
POST /api/v1/jobs/translate-pdf

Multipart form. Field file = PDF.

Useful fields:

  • sourceLanguage or source_language — default en
  • targetLanguage or target_language — default zh
  • layoutModes / output_modes / outputModestranslated_only, side_by_side (comma or JSON array)
  • enable_image_translation / enableImageTranslation
  • enable_table_translation / enableTableTranslation
  • glossary — optional CSV glossary (UTF-8, max 2MB). Header language codes: en,zh,ja,ko,fr,de,es,ru (any subset).

Points: 2 / page.

Glossary example:

csvClick to Copy
en,zh
apple,香蕉
SenB,森巴牌

Example:

bashClick to Copy
curl -X POST 'https://www.kolmopdf.com/api/v1/jobs/translate-pdf' \
  -H 'Authorization: Bearer sk-xxx' \
  -F 'file=@document.pdf' \
  -F 'sourceLanguage=en' \
  -F 'targetLanguage=zh' \
  -F 'layoutModes=translated_only' \
  -F 'glossary=@glossary.csv'

Status and download use the same job endpoints as above.

layoutModesresult.kind
one of translated_only / side_by_sideusually pdf
both modeszip of PDFs

Never save as translated.pdf until you have read result.filename. A ZIP saved as .pdf opens as “file is damaged”.

3. Format conversion

Create:

httpClick to Copy
POST /api/v1/jobs/convert

Multipart form. Field file = Markdown (.md / .markdown) or a ZIP of markdown + images.

Target format field: format / target_format / targetFormat.
Values: word, docx, html, pdf, latex, tex (default word).

Points: 1 / task.

Example:

bashClick to Copy
curl -X POST 'https://www.kolmopdf.com/api/v1/jobs/convert' \
  -H 'Authorization: Bearer sk-xxx' \
  -F 'file=@document.md' \
  -F 'targetFormat=pdf'

Again, track and download with GET /api/v1/jobs/{id} and .../download. Use result.filename. If the input was a ZIP and the target is LaTeX, kind may be zip rather than latex.

Minimal script shape

textClick to Copy
POST create → save job id
GET /events (SSE) until job.succeeded | job.failed | job.cancelled
  fallback: loop GET job with backoff
if succeeded: GET job → result.filename / result.kind
GET download -o "$filename"
if first bytes are PK and name is not .zip → rename to .zip

Or create with webhook_url (your backend) and only GET when the webhook fires.

Errors (short)

401 — bad or missing API key / plan not allowed
402 — not enough points
400 — bad file or params
409 — download before the job finished
500 — server error; if the job failed after charge, points are refunded

Common error_code values: invalid_api_key, insufficient_points, no_file_found, parse_file_not_pdf, parse_file_too_large, parse_page_limit_exceeded.

Endpoint list

textClick to Copy
POST /api/v1/jobs/parse
POST /api/v1/jobs/translate-pdf
POST /api/v1/jobs/convert
GET  /api/v1/jobs/{id}
GET  /api/v1/jobs
GET  /api/v1/jobs/{id}/events
GET  /api/v1/jobs/{id}/download
HEAD /api/v1/jobs/{id}/download
POST /api/v1/jobs/{id}/cancel
GET  /api/v1/balance