Private Cloud OCR batch jobs
A job converts every document under a folder in your object storage. You name the folder; the deployment lists it, converts each file, writes the outputs next to it (or under another folder), and leaves a manifest and a report in the same bucket. The deployment reads and writes the bucket with its own credentials, so nothing is uploaded through the API and no key leaves your network.
Endpoint contract: reference: batch jobs.
1. Look before you run
?dry_run=true lists the folder, counts files by type, samples PDFs for a page total and
estimates GPU hours at the deployment's measured rate. Nothing is created.
curl -s 'http://pco.internal:8080/pco/v1/jobs?dry_run=true' -H 'Content-Type: application/json' -d '{"input": {"folder": "s3://example-docs/batch1/"}}'
{
"dry_run": true,
"files": {"discovered": 18240, "excluded": 12, "already_processed": 0, "by_type": {"pdf": 17900, "docx": 300, "epub": 40}},
"pages": {"files_sampled": 20, "estimated_total": 233000},
"estimated_gpu_hours": 2.1,
"deployment": {"gpu_workers": 8, "measured_pages_per_minute": 1850.0}
}
2. Submit
curl -s http://pco.internal:8080/pco/v1/jobs -H 'Content-Type: application/json' -d '{
"name": "arxiv-2024-q3",
"input": {"folder": "s3://example-docs/arxiv/2024-q3/", "recursive": true, "include": ["**/*.pdf", "**/*.docx"]},
"output": {"folder": "s3://example-docs/arxiv/2024-q3/", "layout": "alongside", "on_existing": "skip"},
"options": {
"conversion_formats": {"md": true, "docx": true},
"extra_outputs": [".lines.json"],
"ocr": {"math_inline_delimiters": ["\\(", "\\)"], "include_smiles": true}
},
"policy": {"max_pages_per_document": 5000, "per_page_timeout_seconds": 15, "max_retries": 3},
"callback_url": "https://hooks.example.internal/mathpix",
"callback_events": ["job.completed", "file.error"]
}'
201 returns the job object with its job_id. The id is derived from the spec, so posting the
same spec again returns the existing job with 200 instead of starting a second one; with
on_existing: skip that is also how a job resumes after a cancel or a Redis loss.
What the fields mean:
input.folderis a folder URI the deployment's credentials can read:s3://bucket/path/,gs://bucket/path/, orhttps://ACCOUNT.blob.core.windows.net/container/path/. The trailing slash is required, so a typo never silently becomes a one-file job. Every supported document under it is part of the job (recursively unlessrecursive: false), narrowed byincludeandexcludeglobs. For an explicit list of documents, sendinput.files(up to 10,000 object URIs) instead ofinput.folder; exactly one of the two.output.folderdefaults to the input folder.layoutisalongside(doc.pdf→doc.mmd,doc.docxnext to it; requires the output folder to be the input folder),mirror(the input tree reproduced under another folder) orflat(one folder per document id).on_existingisskip, the default, orreplace.options.ocris the same objectPOST /v3/pdfaccepts, validated by the same code with the same error ids, so you can test options on one file first.options.conversion_formatsare the hosted format names..mmdand.lines.jsonare always written;extra_outputsmay add.lines.mmd.json, a byte copy of.lines.jsonfor pipelines that still read that name.policyreplaces per-worker flags:max_pages_per_document(default 5000),per_page_timeout_seconds(15),max_retries(3),priority(low,normal,high).callback_url,callback_events,callback_headersare the hosted API's webhook fields, unchanged. Events:job.completed(default),file.error(recommended),file.completed(opt-in). Unlike the hosted API, a deployment may call private and in-cluster receivers, since it lives inside your network.
A job never overwrites your source file: when an output would land on the input's own URI, an
alongside memo.docx with the docx format requested, it is written as memo.converted.docx.
A rerun over the same folder never converts its own outputs: earlier reports under _mathpix/
tell the enumerator what to pass over.
3. Watch
curl -s http://pco.internal:8080/pco/v1/jobs/j_7f3a9c1e4b
{
"job_id": "j_7f3a9c1e4b",
"name": "arxiv-2024-q3",
"status": "processing",
"enumeration": {"status": "completed", "files_listed": 18240, "files_excluded": 12},
"files": {"discovered": 18240, "queued": 11200, "processing": 24, "completed": 6900, "failed": 96, "skipped": 20},
"pages": {"completed": 231540, "failed": 412},
"throughput_pages_per_minute": 1840.5,
"eta_seconds": 5400,
"output": {
"folder": "s3://example-docs/arxiv/2024-q3/",
"layout": "alongside",
"manifest_uri": "s3://example-docs/arxiv/2024-q3/_mathpix/j_7f3a9c1e4b/manifest.json",
"report_uri": "s3://example-docs/arxiv/2024-q3/_mathpix/j_7f3a9c1e4b/report.jsonl"
},
"versions": {"ocr_version": "SuperNet-200", "pdf_version": "SuperNet-200p2"}
}
status | meaning |
|---|---|
enumerating | listing the folder; documents found so far are already being processed |
processing | enumeration finished, documents in flight |
completed | every document completed or skipped; files.failed is 0 |
completed_with_failures | every document reached a terminal state and some failed; retry is available |
cancelled | cancelled by you; in-flight documents finished, queued ones were skipped |
error | the job itself could not run; error_info says why |
Documents are processed as enumeration finds them, so a million-file folder starts producing
outputs within seconds. eta_seconds is null until enumeration is complete and enough pages
have finished to mean something.
4. Failures, retry, cancel
curl -s 'http://pco.internal:8080/pco/v1/jobs/j_7f3a9c1e4b/files?status=failed'
Each file carries its key, status, pages, attempts, error_id, error_message and the
URIs of its outputs. A completed document with pages_failed above 0 finished with a
page-level error inside it; GET .../files/{document_id} lists the pages. The error ids are the
hosted API's (error handling).
Once the job is terminal, requeue what failed, all of it or only some error ids:
curl -s -X POST http://pco.internal:8080/pco/v1/jobs/j_7f3a9c1e4b/retry -H 'Content-Type: application/json' -d '{"error_ids": ["pdf_content_type"]}'
Stop a running job; in-flight documents finish, queued ones are marked skipped:
curl -s -X POST http://pco.internal:8080/pco/v1/jobs/j_7f3a9c1e4b/cancel
5. What lands in your bucket
Under the output folder, _mathpix/{job_id}/ holds manifest.json (the spec with every default
filled in, the deployment's versions, enumeration totals, timestamps) and report.jsonl, one
line per file as it finishes, identical to what GET .../report streams. The report is the
durable record: if the deployment's Redis is ever lost, every result and every error is still
there, and a resubmission with on_existing: skip resumes from it.
arxiv/2024-q3/_mathpix/j_7f3a9c1e4b/manifest.json
arxiv/2024-q3/_mathpix/j_7f3a9c1e4b/report.jsonl
arxiv/2024-q3/2401.01235.pdf 2401.01235.mmd 2401.01235.md 2401.01235.docx 2401.01235.lines.json
arxiv/2024-q3/images/... (figure and equation crops referenced from the Markdown)
6. Webhooks
Pass callback_url on the job and the deployment POSTs a signed notification when a file fails
(file.error), when the job finishes (job.completed), or when each file completes
(file.completed, opt-in). The payloads, the Mathpix-Signature header, the retry ladder and the
signing secret rotation are the hosted API's (webhooks), so an existing
handler works unchanged. The signing secret is per deployment: read it at
GET /pco/v1/webhook-config, rotate it at POST /pco/v1/webhook-config/secret, send a test
delivery with POST /pco/v1/webhook-config/test, and list deliveries with
GET /pco/v1/deliveries?job_id=....
7. From the command line
The pco tool wraps all of the above:
pco convert s3://example-docs/arxiv/2024-q3/ --formats md,docx --dry-run # the estimate
pco convert s3://example-docs/arxiv/2024-q3/ --formats md,docx # submit and watch to the end
pco convert s3://example-docs/arxiv/2024-q3/ --formats md,docx --detach # submit and return the job id
pco jobs status j_7f3a9c1e4b --watch
pco jobs files j_7f3a9c1e4b --status failed
pco jobs retry j_7f3a9c1e4b --error-id pdf_content_type
pco jobs report j_7f3a9c1e4b > report.jsonl
Coming from Mathpix SCS classic: pco migrate-scs --conversion-options conversion_options.json --ocr-options ocr_options.json --ext-list .lines.json --input-folder s3://bucket/folder/ > job.json
turns your existing option files into a job spec, and pco convert --job-spec job.json --dry-run
confirms the enumeration matches what you publish today.