Errors & limits
Every BatchRouter error returns the same JSON envelope so you can handle failures consistently, and submission endpoints run a free preflight check before they ever spend credits. This page covers the error shape, the status codes you should handle, how to recover from a 402, and how Idempotency-Key makes retries safe.
Error envelope
Section titled “Error envelope”All error responses (any non-2xx) share one shape. The envelope always carries a stable, machine-readable code and a human-readable message; some errors add a details object.
{ "error": { "code": "insufficient_credits", "message": "Your credit balance is too low to reserve this batch.", "details": {} }}Branch on error.code (stable) rather than on message (may change). Treat any unknown code as a generic failure for its status class.
Status codes
Section titled “Status codes”| Status | When you get it | What to do |
|---|---|---|
| 400 | Invalid request body or parameters, or a batch/quote preflight failure (see below). | Fix the request. If error.details.preflight is present, surface the listed issues and retry. |
| 401 | Missing or invalid API key. | Send Authorization: Bearer br_live_…. Check the key wasn’t revoked and matches the environment (test keys don’t work on prod). |
| 402 | Insufficient credits to reserve the batch. | Top up credits, then retry. See Recovering from 402. |
| 403 | Authenticated, but not authorized for this resource (for example, a batch belonging to another org). | Use a key for the owning org; don’t retry as-is. |
| 404 | Resource not found (unknown batchId, file_id, etc.). | Verify the id. A just-created resource is available immediately, so a 404 means the id is wrong or not yours. |
| 409 | Conflict with current state: a duplicate Idempotency-Key sent with a different payload, or an action on a batch already in a terminal state. | Don’t blindly retry. See Idempotency & safe retries. |
| 429 | Rate limited. | Back off and retry. Honor the Retry-After header when present; otherwise use exponential backoff with jitter. |
| 5xx | Transient server-side error. | Retry idempotent requests with backoff. For POST /v1/batches, reuse the same Idempotency-Key so a retry can’t create a duplicate. |
400 preflight validation
Section titled “400 preflight validation”Before charging credits, POST /v1/quotes/model and POST /v1/batches run a preflight check on your input. When it fails, you get a 400 whose error.details.preflight holds a structured result you can show the user.
{ "error": { "code": "batch_preflight_failed", "message": "Batch preflight validation failed.", "details": { "preflight": { "ok": false, "errors": [ { "category": "context_window", "code": "preflight_context_window_exceeded", "message": "Item item-1 exceeds the model's context window.", "action": "Reduce the input length or pick a model with a larger context window.", "path": "items[0]" } ], "warnings": [] } } }}Each issue carries a category, a stable code, a message, and a customer-safe action (and optionally a path). The category values you’ll see:
| Category | What failed | Common fix |
|---|---|---|
jsonl_shape | JSONL validation — a line isn’t a valid item, or required fields are missing. | Fix the malformed line(s); each item needs customer_item_id, operation, model, and input. |
file_type | Unsupported uploaded file type for the referenced purpose. | Upload a supported type (for model_input, JSONL or the documented binary inputs). |
context_window | An item’s input exceeds the model’s context window. | Shorten the input or choose a larger-context model from GET /v1/catalog/models. |
tool_support | A required_tools entry isn’t offered by any eligible lane. | Drop the tool, or pick a model/provider that advertises it in the catalog. |
json_schema | A structured-output schema or runtime capability isn’t satisfiable. | Align the schema with what the model/provider supports. |
webhook | The webhook is unsafe — e.g. non-HTTPS or a disallowed host (code: "https_required"). | Use an https:// URL that resolves to a public host, with a secret of at least 8 characters. |
routing | No eligible lane remains after applying your constraints. | Relax routing_mode, privacy_tier, max_price, or region constraints. |
Recovering from 402 (insufficient credits)
Section titled “Recovering from 402 (insufficient credits)”A 402 means you don’t have enough credits to reserve the batch. Quotes are free; credits are reserved when you call POST /v1/batches. Recover in two steps.
-
Read your current balance from your account:
Terminal window curl https://api.batchrouter.com/v1/auth/account \-H "Authorization: Bearer $BATCHROUTER_API_KEY"const res = await fetch("https://api.batchrouter.com/v1/auth/account", {headers: { Authorization: `Bearer ${process.env.BATCHROUTER_API_KEY}` },});const { credit_balance } = await res.json();console.log(credit_balance); // { currency: "usd", amount: "0.00" }import os, requestsres = requests.get("https://api.batchrouter.com/v1/auth/account",headers={"Authorization": f"Bearer {os.environ['BATCHROUTER_API_KEY']}"},)print(res.json()["credit_balance"]) # {"currency": "usd", "amount": "0.00"}The response includes
credit_balanceas a money object ({ currency, amount }). -
Buy credits from the dashboard. Top-ups are handled through a Stripe-hosted checkout on the billing page — the public API has no credit-purchase endpoint, so send the user to:
https://batchrouter.com/app/billingAfter checkout completes and the balance updates, retry your
POST /v1/batchescall. Reuse the sameIdempotency-Keyfrom the original attempt so the retry resolves to one batch.
Idempotency & safe retries
Section titled “Idempotency & safe retries”POST /v1/batches requires an Idempotency-Key header (8–128 characters). It makes batch creation safe to retry: if a request times out or returns a 5xx, resend the exact same request with the same key.
- Same key, same payload → BatchRouter returns the original
202response and does not create a second batch. - Same key, different payload →
409 Conflict. The key is already bound to a different request; generate a fresh key for the new payload.
# First attempt — network drops before you see a responsecurl -X POST https://api.batchrouter.com/v1/batches \ -H "Authorization: Bearer $BATCHROUTER_API_KEY" \ -H "Idempotency-Key: order-2026-06-18-abc123" \ -H "Content-Type: application/json" \ -d @batch.json
# Safe retry — identical key + body returns the same batch, no duplicatecurl -X POST https://api.batchrouter.com/v1/batches \ -H "Authorization: Bearer $BATCHROUTER_API_KEY" \ -H "Idempotency-Key: order-2026-06-18-abc123" \ -H "Content-Type: application/json" \ -d @batch.jsonGuidance:
- Generate one key per logical operation (a UUID or a deterministic hash of your batch payload both work) and persist it before sending the request.
- Retry only on network errors,
429, and5xx. Don’t auto-retry a400/401/403— the request itself needs fixing. - For
429, honorRetry-After; otherwise use exponential backoff with jitter. GETrequests (polling status, fetching results) are naturally safe to retry and need no key.