Quickstart
BatchRouter is an OpenAI-Batch-compatible API that routes batch AI workloads across many providers, picking the cheapest lane that meets your SLA, privacy, and tool requirements — with a routing receipt for every decision. This page takes you from zero to your first completed batch in five steps.
The flow is always: quote (free) → create → poll → results. Quotes cost nothing; credits are reserved only when you create a batch and settled from actual token usage.
Before you start
Section titled “Before you start”You’ll need an API key and a small credit balance. The base URL is https://api.batchrouter.com (use https://test.api.batchrouter.com for testing), and every endpoint lives under /v1. Authenticate by sending your key as a bearer token:
export BATCHROUTER_API_KEY="br_live_…"curl https://api.batchrouter.com/v1/auth/account \ -H "Authorization: Bearer $BATCHROUTER_API_KEY"That call returns your account, including credit_balance. If it’s zero, you’ll get a 402 on create — add credits from the billing dashboard.
Walkthrough
Section titled “Walkthrough”-
Create an API key.
Create a key in the dashboard or, for an agent-first flow with no email, call
POST /v1/auth/agent-registerto get anorg_idandapi_keyin one shot.See Authentication for both paths and key rotation.
-
Get a free quote.
Send your items to
POST /v1/quotes/model. BatchRouter prices the work, picks a lane, and returns aquote_id(qlock_…) plus a per-lane breakdown. Quoting is free and reserves nothing.Terminal window curl https://api.batchrouter.com/v1/quotes/model \-H "Authorization: Bearer $BATCHROUTER_API_KEY" \-H "Content-Type: application/json" \-d '{"routing_mode": "cheapest","items": [{"customer_item_id": "item-1","operation": "responses","model": "gpt-4o-mini","input": {"messages": [{ "role": "user", "content": "Summarize: BatchRouter routes batch-AI workloads across providers." }]}}]}'const res = await fetch("https://api.batchrouter.com/v1/quotes/model", {method: "POST",headers: {Authorization: `Bearer ${process.env.BATCHROUTER_API_KEY}`,"Content-Type": "application/json",},body: JSON.stringify({routing_mode: "cheapest",items: [{customer_item_id: "item-1",operation: "responses",model: "gpt-4o-mini",input: {messages: [{ role: "user", content: "Summarize: BatchRouter routes batch-AI workloads across providers." },],},},],}),});const quote = await res.json();console.log(quote.quote_id, quote.pricing_estimate.total);import os, requestsres = requests.post("https://api.batchrouter.com/v1/quotes/model",headers={"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}"},json={"routing_mode": "cheapest","items": [{"customer_item_id": "item-1","operation": "responses","model": "gpt-4o-mini","input": {"messages": [{"role": "user", "content": "Summarize: BatchRouter routes batch-AI workloads across providers."}]},}],},)quote = res.json()print(quote["quote_id"], quote["pricing_estimate"]["total"])The response includes
quote_id, apricing_estimate(provider_subtotal,batchrouter_fee,customer_discount,total), the selectedquote_lanes, and a plain-languagecustomer_explanation. Unselected lanes come back with rejection reasons such ascapacity_full,context_window_exceeded, ortool_support. -
Submit your first batch.
Pass the
quote_idtoPOST /v1/batchesand include anIdempotency-Keyheader (8–128 chars) so retries never create a duplicate. A202means it’s accepted and queued.Terminal window curl https://api.batchrouter.com/v1/batches \-H "Authorization: Bearer $BATCHROUTER_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: quickstart-batch-001" \-d '{"quote_id": "qlock_…","sla_tier": "standard","items": [{"customer_item_id": "item-1","operation": "responses","model": "gpt-4o-mini","input": {"messages": [{ "role": "user", "content": "Summarize: BatchRouter routes batch-AI workloads across providers." }]}}]}'const res = await fetch("https://api.batchrouter.com/v1/batches", {method: "POST",headers: {Authorization: `Bearer ${process.env.BATCHROUTER_API_KEY}`,"Content-Type": "application/json","Idempotency-Key": "quickstart-batch-001",},body: JSON.stringify({quote_id: "qlock_…",sla_tier: "standard",items: [{customer_item_id: "item-1",operation: "responses",model: "gpt-4o-mini",input: {messages: [{ role: "user", content: "Summarize: BatchRouter routes batch-AI workloads across providers." },],},},],}),});const { batch } = await res.json();console.log(batch.id, batch.status); // bat_… pendingimport os, requestsres = requests.post("https://api.batchrouter.com/v1/batches",headers={"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}","Idempotency-Key": "quickstart-batch-001",},json={"quote_id": "qlock_…","sla_tier": "standard","items": [{"customer_item_id": "item-1","operation": "responses","model": "gpt-4o-mini","input": {"messages": [{"role": "user", "content": "Summarize: BatchRouter routes batch-AI workloads across providers."}]},}],},)batch = res.json()["batch"]print(batch["id"], batch["status"]) # bat_… pendingThe response is
{ "batch": { "id": "bat_…", "status": "pending", "item_count": 1 } }. -
Poll for status.
Fetch the batch until it reaches a terminal state. Add
?include_billing_receipt=trueonce it’s done to see the final cost.Terminal window curl "https://api.batchrouter.com/v1/batches/bat_…" \-H "Authorization: Bearer $BATCHROUTER_API_KEY"const res = await fetch(`https://api.batchrouter.com/v1/batches/${batchId}`, {headers: { Authorization: `Bearer ${process.env.BATCHROUTER_API_KEY}` },});const { batch } = await res.json();console.log(batch.status);import os, requestsres = requests.get(f"https://api.batchrouter.com/v1/batches/{batch_id}",headers={"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}"},)print(res.json()["batch"]["status"])Status moves through this lifecycle:
pending→queued→routing→dispatched→processing→completing→completedTerminal states are
completed,failed,cancelled, andexpired. Poll on an interval (e.g. every 30–60s); to avoid polling entirely, attach a webhook at create time. -
Retrieve your results.
Once
statusiscompleted, read the output.GET /v1/batches/{batchId}/resultsreturns one entry per item, paginated.Terminal window curl "https://api.batchrouter.com/v1/batches/bat_…/results?limit=100" \-H "Authorization: Bearer $BATCHROUTER_API_KEY"const res = await fetch(`https://api.batchrouter.com/v1/batches/${batchId}/results?limit=100`,{ headers: { Authorization: `Bearer ${process.env.BATCHROUTER_API_KEY}` } },);const { results, next_cursor } = await res.json();for (const r of results) {console.log(r.customer_item_id, r.status, r.output);}import os, requestsres = requests.get(f"https://api.batchrouter.com/v1/batches/{batch_id}/results",headers={"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}"},params={"limit": 100},)payload = res.json()for r in payload["results"]:print(r["customer_item_id"], r["status"], r["output"])Each result has
customer_item_id,status(completedorfailed),output, anderror. Page with?cursor=<next_cursor>(limit 1–1000, default 100). For large batches, preferGET /v1/batches/{batchId}/artifact-url, which returns a short-lived signedurlto the full JSONL artifact instead of paginating.
That’s a complete batch, end to end. From here, scale up with file uploads, point an existing OpenAI Batch client at BatchRouter, or switch from polling to webhooks.