Errors & limits
BatchRouter error response envelope, HTTP status codes, 400 preflight validation, 402 insufficient-credits flow, rate limits, and idempotent safe retries.
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
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.
Log the full error object plus the HTTP status when a request fails. The details object is open-ended and frequently carries the exact remediation (for example details.preflight on a 400).
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. |
Successful batch creation returns 202 Accepted (status: "pending"), not 200 — the batch is queued for routing, not finished. Poll GET /v1/batches/{batchId} until a terminal status.
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. |
warnings are non-blocking — ok can be true with warnings present. Surface them, but they don't stop the request. Only entries in errors cause the 400.
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:
curl https://api.batchrouter.com/v1/auth/account \ -H "Authorization: Bearer $BATCHROUTER_API_KEY"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.
Credits are purchased from the dashboard at https://batchrouter.com/app/billing; there is no programmatic checkout endpoint. Credits are charged at batch creation and settled from actual provider token usage on completion — see the per-batch billing receipt (GET /v1/batches/{batchId}/billing-receipt).
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 response
curl -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 duplicate
curl -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.
Don't reuse one Idempotency-Key across different batches. Once a key is bound to a payload, sending a different payload with it returns 409.