mineru-runpod
Reference

API reference

The JSON job-input contract for the mineru-runpod worker, plus the success and failure response shapes.

This page documents the JSON payload contract the worker accepts. It mirrors the docstring in handler.py, which is the source of truth; this is a friendlier rendering.

Job input

Send a POST to /v2/{endpoint_id}/runsync (or /run for async) with an input object:

{
  "input": {
    "file_url":       "https://example.com/report.pdf",
    "start_page":     0,
    "end_page":       99,
    "lang":           "en",
    "backend":        "vlm-auto-engine",
    "formula_enable": true,
    "table_enable":   true,
    "transport":      "tarball_b64",
    "formats":        ["markdown", "content_list", "middle", "images"],
    "basename":       "my-doc"
  }
}

Complete cURL request:

curl --request POST "https://api.runpod.ai/v2/<endpoint-id>/runsync" \
  --header "Authorization: Bearer $RUNPOD_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"input":{"file_url":"https://example.com/report.pdf","transport":"inline","formats":["markdown"]}}'

Required (exactly one of)

The worker accepts PDF, image (PNG/JPEG/GIF/BMP/TIFF/WebP), DOCX, PPTX, and XLSX, auto-detected from the bytes. Provide exactly one input source; providing zero or two raises a validation error.

FieldTypeNotes
file_urlstringHTTP/HTTPS URL the worker can GET, resolving to a publicly routable host. Downloaded server-side with a 200 MB cap and a 120 s timeout. See MINERU_ALLOW_LOCAL_FETCH for serving documents from inside your own network
file_b64stringBase64-encoded file bytes. RunPod caps the whole JSON request at 10 MB on /run and 20 MB on /runsync. Base64 adds about 33%, so keep raw files below roughly 7.5 MB and 15 MB respectively; for larger inputs use file_url or volume_path.
volume_pathstringAbsolute path to a file inside the container, under one of the worker's input roots. Useful for files mounted via a RunPod network volume or baked into the image

Optional

