OpenAI compatibility
BatchRouter speaks the OpenAI Batch contract: the same JSONL request shape, file uploads, and batch lifecycle you already use. Point your batch workflow at BatchRouter with a br_live_ key and BatchRouter routes each job across many providers (with SLA-aware lanes and routing receipts) instead of pinning you to one.
This guide shows what carries over unchanged, what to swap, and the BatchRouter-only extras you can opt into.
What’s compatible
Section titled “What’s compatible”If you already run OpenAI batch jobs, these map directly onto BatchRouter:
- The JSONL / item input shape. Each item’s
inputaccepts the OpenAI-Batch-compatible body —messages,input, or an embeddingsinputfield. You can keep your existing line shape. - Files + batches. Upload large JSONL inputs with
POST /v1/files, then reference the returnedfile_idasinput_file_idwhen you create the batch — the same two-step flow as the OpenAI Batch API. - The lifecycle. Create a batch, poll it to a terminal status, then fetch results — conceptually identical to create → poll → retrieve.
Here is the canonical BatchRouter item. It is the same JSONL you would build for OpenAI batch, with a customer_item_id so you can join results back to your rows (model is illustrative — list real models via GET /v1/catalog/models):
{"customer_item_id":"item-1","operation":"responses","model":"gpt-4o-mini","input":{"messages":[{"role":"user","content":"Summarize: BatchRouter routes batch-AI workloads across providers."}]}}Point your client at BatchRouter
Section titled “Point your client at BatchRouter”There are two changes: the base URL and the API key.
-
Create a BatchRouter API key. Generate a
br_live_…key in the dashboard (or withPOST /v1/auth/account/api-keys). Keys are shown once — store it server-side asBATCHROUTER_API_KEY. See Authentication. -
Swap the base URL to
https://api.batchrouter.com(orhttps://test.api.batchrouter.comfor staging). All public endpoints live under/v1. -
Swap the key for your
br_live_…key, sent asAuthorization: Bearer br_live_….
If your client points at the OpenAI Batch base URL today, the change is the base URL and the credential — your JSONL stays the same.
from openai import OpenAI
# Before — OpenAI# client = OpenAI(api_key="sk-...")
# After — BatchRouterclient = OpenAI( base_url="https://api.batchrouter.com/v1", api_key="br_live_your_key_here", # or os.environ["BATCHROUTER_API_KEY"])import OpenAI from 'openai';
// Before — OpenAI// const client = new OpenAI({ apiKey: 'sk-...' });
// After — BatchRouterconst client = new OpenAI({ baseURL: 'https://api.batchrouter.com/v1', apiKey: process.env.BATCHROUTER_API_KEY, // br_live_...});# Before — OpenAI# curl https://api.openai.com/v1/... -H "Authorization: Bearer sk-..."
# After — BatchRoutercurl https://api.batchrouter.com/v1/... \ -H "Authorization: Bearer $BATCHROUTER_API_KEY"Upload JSONL and create a batch
Section titled “Upload JSONL and create a batch”The file-then-batch flow mirrors OpenAI Batch. Upload your JSONL, then create a batch from the file.
-
Upload the input file with
POST /v1/files. Send the raw body withContent-Length,Content-Type,X-BatchRouter-Filename, andX-BatchRouter-Purpose: model_input. You get back afile_id. -
Create the batch with
POST /v1/batches, passinginput_file_idand anIdempotency-Keyheader (8–128 chars) so retries are safe. You get back 202 with the batchid(bat_…).
# 1. Upload the JSONL input fileFILE_ID=$(curl -s https://api.batchrouter.com/v1/files \ -H "Authorization: Bearer $BATCHROUTER_API_KEY" \ -H "Content-Type: application/jsonl" \ -H "Content-Length: $(wc -c < input.jsonl)" \ -H "X-BatchRouter-Filename: input.jsonl" \ -H "X-BatchRouter-Purpose: model_input" \ --data-binary @input.jsonl | jq -r '.file_id')
# 2. Create the batch from the uploaded filecurl -s https://api.batchrouter.com/v1/batches \ -H "Authorization: Bearer $BATCHROUTER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: import-2026-06-18-001" \ -d "{\"input_file_id\":\"$FILE_ID\",\"sla_tier\":\"standard\"}"import { readFile } from 'node:fs/promises';
const BASE = 'https://api.batchrouter.com/v1';const auth = { Authorization: `Bearer ${process.env.BATCHROUTER_API_KEY}` };
// 1. Upload the JSONL input fileconst body = await readFile('input.jsonl');const upload = await fetch(`${BASE}/files`, { method: 'POST', headers: { ...auth, 'Content-Type': 'application/jsonl', 'Content-Length': String(body.byteLength), 'X-BatchRouter-Filename': 'input.jsonl', 'X-BatchRouter-Purpose': 'model_input', }, body,});const { file_id } = await upload.json();
// 2. Create the batch from the uploaded fileconst created = await fetch(`${BASE}/batches`, { method: 'POST', headers: { ...auth, 'Content-Type': 'application/json', 'Idempotency-Key': 'import-2026-06-18-001', }, body: JSON.stringify({ input_file_id: file_id, sla_tier: 'standard' }),});const { batch } = await created.json();console.log(batch.id, batch.status); // bat_... pendingimport os, requests
BASE = "https://api.batchrouter.com/v1"auth = {"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}"}
# 1. Upload the JSONL input filewith open("input.jsonl", "rb") as f: body = f.read()
upload = requests.post( f"{BASE}/files", headers={ **auth, "Content-Type": "application/jsonl", "Content-Length": str(len(body)), "X-BatchRouter-Filename": "input.jsonl", "X-BatchRouter-Purpose": "model_input", }, data=body,)file_id = upload.json()["file_id"]
# 2. Create the batch from the uploaded filecreated = requests.post( f"{BASE}/batches", headers={**auth, "Idempotency-Key": "import-2026-06-18-001"}, json={"input_file_id": file_id, "sla_tier": "standard"},)batch = created.json()["batch"]print(batch["id"], batch["status"]) # bat_... pendingYou can also submit items inline (items: [...]) instead of uploading a file — see Submit a batch.
How BatchRouter differs
Section titled “How BatchRouter differs”BatchRouter accepts the OpenAI input contract but adds a routing layer on top. The differences are additive — ignore them and you get sensible defaults; opt in for more control.
- It routes across providers, not just one. BatchRouter picks a lane per the routing policy, applies the GTM discount on native OpenAI/Anthropic lanes, and writes a routing/rejection receipt. Unselected lanes come back with reasons (
capacity_full,context_window_exceeded,privacy_tier_mismatch, …). - Quotes are free and first. Call
POST /v1/quotes/modelto get a binding price (quote_id,qlock_…) and lane breakdown before you commit. Pass thequote_idon create to lock that estimate. OpenAI has no equivalent step. - SLA tiers.
sla_tierisstandard(24h, default),flex(up to 48h, cheaper), orpriority. Choose the speed/cost trade-off per batch. - Routing modes.
routing_modeischeapest(default),sla_aware,public_only,edge_only,hybrid, orprivacy_constrained— control which lanes are eligible. - Privacy tiers + per-batch webhooks. Set
privacy_tier(standard/confidential/restricted) and a signedwebhookdirectly on create. Webhook events carry anX-BatchRouter-Signature(HMAC-SHA256) header. - One unified result surface. However a job is routed, results come back in a single BatchRouter schema — fetch per-item output (with
customer_item_id,status,output,error) fromGET /v1/batches/{batchId}/results, or get a signed JSONL artifact URL fromGET /v1/batches/{batchId}/artifact-urlfor large batches. You poll the same way regardless of which provider lane ran each item.
To stay maximally OpenAI-like, omit the extras: BatchRouter defaults to sla_tier: "standard", routing_mode: "cheapest", and privacy_tier: "standard", and you can poll for results without configuring a webhook.
{"input_file_id":"file_abc123","sla_tier":"flex","routing_mode":"sla_aware","quote_id":"qlock_..."}