Translate PDFs with BabelDOC on a custom OpenAI base URL.
Updated 2026-07-16
BabelDOC's translator is OpenAI-compatible by design: three flags (--openai, --openai-base-url, --openai-api-key) plus --openai-model select the endpoint and the model. Point the base URL at https://api.apisrouter.com/v1 and translate documents with Claude, DeepSeek, GLM, or Gemini through one key.
Quick answer: three flags route every translation call.
BabelDOC's command line takes the endpoint directly: --openai enables the LLM translator, --openai-base-url sets where requests go, --openai-api-key authenticates, and --openai-model picks the model id. The README's own examples show exactly this flag set, and its translation-service note states that only OpenAI-compatible LLMs are supported, which makes a multi-vendor OpenAI-compatible gateway the natural fit rather than a workaround. Because the model id is forwarded as a plain string, anything the endpoint serves works: the upstream docs themselves recommend OpenAI-compatible-friendly models from the GLM and DeepSeek families, and through APIsRouter those sit next to Claude and Gemini ids behind the same base URL.
babeldoc --files paper.pdf \
--lang-in en --lang-out zh \
--openai \
--openai-model "deepseek-v4-flash" \
--openai-base-url "https://api.apisrouter.com/v1" \
--openai-api-key "$APISROUTER_API_KEY"How BabelDOC turns a PDF into model calls.
BabelDOC (funstory-ai on GitHub, roughly 9K stars, from the team behind Immersive Translate) is a PDF document translator that preserves layout: it parses the document structure, protects formulas and figures, finds paragraphs, translates them with an LLM, and rebuilds the PDF as a translated mono version and a side-by-side dual version. It ships as a CLI and a Python API, and it is the self-hosted counterpart to the hosted BabelDOC service. The translation phase is where the endpoint matters. A document becomes many paragraph-sized chat-completions requests, throttled by the --qps flag (default 4 queries per second) and processed by a worker pool (pool-max-workers, defaulting to the QPS value). That shape has two consequences. First, translation is a volume workload: a long PDF is hundreds of small calls, so per-token price compounds fast. Second, unlike retrieval workloads where the model mostly reads, translation writes roughly as much as it reads, so output-token price matters as much as input price when you compare ids. BabelDOC also caches translations, so re-running a document reuses previous results unless you pass --ignore-cache. Glossary CSVs (--glossary-files) pin terminology across the run, and --max-pages-per-part splits very large documents into parts that are translated and merged automatically.
Full setup: CLI flags or the TOML config file.
For repeated use, the same settings live in a TOML file passed with --config. The [babeldoc] table accepts the identical keys in kebab-case: openai, openai-model, openai-base-url, openai-api-key, plus the throughput and output options. This keeps the key out of your shell history and makes a translation profile reproducible across documents. The config below is a practical volume profile: a fast id for the bulk of documents, QPS raised to match a pooled gateway, and both output modes kept. Swap openai-model to a stronger id for documents where nuance matters more than throughput.
[babeldoc]
lang-in = "en-US"
lang-out = "zh-CN"
qps = 10
pool-max-workers = 10
# Translation service
openai = true
openai-model = "deepseek-v4-flash"
openai-base-url = "https://api.apisrouter.com/v1"
openai-api-key = "sk-YOUR-APISROUTER-KEY"
# Output control
no-dual = false
no-mono = false
watermark-output-mode = "no_watermark"Choosing a translation model.
The comparison workflow is concrete: translate the same ten pages with two ids (the cache keyed per run keeps them separate), read the duals side by side, and check the per-key usage log for what each pass cost. Most teams land on a fast default plus a premium profile for documents that deserve it, both as TOML files.
- Volume documents (manuals, papers read once) fit deepseek-v4-flash: translation quality holds for technical prose and the per-page cost is close to negligible.
- Chinese-target translation is a home game for glm-5.2 and the DeepSeek family; the upstream docs themselves point at GLM and DeepSeek models as well-behaved OpenAI-compatible choices.
- Nuance-critical documents (contracts, published translations) justify claude-sonnet-4-6 or claude-haiku-4-5-20251001, which track terminology and register more faithfully across long documents.
- Output tokens matter here. Translation writes as much as it reads, so compare ids on the output price column too, not just input.
- Pair glossaries with fast ids. A glossary CSV pins the terminology that fast models occasionally drift on, which closes much of the quality gap on technical text.
Pay-as-you-go · transparent per-model pricing
Selected models are priced below official list prices. Exact input, output, cache, and per-request prices are shown for each model.
| Model | Official Price | Our Price |
|---|---|---|
| DeepSeek V4 Flash | $0.14 / $0.28 per M | $0.13 / $0.25 per M |
| GLM-5.2 | $1.14 / $4.00 per M | $1.03 / $3.60 per M |
| Gemini 3.5 Flash | $1.50 / $9.00 per M | $1.20 / $7.20 per M |
| Claude Haiku 4.5 20251001 | $1.00 / $5.00 per M | $0.80 / $4.00 per M |
| Claude Sonnet 4.6 | $3.00 / $15.00 per M | $2.40 / $12.00 per M |
Failure modes and throughput tuning.
QPS is the knob that interacts with the gateway. The default of 4 queries per second is conservative; pooled upstream capacity usually sustains more, and raising --qps (with pool-max-workers following it) is how a 300-page document stops taking all afternoon. Ramp it while watching for 429 responses rather than jumping to a large number cold, because a rate-limited paragraph retries and slows the whole run. The flags only apply when --openai is set. Passing a base URL without --openai leaves the translator disabled, which surfaces as a run that parses the PDF but never translates. Model ids are exact strings against the endpoint's /v1/models listing; a typo fails the first paragraph call with model-not-found. A 401 means the key and base URL do not belong together. Layout problems are not endpoint problems. Overlapping text, lost formulas, or broken tables trace to the PDF parsing side (try --enhance-compatibility, --ocr-workaround for scanned documents, or the rich-text toggle), and switching models will not fix them. The reverse also holds: mistranslated terminology is a model or glossary issue, not a parser issue. The cache can mask changes. After switching models, pass --ignore-cache if you want the new id to retranslate content the old id already covered; otherwise cached paragraphs stay as they were.
Who routes BabelDOC through a gateway.
- Researchers translating papers in bulk, where hundreds of small calls per document make volume pricing and per-key usage visibility the whole game.
- Teams standardizing bilingual documentation, running a fast default profile and a premium profile against the same endpoint with different model strings.
- Users in markets where the strongest translation models for their language pair sit with different vendors: GLM, DeepSeek, Claude, and Gemini ids all behind one key.
- Self-hosters replacing the hosted service for confidential documents, keeping parsing local and sending only paragraph text to one auditable endpoint.
- Developers without access to a given vendor's billing. Top-up based access with no card requirement removes the per-provider sign-up dependency.
Verify the endpoint and debug the first document.
List the models your key can address before starting a long run; --openai-model must match a served id exactly. Then translate something tiny (a one-page PDF, or --pages 1 on a bigger one) end to end. A 401 on the first paragraph means the key does not match the base URL. Model-not-found is an id typo. A run that parses but never calls the endpoint is missing --openai. Frequent stalls with retry messages point at QPS set higher than the endpoint sustains; drop it and ramp back up. Once documents flow, the APIsRouter console shows per-request model, token counts, and spend. Translation cost scales with document length in both directions (input and output), and the usage log per key is how you learn your real cost per page for each model rather than estimating it.
curl -s https://api.apisrouter.com/v1/models \
-H "Authorization: Bearer $APISROUTER_API_KEY" | head -50
# then a one-page smoke test
babeldoc --config babeldoc.toml --files sample.pdf --pages 1FAQ
Does BabelDOC support custom OpenAI-compatible endpoints?
Yes, natively. The CLI exposes --openai-base-url and --openai-api-key alongside --openai-model, and the TOML config accepts the same keys. The upstream README states that OpenAI-compatible LLMs are the supported translator type.
Can BabelDOC translate with Claude, GLM, or DeepSeek models?
Yes. The model id is forwarded as a plain string to the endpoint behind --openai-base-url, so any catalog id works. The upstream docs themselves recommend GLM and DeepSeek family models as well-behaved choices.
How many API calls does one PDF cost?
BabelDOC translates paragraph-sized chunks, so a document becomes hundreds of small chat-completions calls throttled by --qps. Both input and output tokens scale with document length; the per-key usage log shows the exact per-document cost.
What QPS should I set against a gateway?
Start near the default of 4 and ramp up while watching for 429 responses; pooled endpoints usually sustain more, and pool-max-workers follows the QPS value unless set separately. A stable higher QPS is the difference between minutes and hours on long documents.
I switched models but the translation did not change. Why?
The translation cache. BabelDOC reuses cached results per document; pass --ignore-cache after changing --openai-model so the new id retranslates previously covered content.
Does the endpoint choice affect layout, formulas, or tables?
No. Parsing, layout analysis, and PDF reconstruction run locally regardless of the endpoint. Layout issues have their own flags (--enhance-compatibility, --ocr-workaround); the base URL only decides which model translates the text.