FieldTypeDefaultNotes
start_pageint00-based, inclusive
end_pageint-10-based, inclusive. Any negative value or omission means "to end of document". When set, must be >= start_page
langstring"en"OCR model hint for the pipeline backend; VLM backends ignore it. MinerU 3.4's public choices are ch, ch_server, korean, ta, te, ka, th, el, arabic, east_slavic, cyrillic, and devanagari. The default en remains a compatibility alias that normalizes to ch. See Input formats.
backendstring"vlm-auto-engine"One of pipeline | vlm-auto-engine | vlm-http-client | hybrid-auto-engine | hybrid-http-client. The pipeline and hybrid-* backends use MinerU's PP-OCRv6 OCR models. See Choosing a GPU → Picking a backend
effortstringnullHybrid backends only (hybrid-auto-engine / hybrid-http-client). "medium" (MinerU's default when omitted) or "high": "high" enables image/chart analysis at a speed cost, "medium" skips it for throughput. Rejected on non-hybrid backends.
server_urlstringnullRequired for *-http-client backends. URL of an external vLLM OpenAI-compatible server (e.g. https://your-host/v1)
formula_enablebooltrueExtract LaTeX equations
table_enablebooltrueExtract structured HTML tables
transportstring"tarball_b64"How the worker ships output. "tarball_b64" (default, base64-encoded .tar.gz inside the entry), "inline" (per-format keys inside the entry, filterable via formats), or "s3" (presigned URL — requires BUCKET_* env vars on the endpoint). See Output modes.
formatsarray of stringall fourSubset of ["markdown", "content_list", "middle", "images"]. Selects which artifacts the inline payload contains. Omit (or pass all four) to get everything. No-op for tarball_b64 and s3 — those transports always ship a self-contained archive with all four artifacts. Empty list is rejected.
basenamestring"doc"Filename stem for output files. Must be alphanumeric with - or _, at most 128 characters, and short enough that the longest generated filename stays within 255 bytes — non-ASCII characters cost more than one byte each, so a CJK stem is limited to 78 characters
archive_formatstring"tar.gz"Archive container for the tarball_b64 and s3 transports: "tar.gz" (default) or "zip". No-op for inline. See Output modes → archive format.
probeboolfalseDiagnostic mode. {"probe": true} skips parsing and every field above, then returns the worker's filesystem layout plus the running MinerU version instead of a parse result — for checking baked model paths and volume mounts. On by default; set MINERU_DISABLE_PROBE=1 on the endpoint to turn it off, where callers should not see filesystem paths or model-cache details. Any value other than 0/false/no/off disables it, so a typo denies rather than exposes.

URL inputs

file_url and server_url are checked before the worker acts on them:

  • Both must be http:// or https:// with a host. Anything else is rejected with the field name in the message.

  • file_url must additionally resolve to a publicly routable address — what a document URL handed to a serverless worker normally is. Set MINERU_ALLOW_LOCAL_FETCH=1 on the endpoint when you serve documents from a host inside your own network, or when running the handler locally against http://localhost.

  • server_url gets the shape check by default. Setting MINERU_ENFORCE_TARGET_POLICY=1 additionally requires its host to be named in MINERU_ALLOWED_SERVER_HOSTS (comma-separated, exact host match) — including public hosts. Worth understanding, because the default is not the safe one. server_url arrives in the job payload rather than the endpoint environment, so without enforcement any caller of your endpoint can point the worker at a loopback or link-local address — a cloud metadata endpoint included — and have it issue requests there from inside your network. The default is compatibility: a private model server is the ordinary way to run one, and enforcing by default would break every such deployment on a patch upgrade. If your endpoint is reachable by callers you do not control, set MINERU_ENFORCE_TARGET_POLICY=1 and list your model server's host.

    Enforcement is an allow-list rather than an address check, and that is deliberate. Checking where a host resolves cannot protect this field: the engine's own HTTP client resolves the name again and opens the connection, so a host answering publicly at validation can answer privately at connect time. mineru_vl_utils builds its httpx.Client internally with no transport to inject, and pinning the checked address would mean handing it a literal IP with a Host header — which fails certificate validation for any https target. A host the operator named is not subject to rebinding by a caller, which is what makes the list the stronger check.

    Do not use MINERU_ALLOW_LOCAL_FETCH for this. That flag is the operator's exemption for fetching documents from their own network, and it no longer reaches server_url at all.

I just want the Markdown — how do I get it?

Set "transport": "inline" and (optionally) filter to just markdown:

{
  "input": {
    "file_url": "https://example.com/report.pdf",
    "transport": "inline",
    "formats": ["markdown"]
  }
}

Response (truncated):

{
  "ok": true,
  "elapsed_seconds": 4.2,
  "mineru_version": "3.4.x",
  "results": [
    {
      "basename": "doc",
      "source": "url:https://example.com/report.pdf",
      "pages_requested": -1,
      "markdown": "# Document title\n\nFirst paragraph...\n\n## Section\n\nA table:\n\n<table>...</table>\n"
    }
  ],
  "debug": {"...": "..."}
}

For single-file jobs the markdown lives at result.results[0].markdown. Higher-level helpers: the Python MineruClient exposes MineruClient.first(result) to skip the indexing.

tarball_b64 (the default) also includes a .md file inside the gzipped tarball at {basename}.md — extract the tarball and the markdown is there too. Use inline when you want to read the markdown directly without unpacking; use tarball_b64 (or s3) when you also want the structured JSON / image files together.

Success response

{
  "ok": true,
  "elapsed_seconds": 18.4,
  "mineru_version": "3.4.x",
  "results": [
    {
      "basename": "doc",
      "source": "url:https://example.com/report.pdf",
      "pages_requested": 100,
      "tarball_b64": "<base64-encoded gzipped tarball>"
    }
  ],
  "debug": {"...": "..."}
}

Top-level keys (job-scoped)

FieldTypeNotes
okboolAlways true on success
elapsed_secondsfloatWall time inside the handler. Does not include cold-start time or transport
mineru_versionstringThe MinerU version that produced the parse (e.g. 3.4.4)
resultsarrayOne entry per parsed file. Single-file jobs have a one-element list. See below for the entry shape.
debugobjectObservability data: backend, GPU info, phase timings, and a best-effort cached VLM path. See below.
refresh_workerboolPresent only when a REFRESH_WORKER_AFTER_* threshold has tripped. Tells RunPod to recycle the worker after returning.

Per-entry keys (file-scoped, inside results[])

FieldTypeNotes
basenamestringEcho of the input basename, handy for correlating many concurrent jobs
sourcestringEcho of the input source: url:..., b64, or volume:/path/...
pages_requestedintThe slice the caller asked for. -1 if end_page was open-ended
tarball_b64stringPresent when transport: "tarball_b64". Base64-encoded archive of the output directory — .tar.gz by default, .zip when archive_format: "zip"
markdown, content_list, middle, imagesvariousPresent when transport: "inline". The set of keys reflects the formats filter — see below
tarball_url, tarball_url_expires_in, bucket_key, bucket_bytesvariousPresent when transport: "s3". The archive is .tar.gz by default, .zip when archive_format: "zip". See Output modes → s3
degradedobjectPresent only when the response is short of something. See Incomplete responses

Each job parses one file. To process many documents, submit them as individual jobs and raise workers_max — RunPod's queue runs them in parallel across workers (see Scaling).

When transport: "inline"

Each entry contains the requested format keys directly. With the default formats=["markdown", "content_list", "middle", "images"]:

FieldTypeNotes
markdownstringThe full Markdown rendering of the document
content_listarrayFlat list of MinerU block objects. Common types include text, equation, table, image, code, and list; backend-specific auxiliary types such as header and page_number can also appear. Suitable for RAG chunking
middleobjectMinerU's intermediate representation with layout, bounding boxes, reading order
imagesobject{filename: base64-encoded-bytes} for each extracted image; the original image filename and format are preserved

content_list is a convenient integration surface, but it is MinerU output, not a schema owned by this wrapper. Fields can vary by backend and MinerU version. These are the common 3.4.x fields used by the examples in this repo:

Block typeCommon fieldsNotes
texttext, optional text_leveltext_level can indicate a heading, but still needs document-specific validation.
listlist_itemsOrdered markers may be preserved inside the item strings.
tabletable_body, table_caption, table_footnotetable_body is HTML. Caption and footnote fields are arrays when present.
codecode_body, code_captionCode may already include a fenced block.
equationtextUsually LaTeX, sometimes already wrapped in math delimiters.
imagecontent, image_caption, sometimes img_pathcontent is a generated description; use the images map or archive for image bytes.
header, page_number, and other auxiliary typesbackend-specificHeaders, footers, page numbers, margin notes, and footnotes can appear alongside document content.

Most blocks also include zero-based page_idx. Treat unknown block types and extra fields as forward-compatible data rather than rejecting the response. The document-tree case study shows the rendering and verification rules used on a 5,039-page parse.

Filtering with formats

The formats field whitelists which artifacts the inline payload contains. Omitted keys are absent from the entry — not present-as-empty. Useful when only the markdown matters and the per-page images would otherwise bloat the response.

{
  "input": {
    "file_url": "https://example.com/report.pdf",
    "transport": "inline",
    "formats": ["markdown", "content_list"]
  }
}

The resulting entry has markdown and content_list only; middle and images are not present in the response. Empty list is rejected (formats: [] is a validation error).

For transport: "tarball_b64" and transport: "s3", formats is a no-op: the tarball always carries the full set of four artifacts (filtering inside an archive would be confusing for downstream callers).

Incomplete responses

A parse can produce a file that cannot then be read — bytes that are not the UTF-8 the artifact is declared as, JSON truncated by a disk that filled mid-write, an image the filesystem will no longer describe. One corrupt file does not fail the job: the artifact comes back as its empty default, or the archive ships without that member, and the entry gains a degraded object saying so.

{
  "basename": "doc",
  "source": "url:https://example.com/report.pdf",
  "markdown": "# The document's real text\n",
  "content_list": [],
  "degraded": {
    "count": 1,
    "items": [
      {"artifact": "content_list", "file": "doc_content_list.json",
       "reason": "unreadable", "error_type": "JSONDecodeError"}
    ]
  }
}
FieldTypeNotes
countintEverything lost. items stops at 50, so count above items.length means the list is abridged
items[].artifactstring | nullWhich response key it cost. null for an archive member, which is not one of the four formats
items[].filestringFilename only — the output directory is a temp path inside the worker
items[].reasonstringunreadable, unresolvable, outside_output_dir, or unsafe_name
items[].error_typestringException class name, when an exception revealed the problem

degraded is absent on an intact job, so its presence is the signal. Treat an entry that has it as a document to reprocess: ok is true and nothing else will flag it, because from the outside an empty content_list looks the same as a document that had no tables in it.

markdown is the exception. It is the document, so a job that cannot produce readable Markdown fails with ok: false rather than returning an empty string — a failed job can be retried, a successful one that quietly returned nothing cannot. The other three artifacts degrade.

Every item is also emitted as a response degraded warning on stdout, carrying the same fields plus the job_id, and counted as mineru.degraded.total when OpenTelemetry is configured. The field is the per-document signal — the only thing that says which document to reprocess. The log line and the counter are the fleet-wide ones, and they are what tells you the rate is non-zero at all.

Debug observability

Every response includes a top-level debug block with information that lets you correlate a parse to its environment without having to read worker logs:

{
  "debug": {
    "backend": "vlm-auto-engine",
    "model_dir": "/root/.cache/huggingface/hub/models--opendatalab--MinerU2.5-Pro-2605-1.2B/snapshots/<hash>",
    "gpu": {
      "available": true,
      "name": "NVIDIA RTX 4090",
      "compute_capability": "8.9",
      "total_memory_gb": 23.99
    },
    "phase_ms": {
      "fetch_input": 12,
      "mineru_parse": 18420,
      "package": 95
    }
  }
}
FieldNotes
backendThe backend that ran. Echoes the input or the default
model_dirBest-effort path to a cached MinerU VLM snapshot. It confirms a matching cache directory exists, not which model the selected backend loaded. Pipeline weights are baked separately.
gpuCard name, compute_capability (8.6 = Ampere, 8.9 = Ada, 9.0 = Hopper, 12.0 = Blackwell), VRAM. Helps debug "why did my job land on a different card than my pool config?"
phase_msPer-phase timings: fetch_input (download/decode), mineru_parse (MinerU's aio_do_parse), package (tarball or inline assembly)

On failure, debug still contains gpu, model_dir, and whatever phase_ms was collected before the error.

Failure response

When the handler raises or returns an error, the response sets ok: false and includes a top-level error key. RunPod marks the job FAILED in the dashboard based on the presence of that key. There is no results list on a failure response.

{
  "error": "ValueError: must provide exactly one of file_url / file_b64 / volume_path",
  "ok": false,
  "elapsed_seconds": 0.1,
  "mineru_version": "3.4.x",
  "traceback": "Traceback (most recent call last):\n  File ...",
  "debug": {"gpu": {"...": "..."}, "model_dir": "...", "phase_ms": {"...": "..."}}
}
FieldTypeNotes
errorstringType name + message, e.g. ValueError: ...
okboolAlways false on failure
elapsed_secondsfloatTime before the error
mineru_versionstringVersion that was running
tracebackstringLast 5 frames of the Python traceback; useful for debugging
debugobjectSame shape as the success response — gpu, model_dir, and whatever phase_ms was collected before the failure

error and traceback are normalized before they are returned: URLs inside them keep their scheme, host and path but drop credentials, query and fragment, and very long text is truncated. The same text goes to the worker's logs and, when OpenTelemetry export is configured, to the collector — so one failure reads the same in all three places.

Progress updates (streaming)

The handler posts {"status": "IN_PROGRESS", "output": ...} events during a parse, which is what /status and /stream report while the job runs. Phases:

{"phase": "fetching_input"}
{"phase": "parsing", "input_bytes": 1234567, "input_format": "pdf", "start_page": 0, "end_page": 99}

Each update is awaited before the parse continues, rather than posted from a background thread the handler does not wait for. Progress travels to the same job-results endpoint as the final result, so an update still in flight at completion can be applied after the COMPLETED status and leave a finished job stuck IN_PROGRESS. Waiting for it is what keeps the two in order regardless of how fast the parse is — office inputs (DOCX/PPTX/XLSX) skip model inference and can finish in under 100 ms.

There is deliberately no packaging phase event either: it would carry no information a caller can act on, and no update is emitted after the parse.

With the RunPod SDK, submit asynchronously and stream from the returned job:

job = endpoint.run({"file_url": "https://example.com/report.pdf"})
for update in job.stream():
    print(update)

The HTTP equivalent is GET /v2/{endpoint_id}/stream/{job_id}. The MineruClient wrapper only supports run_sync, so use the RunPod SDK or HTTP directly when you need progress.

Validation behaviour

The handler validates input before doing any work:

  1. Field types and bounds via runpod.serverless.utils.rp_validator. Wrong type, missing required field, out-of-range value, or unknown field name raises ValueError.
  2. XOR source rule. Exactly one of file_url, file_b64, volume_path must be set. Zero or two raises ValueError.
  3. Basename safety. Every character in basename must be alphanumeric, -, or _, and the whole stem at most 128 characters. Unicode letters and digits are accepted — and because filesystems bound a filename in bytes rather than characters, the stem plus the longest suffix the worker appends (_content_list_v2.json) must also stay within 255 bytes. That is 234 characters' worth of headroom for ASCII, but only 78 for three-byte characters such as CJK.
  4. Inline file size. The worker rejects decoded file_b64 data over 20 MB — measured first on the encoded string, then on the decoded bytes. RunPod's whole-request limits are reached earlier because base64 expands the raw file; use the practical thresholds above.
  5. Format detection. The first few bytes must match a known signature (PDF / image / OOXML); otherwise ValueError.
  6. formats membership. Each entry must be one of ["markdown", "content_list", "middle", "images"]; empty list is rejected; duplicates are silently collapsed.
  7. URL fields. file_url and server_url must be http(s) with a host. file_url must also resolve to a publicly routable address unless MINERU_ALLOW_LOCAL_FETCH=1. Under MINERU_ENFORCE_TARGET_POLICY=1, server_url's host must appear in MINERU_ALLOWED_SERVER_HOSTS; MINERU_ALLOW_LOCAL_FETCH does not affect it. See URL inputs.
  8. volume_path roots. The path must be absolute and, once resolved, sit under one of the worker's input roots. See What volume_path accepts.
  9. lang shape. A short script/language code: letters, digits, - or _, up to 32 characters.
  10. Page range. start_page must be >= 0; an end_page of 0 or more must be >= start_page. If the endpoint sets MINERU_MAX_PAGES_PER_JOB, an explicit range larger than that is rejected — see Scaling.

All validation errors produce a failure response with error: "ValueError: ..." and the job is marked FAILED.

Source of truth

This page is regenerated by hand when the contract changes. The authoritative source is the docstring at the top of handler.py plus the INPUT_SCHEMA dict and validate_input function in worker/schema.py. If you see a discrepancy, the code wins; please open an issue so the docs page can be updated.

The formats values and the transport list are not spelled out in the schema: the formats are the keys of the output manifest in worker/harness.py, in declaration order, and the transports are whatever the runpod-doc-worker harness can pack. Response assembly — the results entries, the archive, the presigned URL — is that harness's; which files go into it is the manifest's.

Last updated on

On this page