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:
- Create a job with your API key and a file. Response is 202 Accepted with an id like
job_01H.... - Track the job with status GET, SSE, or webhook.
- Download when
statusissucceeded.
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:
Authorization: Bearer sk-xxx
or:
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 laterwebhook_url— we POST when the job finishes (optional)webhook_secret— optional secret for webhook signature
Job status
Use:
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:
GET /api/v1/jobs?status=processing&client_reference_id=order_123&limit=20
Cancel:
POST /api/v1/jobs/{id}/cancel
Balance:
GET /api/v1/balance
Download (only after success):
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:
"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:
POST /api/v1/jobs/parse
Content type: multipart form. Field file = PDF.
Useful form fields:
table_mode—markdown(default) orimageformula_format—dollar(default) orbracketenable_translation—true/falsetarget_language—zhenjakofrdeesru(when translation is on)output_options— comma list:original,translated,bilingualimages_as_url— when enrichment is off,truereturns 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 (plusoutline.md/summary.md/ …). Clients must detect ZIP vs raw markdown (magicPKorContent-Type).skip_rotation_detection,enable_cross_page_merge—true/falseenrichment— optional AI reading aids attached after parse (does not rewrite the primary Markdown body).- Omit → default
outline,summarywhen the server hasENRICHMENT_LLM_API_KEYconfigured. 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, …).
- Omit → default
Points: 2 / page parse only; 3 / page with translation. Enrichment sidecars do not cost extra points.
Example:
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):
{
"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:
# 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
| Condition | result.kind |
|---|---|
| Default parse (md + images) | zip |
images_as_url=true and enrichment off / skipped | markdown |
Enrichment sidecars produced (default outline,summary) | zip even if images_as_url=true |
enrichment=none | same 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:
POST /api/v1/jobs/translate-pdf
Multipart form. Field file = PDF.
Useful fields:
sourceLanguageorsource_language— defaultentargetLanguageortarget_language— defaultzhlayoutModes/output_modes/outputModes—translated_only,side_by_side(comma or JSON array)enable_image_translation/enableImageTranslationenable_table_translation/enableTableTranslationglossary— 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:
en,zh
apple,香蕉
SenB,森巴牌
Example:
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.
layoutModes | result.kind |
|---|---|
one of translated_only / side_by_side | usually pdf |
| both modes | zip 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:
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:
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
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
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