OpenAI compatibility
BatchRouter exposes an OpenAI-Batch-compatible API under /v1/openai/v1/. If you already have code built against the openai SDK’s Files and Batches resources, you can route those workloads through BatchRouter by changing two things: the base URL and the API key. No request reshaping required.
Behind that familiar surface, BatchRouter is a routing and aggregation layer, not a passthrough proxy to OpenAI. Your batch is quoted, funding-gated, and dispatched across BatchRouter’s provider lanes using the same pipeline as the native /v1/batches surface — and the responses you download are projected back into OpenAI’s shape so your client doesn’t notice the difference.
When to use this surface
Section titled “When to use this surface”Use the compatible surface when you want a drop-in migration: minimal code changes to send existing OpenAI Batch jobs through BatchRouter.
Use the native /v1/batches surface when you want BatchRouter’s full feature set — free pre-submission quotes, sla_tier and routing_mode selection, privacy_tier controls, and routing/billing receipts. The OpenAI surface deliberately keeps the OpenAI request shape, so those BatchRouter-specific knobs aren’t expressed in the request body.
Base URL and authentication
Section titled “Base URL and authentication”Point your OpenAI client’s base_url at:
https://api.batchrouter.com/v1/openai/v1For the test environment, use https://test.api.batchrouter.com/v1/openai/v1.
Authenticate with a BatchRouter API key (prefixed br_live_) as the bearer token — exactly where the OpenAI SDK already puts OPENAI_API_KEY. Create a key in the dashboard (POST /v1/auth/account/api-keys) or via POST /v1/auth/agent-register. Keys are shown once.
from openai import OpenAI
client = OpenAI( base_url="https://api.batchrouter.com/v1/openai/v1", api_key="br_live_...", # your BatchRouter key, not an OpenAI key)import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.batchrouter.com/v1/openai/v1", apiKey: "br_live_...", // your BatchRouter key, not an OpenAI key});The compatible endpoints
Section titled “The compatible endpoints”These are the operations exposed under the OpenAI-compatible surface. They mirror the openai SDK’s client.files.* and client.batches.* methods.
| Method & path | OpenAI SDK equivalent |
|---|---|
POST /v1/openai/v1/files | client.files.create({ file, purpose: "batch" }) |
GET /v1/openai/v1/files/{fileId} | client.files.retrieve(id) |
GET /v1/openai/v1/files/{fileId}/content | client.files.content(id) |
POST /v1/openai/v1/batches | client.batches.create({ input_file_id, endpoint, completion_window }) |
GET /v1/openai/v1/batches | client.batches.list() |
GET /v1/openai/v1/batches/{batchId} | client.batches.retrieve(id) |
POST /v1/openai/v1/batches/{batchId}/cancel | client.batches.cancel(id) |
For the full request/response field reference, see the interactive API reference or the raw spec.
The file-then-batch flow
Section titled “The file-then-batch flow”The flow matches OpenAI’s Batch API exactly: upload a JSONL input file, create a batch from it, poll, then download the output file.
-
Upload the input file. Send your OpenAI-Batch JSONL with
purpose: "batch"toPOST /v1/openai/v1/files(multipart/form-data). Each JSONL row is{ custom_id, method, url, body }. BatchRouter converts each row into a native item —urlselects the operation,body.modelselects the model, and the rest ofbodybecomes the item input. Invalid rows are rejected with an OpenAI-shaped error. You get back an OpenAI File object; keep itsid. -
Create the batch.
POST /v1/openai/v1/batcheswith{ input_file_id, endpoint, completion_window }. Theendpointis the OpenAI endpoint your rows target —/v1/chat/completions,/v1/responses, or/v1/embeddings. Chat-completions and responses route to BatchRouter’sresponsesoperation; embeddings route toembeddings.completion_windowis"24h". The call is quote-free to you — BatchRouter quotes and funding-gates internally using the same pricing and billing controls as the native pipeline. You get back a bare OpenAI Batch object. -
Poll the batch.
GET /v1/openai/v1/batches/{batchId}untilstatusis terminal:completed,failed,expired, orcancelled. (In-flight statuses are OpenAI’svalidating→in_progress→finalizing;cancellingis the in-progress cancel state.) -
Download the results. When
completed, readoutput_file_id(anderror_file_idfor failures) from the Batch object and fetch the bytes withGET /v1/openai/v1/files/{fileId}/content. Result rows are projected to OpenAI’s per-line shape{ id, custom_id, response: { status_code, request_id, body }, error }, keyed by yourcustom_id.
Upload (step 1)
Section titled “Upload (step 1)”The OpenAI SDK handles the multipart upload for you.
input_file = client.files.create( file=open("requests.jsonl", "rb"), purpose="batch",)import fs from "node:fs";
const inputFile = await client.files.create({ file: fs.createReadStream("requests.jsonl"), purpose: "batch",});A JSONL row looks like the OpenAI Batch format — for example:
{"custom_id":"item-1","method":"POST","url":"/v1/responses","body":{"model":"gpt-4o-mini","input":{"messages":[{"role":"user","content":"Summarize: BatchRouter routes batch-AI workloads across providers."}]}}}Create, poll, and download (steps 2–4)
Section titled “Create, poll, and download (steps 2–4)”import time
batch = client.batches.create( input_file_id=input_file.id, endpoint="/v1/responses", completion_window="24h",)
while batch.status not in ("completed", "failed", "expired", "cancelled"): time.sleep(30) batch = client.batches.retrieve(batch.id)
if batch.status == "completed": output = client.files.content(batch.output_file_id) print(output.text) # OpenAI-shaped JSONL, one object per linelet batch = await client.batches.create({ input_file_id: inputFile.id, endpoint: "/v1/responses", completion_window: "24h",});
const terminal = ["completed", "failed", "expired", "cancelled"];while (!terminal.includes(batch.status)) { await new Promise((r) => setTimeout(r, 30_000)); batch = await client.batches.retrieve(batch.id);}
if (batch.status === "completed") { const output = await client.files.content(batch.output_file_id); console.log(await output.text()); // OpenAI-shaped JSONL, one object per line}Response body format: normalized vs raw
Section titled “Response body format: normalized vs raw”Because BatchRouter routes across many providers, the per-line response.body you download is, by default, BatchRouter’s normalized cross-provider output. If you need each provider’s raw API response where it was captured, request raw:
- Per batch — pass
body_format(normalized|raw, defaultnormalized) when creating the batch viaPOST /v1/openai/v1/batches. - Per download — pass
?body_format=rawonGET /v1/openai/v1/files/{fileId}/contentto override for that one download.
The effective format is echoed in the x-batchrouter-body-format response header.
Errors and expiry
Section titled “Errors and expiry”- Errors are returned in OpenAI’s envelope. On insufficient funds, batch creation returns an OpenAI-shaped
{ "error": { "type": "insufficient_quota" } }— top up in the dashboard. - Output and error files are subject to BatchRouter’s retention. A file whose bytes have been reclaimed reports
status: "expired"in its metadata and returns 410 from/content(distinct from a 404 for a file that never existed). See credits and billing and the API reference for retention details.
Migrating to the native surface
Section titled “Migrating to the native surface”Once you’re routing through BatchRouter, the native /v1/batches API unlocks capabilities the OpenAI shape can’t express:
- Free quotes before you commit (
POST /v1/quotes/model,POST /v1/quotes/workflow). sla_tier—standard(24h),flex, orpriority.routing_mode—cheapest(default),sla_aware,public_only,edge_only,hybrid, orprivacy_constrained.privacy_tier—standard,confidential, orrestricted.- Receipts — routing and billing receipts (
GET /v1/batches/{batchId}/billing-receipt).
These two surfaces operate on the same batches, so you can adopt the native API incrementally — keep the OpenAI client for existing jobs and reach for the native endpoints where you need more control.