Skip to content

API

1 post with the tag “API”

How to self-host the MinerU API on RunPod

Last Updated: 2026-06-08

If you’re calling the official MinerU API (mineru.net/api/v4) in production, you’ve probably hit one of three walls: the daily quota of 1,000 high-priority pages, the per-file 200 MB cap, or a compliance review asking why your documents leave your infrastructure for a third-party cloud. You can run the exact same MinerU engine yourself on RunPod Serverless, keep documents in your own bucket, drop the quota, and pay roughly $0.001 per page warm. The migration is close to drop-in: swap the client, keep your create-task / poll / download loop.

The cloud API is the right way to try MinerU. Once you’re parsing real volume, self-hosting changes the economics and the data path. Here’s why, what it costs, and how to move your code over.

Why self-host the MinerU API instead of using mineru.net?

Section titled “Why self-host the MinerU API instead of using mineru.net?”

Three reasons: cost at volume, data residency, and control. The cloud API meters a daily high-priority page quota and then deprioritizes you; a self-hosted endpoint has no quota. Self-hosted, your PDFs and their parsed output never leave your RunPod worker and your own S3 bucket. And you pin the model version, pick the GPU, and set your own concurrency.

The official MinerU API caps each file at 200 MB and gives each account a daily quota of 1,000 high-priority pages (see the published limits) before jobs drop to lower priority. That ceiling is fine for evaluation and light use. It becomes a planning problem the moment you’re ingesting thousands of pages a day on a deadline.

The data path matters too. On the SaaS, every document round-trips through mineru.net. Self-hosted, the worker pulls the file from a URL you control (or you send the bytes inline), parses on your GPU, and writes the result to your bucket. Nothing transits a vendor you have to put in a data-processing agreement.

The trade-off is honest: you run the infrastructure. There’s a cold-start cost, but FlashBoot blunts it: once a host has booted the worker, it restores from a process snapshot in ~7-8 s across scale-to-zero cycles, and only a brand-new host pays the full ~110 s (vLLM init plus loading the model into VRAM). The model is baked into the image, so nothing is downloaded at request time. If your traffic is a handful of pages a month, the cloud API’s free tier is simpler; cross into steady volume and the math flips.

What does self-hosting MinerU cost vs the cloud API?

Section titled “What does self-hosting MinerU cost vs the cloud API?”

Roughly $0.001 per page warm on a 24 GB RTX 4090, plus a ~$0.03 cold-start tax the first time a host boots the worker (≈110 s of GPU billing for vLLM init and loading the baked model into VRAM). FlashBoot then snapshots that state, so the same host restarts in ~7-8 s across scale-to-zero cycles. RunPod bills per second and scales to zero, so an idle endpoint costs nothing. The cloud API is free under its daily quota, then queues or meters you; self-hosting trades that ceiling for a steady, predictable per-page rate.

The real number depends on how well you amortize that first-boot tax. A thousand pages parsed across one warm window land near $0.001/page; a single short doc on a cold host lands closer to $0.005–$0.01 because you’re paying the one-time tax against very few pages. FlashBoot shrinks that in practice, since a host pays the full tax only once. I broke the full workload-shape math down in Serverless MinerU on RunPod: honest cost math.

One cost the marketing pages skip: the first-boot cold start (~110 s on a brand-new host) is billed GPU time. FlashBoot snapshots that boot, so the same host restarts in ~7-8 s afterward. The model is baked into the image, so there’s no model-download step at boot or request time. Budget for the first boot per host, not for every request.

How do you deploy your own MinerU endpoint on RunPod?

Section titled “How do you deploy your own MinerU endpoint on RunPod?”

Deploy the open-source mineru-runpod template from the RunPod Hub, or fork it and point RunPod’s GitHub build at your fork. Create a Serverless Endpoint on a 24 GB GPU (ADA_24 / RTX 4090) with FlashBoot enabled. For full_zip_url parity with the cloud API, also set four BUCKET_* env vars pointing at an S3-compatible bucket.

The fastest path is the Hub listing: one click, fill in the deploy-time form, done. The full walkthrough (fork-and-build and bring-your-own-image included) is in the deploy guide.

The one piece worth getting right up front is object storage. The compat client returns results as a full_zip_url, which means the worker uploads the output archive to a bucket and hands back a presigned URL, exactly like the SaaS. That path needs four env vars on the endpoint:

Terminal window
BUCKET_ENDPOINT_URL=https://<account>.r2.cloudflarestorage.com
BUCKET_NAME=mineru-outputs
BUCKET_ACCESS_KEY_ID=<key>
BUCKET_SECRET_ACCESS_KEY=<secret>

Cloudflare R2 is a good pairing here because its egress is free, so downloading your own results costs nothing. Any S3-compatible store works (R2, Backblaze B2, MinIO, AWS S3). To get started you’ll need a RunPod account; sign up here (full disclosure: that’s a referral link). Add a few dollars of credit and it covers thousands of cold starts plus millions of warm pages.

How do you migrate your code off the MinerU API?

Section titled “How do you migrate your code off the MinerU API?”

Install the mineru-client package and swap the official requests calls for MineruApiClient. It mirrors the cloud API’s create_task / get_task surface and returns the same response dicts, so your existing poll loop keeps working. Under the hood it requests archive_format="zip", so full_zip_url comes back as a real .zip like the SaaS does.

Install it (no version pin, so it tracks the repo):

Terminal window
pip install "mineru-client @ git+https://github.com/sergeyshmakov/mineru-runpod"

Here’s the before and after. The official API, polling by hand:

