BatchRouter Docs

OpenAI compatibility

Point an existing OpenAI Batch client at BatchRouter by changing base_url and the API key. The file-then-batch flow on the /v1/openai/v1/* surface.

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. The short version is OpenAI Batch in five minutes.

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

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, 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.

Both surfaces operate on the same underlying batches. A batch created through /v1/openai/v1/batches is a real BatchRouter batch — it appears in your dashboard, counts against your credits and billing controls, and can also be inspected through the native API.

Base URL and authentication

Point your OpenAI client's base_url at:

https://api.batchrouter.com/v1/openai/v1

For 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
)

This is a routing layer, not OpenAI. Your br_live_ key bills against your BatchRouter credit balance, and the model you name in each request row is resolved against BatchRouter's catalog (GET /v1/catalog/models) and routed to whichever provider lane serves it — which may not be OpenAI at all.

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 & pathOpenAI SDK equivalent
POST /v1/openai/v1/filesclient.files.create({ file, purpose: "batch" })
GET /v1/openai/v1/files/{fileId}client.files.retrieve(id)
GET /v1/openai/v1/files/{fileId}/contentclient.files.content(id)
POST /v1/openai/v1/batchesclient.batches.create({ input_file_id, endpoint, completion_window })
GET /v1/openai/v1/batchesclient.batches.list()
GET /v1/openai/v1/batches/{batchId}client.batches.retrieve(id)
POST /v1/openai/v1/batches/{batchId}/cancelclient.batches.cancel(id)

For the full request/response field reference, see the interactive API reference or the raw spec.

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.

  1. Upload the input file. Send your OpenAI-Batch JSONL with purpose: "batch" to POST /v1/openai/v1/files (multipart/form-data). Each JSONL row is { custom_id, method, url, body }. BatchRouter converts each row into a native item — url selects the operation, body.model selects the model, and the rest of body becomes the item input. Invalid rows are rejected with an OpenAI-shaped error. You get back an OpenAI File object; keep its id.

  2. Create the batch. POST /v1/openai/v1/batches with { input_file_id, endpoint, completion_window }. The endpoint is the OpenAI endpoint your rows target — /v1/chat/completions, /v1/responses, or /v1/embeddings. Chat-completions and responses route to BatchRouter's responses operation; embeddings route to embeddings. completion_window is "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.

  3. Poll the batch. GET /v1/openai/v1/batches/{batchId} until status is terminal: completed, failed, expired, or cancelled. (In-flight statuses are OpenAI's validating → in_progress → finalizing; cancelling is the in-progress cancel state.)

  4. Download the results. When completed, read output_file_id (and error_file_id for failures) from the Batch object and fetch the bytes with GET /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 your custom_id.

Upload (step 1)

The OpenAI SDK handles the multipart upload for you.

input_file = client.files.create(
    file=open("requests.jsonl", "rb"),
    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-5.4-mini","input":{"messages":[{"role":"user","content":"Summarize: BatchRouter routes batch-AI workloads across providers."}]}}}

gpt-5.4-mini is a model id from the live catalog. The models available to you come from GET /v1/catalog/models — see the API reference.

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 line

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, default normalized) when creating the batch via POST /v1/openai/v1/batches.
  • Per download — pass ?body_format=raw on GET /v1/openai/v1/files/{fileId}/content to override for that one download.

The effective format is echoed in the x-batchrouter-body-format response header.

If you need provider-portable output, keep the default normalized — it gives you a consistent shape regardless of which lane served the row. Use raw only when your downstream code depends on a specific provider's exact response structure.

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

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).
  • routing_mode — cheapest (default), sla_aware, public_only, edge_only, hybrid, or privacy_constrained.
  • privacy_tier — standard, confidential, or restricted.
  • 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.

Next steps

On this page