Submit a batch
Creating a batch submits your work for asynchronous processing and reserves credits against a quote. You send POST /v1/batches with either inline items or a reference to an uploaded file, accept a quote_id, and pass an Idempotency-Key so retries are safe.
This is the deep version of step 3 of the quickstart. It assumes you already have an API key (see Authentication) and a quote (see Quotes and pricing).
Before you start
Section titled “Before you start”You need two things in hand:
- An API key, sent as
Authorization: Bearer br_live_…. Keep it server-side. - A
quote_id(qlock_…) fromPOST /v1/quotes/model. The quote is free, pins your price estimate, and is required to reserve credits before dispatch.
Choose your input: inline items or a file
Section titled “Choose your input: inline items or a file”A batch’s work comes from exactly one of two sources — they are mutually exclusive:
items— an inline array of batch items. Best for small-to-medium batches you assemble in memory.input_file_id— afile_…id from a JSONL file you uploaded withPOST /v1/files. Best for large batches or binary inputs. See JSONL format for the line shape and the file-based example below.
Send one or the other. Sending both — or neither — fails preflight with 400 and error.details.preflight.
The canonical item used across these docs:
{"customer_item_id":"item-1","operation":"responses","model":"gpt-4o-mini","input":{"messages":[{"role":"user","content":"Summarize: BatchRouter routes batch-AI workloads across providers."}]}}The Idempotency-Key header
Section titled “The Idempotency-Key header”POST /v1/batches requires an Idempotency-Key header — a unique string of 8–128 characters per logical submission. If a network error leaves you unsure whether the batch was created, retry with the same key: BatchRouter returns the original batch instead of creating a duplicate. Use a fresh key for each new batch (a UUID works well).
Request options
Section titled “Request options”Beyond the input and quote_id, the body accepts these optional fields:
| Field | Values | Default | What it does |
|---|---|---|---|
sla_tier | standard · flex · priority | standard | standard = 24h SLA; flex = up to 48h, cheaper; priority = expedited. |
routing_mode | cheapest · sla_aware · public_only · edge_only · hybrid · privacy_constrained | cheapest | How BatchRouter selects a lane across providers. |
privacy_tier | standard · confidential · restricted | standard | confidential routes to data-retention opt-out providers; restricted routes only to private edge nodes. |
webhook | { url, secret } | — | Per-batch completion callback; secret is 8+ chars and signs X-BatchRouter-Signature. |
metadata | object | — | Arbitrary key/value pairs echoed back on the batch. |
Create a batch (inline items)
Section titled “Create a batch (inline items)”-
Assemble your items and accept the quote.
-
Send the request with an
Idempotency-Key.Terminal window curl -X POST https://api.batchrouter.com/v1/batches \-H "Authorization: Bearer $BATCHROUTER_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: $(uuidgen)" \-d '{"quote_id": "qlock_abc123","sla_tier": "standard","routing_mode": "cheapest","privacy_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." }]}}],"webhook": {"url": "https://example.com/hooks/batchrouter","secret": "a-strong-shared-secret"},"metadata": { "project": "docs-demo" }}'import { randomUUID } from "node:crypto";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": randomUUID(),},body: JSON.stringify({quote_id: "qlock_abc123",sla_tier: "standard",routing_mode: "cheapest",privacy_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." },],},},],webhook: {url: "https://example.com/hooks/batchrouter",secret: "a-strong-shared-secret",},metadata: { project: "docs-demo" },}),});const { batch } = await res.json();console.log(batch.id, batch.status); // bat_… pendingimport osimport uuidimport requestsres = requests.post("https://api.batchrouter.com/v1/batches",headers={"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}","Content-Type": "application/json","Idempotency-Key": str(uuid.uuid4()),},json={"quote_id": "qlock_abc123","sla_tier": "standard","routing_mode": "cheapest","privacy_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."}]},}],"webhook": {"url": "https://example.com/hooks/batchrouter","secret": "a-strong-shared-secret",},"metadata": {"project": "docs-demo"},},)batch = res.json()["batch"]print(batch["id"], batch["status"]) # bat_… pending -
Store
batch.id(bat_…) and poll for completion. See Results.
Create a batch (file-based)
Section titled “Create a batch (file-based)”For large or binary inputs, upload a JSONL file first, then reference its file_id. Use input_file_id instead of items.
curl -X POST https://api.batchrouter.com/v1/batches \ -H "Authorization: Bearer $BATCHROUTER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "quote_id": "qlock_abc123", "input_file_id": "file_xyz789", "sla_tier": "flex", "routing_mode": "sla_aware" }'import { randomUUID } from "node:crypto";
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": randomUUID(), }, body: JSON.stringify({ quote_id: "qlock_abc123", input_file_id: "file_xyz789", sla_tier: "flex", routing_mode: "sla_aware", }),});
const { batch } = await res.json();console.log(batch.id, batch.status);import osimport uuidimport requests
res = requests.post( "https://api.batchrouter.com/v1/batches", headers={ "Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}", "Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4()), }, json={ "quote_id": "qlock_abc123", "input_file_id": "file_xyz789", "sla_tier": "flex", "routing_mode": "sla_aware", },)
batch = res.json()["batch"]print(batch["id"], batch["status"])The 202 response
Section titled “The 202 response”A successful create returns 202 Accepted — the batch is queued for routing, not yet processed. The body wraps the batch summary:
{ "batch": { "id": "bat_9f3c2a1b", "status": "pending", "item_count": 1, "created_at": "2026-06-18T10:00:00Z", "sla_deadline": "2026-06-19T10:00:00Z" }}| Field | Meaning |
|---|---|
batch.id | The bat_… identifier you poll and fetch results with. |
batch.status | Starts at pending, then advances queued → routing → dispatched → processing → completing → completed. |
batch.item_count | Number of items accepted into the batch. |
batch.sla_deadline | The SLA deadline by which the batch is expected to complete, derived from the chosen sla_tier. |
Replaying the same Idempotency-Key returns this same response rather than creating a second batch.
Errors
Section titled “Errors”| Status | Cause | Fix |
|---|---|---|
400 | Preflight failed (bad JSONL, both/neither input, context window exceeded, unsafe webhook URL). | Inspect error.details.preflight; see JSONL format. |
401 | Missing or invalid key. | Check your Authorization header — see Authentication. |
402 | Insufficient credits. | Check GET /v1/auth/account; top up credits in the dashboard at https://batchrouter.com/app/billing. |
409 | Conflict (e.g. reused key on a different payload). | Use a fresh Idempotency-Key for a new batch. |