import requests, time
H = {"Authorization": f"Bearer {MINERU_TOKEN}"}
task_id = requests.post(
"https://mineru.net/api/v4/extract/task",
headers=H, json={"url": pdf_url, "model_version": "vlm"},
).json()["data"]["task_id"]
while True:
data = requests.get(
f"https://mineru.net/api/v4/extract/task/{task_id}", headers=H
).json()["data"]
if data["state"] in ("done", "failed"):
break
time.sleep(2)
zip_url = data["full_zip_url"] # then download + unzip yourself

Self-hosted, against your own endpoint:

from mineru_client import MineruApiClient
client = MineruApiClient(endpoint_id="<your-endpoint-id>", api_key="<runpod-key>")
task_id = client.create_task(pdf_url, model_version="vlm")["data"]["task_id"]
done = client.wait_for_task(task_id) # polls to a terminal state
client.download_results(done, "./out") # full_zip_url is a real .zip; unpacked for you

Same lifecycle, same {"code": 0, "data": {...}} response shape. The parameter names map across cleanly too: model_version to the worker’s backend, language to lang, enable_formula / enable_table straight through. The full field-by-field mapping is in Migrate from the MinerU API, and the Clients page covers the native client if you’d rather use the worker’s own (richer) request shape once you’ve moved.

One auth note: the self-hosted endpoint authenticates with your RunPod API key, also via Authorization: Bearer, so even that part of your code barely changes.

What doesn’t carry over from the cloud API?

Section titled “What doesn’t carry over from the cloud API?”

Most of it carries; four things don’t. The compat client rejects callback (a RunPod webhook delivers a different, unsigned payload than MinerU’s signed {checksum, content} callback, so it raises instead of misleading you), and it doesn’t support extra_formats (docx/html/latex), the MinerU-HTML model, or multi-range page_ranges like "2,4-6". There’s no batch endpoint (RunPod’s queue already parallelizes individual jobs across workers, so one isn’t needed). And full_zip_url requires the BUCKET_* setup above.

The full list, so there are no surprises:

Cloud API feature Self-hosted status
create_task / get_task, state machine, full_zip_url Supported (with object storage configured)
model_version: pipeline / vlm Supported (maps to the worker backends)
model_version: MinerU-HTML Not supported, raises
extra_formats (docx / html / latex) Not produced by this worker, raises
page_ranges multi-range ("2,4-6") One contiguous range per job, raises otherwise
callback + seed Rejected (poll with get_task / wait_for_task instead)
Batch (/extract/task/batch) Not offered; submit tasks individually and raise workers_max — RunPod’s queue parallelizes them across workers

Where it falls down: if your pipeline leans on webhook callbacks or exports to DOCX/LaTeX, the compat client isn’t a clean swap today. There’s no batch endpoint either, but you rarely need one: submit tasks individually and let RunPod fan them across workers_max workers (the queue parallelizes them). For everything else, the create / poll / download path behaves like the SaaS.

One more practical detail. The compat client is URL-only, mirroring the cloud API’s POST /extract/task, which doesn’t accept file uploads. For small local files you don’t need to host anything: use the native MineruClient with file_b64 instead, which sends the bytes inline (fine under RunPod’s ~20 MB request cap). I parsed a 522 KB scanned Russian invoice that way and got clean Cyrillic Markdown back, no bucket round-trip involved.

The official MinerU cloud API has a free daily quota of 1,000 high-priority pages, after which jobs run at lower priority. There’s no per-call charge inside the quota. Self-hosting removes the quota entirely and replaces it with per-second GPU billing (around $0.001 per page warm).

Each file is capped at 200 MB, with a daily quota of 1,000 high-priority pages plus rate limits of 50 files/minute and 5,000 files/day (see the published limits). Self-hosting on RunPod removes the daily quota; the per-file practical limit becomes your GPU’s memory and your endpoint’s job timeout rather than a fixed page count.

Yes. MinerU is Apache-2.0 open source and runs anywhere with a CUDA GPU. RunPod Serverless is the path this template targets because it scales to zero, so a bursty workload doesn’t pay for an idle GPU. On a fixed VPS or your own hardware you’d run MinerU directly and skip the serverless wrapper.

Does the self-hosted output match the cloud API’s full_zip_url?

Section titled “Does the self-hosted output match the cloud API’s full_zip_url?”

Yes, when the endpoint has object storage configured. The compat client requests archive_format="zip", so the worker uploads a .zip to your bucket and returns a presigned full_zip_url, the same container and field the SaaS returns. download_results fetches and unpacks it for you, and autodetects .tar.gz too if you change the format.

Is self-hosting actually cheaper than the MinerU API?

Section titled “Is self-hosting actually cheaper than the MinerU API?”

At steady volume, usually yes, because you stop being throttled by the daily quota and pay only for GPU seconds used. The crossover depends on cold-start amortization: dense traffic lands near $0.001/page, sparse one-doc-per-cold-start traffic closer to $0.005–$0.01. Below a few hundred pages a month, the cloud API’s free tier is the cheaper and simpler option.

Do my documents stay private when self-hosting?

Section titled “Do my documents stay private when self-hosting?”

Yes. The worker fetches each file from a URL you control or from bytes you send inline, parses on your own RunPod GPU, and writes output to your own S3 bucket. No document or result passes through a third-party parsing service, which is the usual blocker in a data-residency or compliance review.


Self-hosting MinerU isn’t about beating the cloud API on accuracy. The accuracy is identical: it’s the same model. It’s about removing the quota ceiling, keeping documents in your stack, and paying per-second instead of per-tier. If that’s the wall you’ve hit, fork the template, or deploy it from the RunPod Hub and grab a RunPod account to run the first parse.