FAQ
Common questions developers ask when integrating the BatchRouter API. Each answer links to the guide or reference page with the full detail. For the complete endpoint list, see the interactive API reference.
How is BatchRouter different from calling a provider directly?
Section titled “How is BatchRouter different from calling a provider directly?”When you call a provider’s batch API directly, you pick the provider, the model, and the SLA window yourself — and you re-implement that integration for every provider you want to use. BatchRouter is a routing layer in front of many providers: you submit a batch once, BatchRouter quotes the work and routes it to the cheapest eligible provider lane that satisfies your model, deadline, capacity, and privacy requirements, then tracks execution, delivery, and billing through one API and one credit balance.
Concretely, BatchRouter adds:
- One API and one key across all providers (no per-provider integration).
- Quote-first pricing — you see the price before you commit (see below).
- SLA-aware lanes —
standard(24h),flex, orpriorityvia thesla_tierfield. - Routing receipts — every quote explains why a lane was chosen, and rejected lanes carry a reason, so routing is auditable.
- Privacy controls —
privacy_tier(standard·confidential·restricted) androuting_modeconstrain which providers are eligible.
See Core concepts for the routing model and Routing modes for how lanes are selected.
Is BatchRouter OpenAI-Batch compatible?
Section titled “Is BatchRouter OpenAI-Batch compatible?”Yes. BatchRouter exposes an OpenAI-Batch-shaped surface under /v1/openai/v1/* so you can point an
existing OpenAI Batch client at BatchRouter by changing the base URL and key. The compatible
endpoints are:
POST /v1/openai/v1/filesandGET /v1/openai/v1/files/{fileId}(+/content) — upload and read JSONL files.POST /v1/openai/v1/batches,GET /v1/openai/v1/batches,GET /v1/openai/v1/batches/{batchId}— create, list, and poll batches.POST /v1/openai/v1/batches/{batchId}/cancel— cancel.
# Point an OpenAI Batch client at BatchRoutercurl https://api.batchrouter.com/v1/openai/v1/batches \ -H "Authorization: Bearer $BATCHROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input_file_id": "file_...", "endpoint": "/v1/responses", "completion_window": "24h" }'import OpenAI from 'openai';
const client = new OpenAI({ apiKey: process.env.BATCHROUTER_API_KEY, baseURL: 'https://api.batchrouter.com/v1/openai/v1',});
const batch = await client.batches.create({ input_file_id: 'file_...', endpoint: '/v1/responses', completion_window: '24h',});from openai import OpenAI
client = OpenAI( api_key=os.environ["BATCHROUTER_API_KEY"], base_url="https://api.batchrouter.com/v1/openai/v1",)
batch = client.batches.create( input_file_id="file_...", endpoint="/v1/responses", completion_window="24h",)How is pricing calculated?
Section titled “How is pricing calculated?”Pricing is quote-first and settled from usage:
- Quote (free). Call
POST /v1/quotes/model(a direct route — one or several acceptable models) orPOST /v1/quotes/workflow(a curated workflow product). The quote is free, returns aquote_id, an expiration, and the selected lane(s) and price. Quotes are versioned snapshots, so the price you see is the price you commit to. - Reserve at create. When you create the batch with
POST /v1/batches(passing thequote_id), BatchRouter reserves credits against your prepaid balance for the quoted amount. - Settle from actual usage. After execution, the batch is settled from real provider usage and
the reservation is reconciled. Per-batch costs are itemized on the billing receipt at
GET /v1/batches/{batchId}/billing-receipt, and platform fees are described byGET /v1/pricing/fees.
See Quotes and pricing and Credits and billing.
What happens if I don’t have enough credits (402)?
Section titled “What happens if I don’t have enough credits (402)?”Credits are prepaid. If your balance can’t cover a batch’s reservation, the create request is
rejected with 402 Payment Required rather than running and overdrawing your account — the
funding gate is checked before any work is dispatched. POST /v1/quotes/model,
POST /v1/quotes/workflow, and POST /v1/batches can all return 402.
To recover, top up your balance in the dashboard at batchrouter.com/app/billing, then retry the create. There is no public billing-checkout endpoint — credits are purchased in the dashboard.
How long are my results kept?
Section titled “How long are my results kept?”Results retention is controlled per-org by GET|PUT /v1/retention-settings. The default is the free
grace_only tier — results are downloadable for a short grace window after completion, then garbage
collected. The available retention_tier values are:
grace_only— free; download within the grace window.extended_fixed— keep results for a fixedextended_dayswindow. Metered (a storage charge against your prepaid balance) once storage fees are enabled.until_deleted— keep until you delete. Also metered when storage fees are enabled.
Set never_charge: true to guarantee zero storage spend (this forces the free grace_only tier).
Once retention reclaims the bytes, a result file persists as expired (distinct from a 404 that
never existed) and its content endpoint returns 410.
Download results while they’re available via GET /v1/batches/{batchId}/results (paginated) or a
signed URL from GET /v1/batches/{batchId}/artifact-url. See
Retrieving results and Retention.
Can I cancel a batch?
Section titled “Can I cancel a batch?”Yes — POST /v1/batches/{batchId}/cancel requests cancellation. Whether a batch can still be
cancelled depends on its current status: a batch already in a terminal state (completed, failed,
cancelled, expired) cannot be cancelled. See the
API reference for the exact status transitions.
Can I retry a batch?
Section titled “Can I retry a batch?”Yes. There are two distinct operations:
POST /v1/batches/{batchId}/retry— re-queues a failed or expired batch for another dispatch attempt. Only available for batches infailedorexpiredstatus. (Beta — behaviour may change.)POST /v1/batches/{batchId}/redeliver— re-enqueue delivery to your configured customer bucket when a delivery failed. This is guarded against double-delivery (it returns409if the batch is alreadydeliveredordelivering) and requires a configured delivery target.
See the Batches reference for the full lifecycle.
How do I get notified when a batch finishes?
Section titled “How do I get notified when a batch finishes?”Three options, in order of how hands-off they are:
- Poll. Call
GET /v1/batches/{batchId}and watchstatusmovepending → queued → routing → dispatched → processing → completing → completed(terminal:failed·cancelled·expired). Simplest to integrate; good for scripts and short jobs. - Webhook. Set a per-batch
{ url, secret }at create time, or an org-wide default withPUT /v1/auth/account/delivery-webhook. BatchRouter signs every webhook with anX-BatchRouter-Signatureheader (HMAC-SHA256) — verify it before trusting the payload. Inspect delivery attempts withGET /v1/batches/{batchId}/webhooks. - Delivery target. Configure a customer-owned destination (e.g. your own bucket) with
GET|POST /v1/delivery-targets, test it withPOST /v1/delivery-targets/{id}/test, and BatchRouter delivers results there directly.
See Webhooks and delivery for signature verification and payloads.
Which models and providers are available?
Section titled “Which models and providers are available?”The catalog is live — query it rather than hardcoding model names:
- Models —
GET /v1/catalog/models(list) andGET /v1/catalog/models/{slug}(detail). - Workflow products —
GET /v1/catalog/workflow-productsand.../{slug}. - Providers —
GET /v1/providers(list) andGET /v1/providers/{slug}(detail).
curl https://api.batchrouter.com/v1/catalog/models \ -H "Authorization: Bearer $BATCHROUTER_API_KEY"const res = await fetch('https://api.batchrouter.com/v1/catalog/models', { headers: { Authorization: `Bearer ${process.env.BATCHROUTER_API_KEY}` },});const { data } = await res.json();import os, requests
res = requests.get( "https://api.batchrouter.com/v1/catalog/models", headers={"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}"},)models = res.json()["data"]