Skip to content

Troubleshooting

When a request fails, BatchRouter returns a JSON body with a stable, machine-readable code so you can branch on the cause rather than the message:

{
"error": {
"code": "insufficient_credits",
"message": "Human-readable explanation.",
"details": { }
}
}

Always read error.code (and, where present, error.details) — the message text is for humans and may change. This page walks through the failures you are most likely to hit, organized as problem → cause → fix. For the full code reference see Errors, and for limits and backoff see Rate limits.

401 Unauthorized — missing or invalid key

Section titled “401 Unauthorized — missing or invalid key”

Problem. Any request returns 401.

Cause. No Authorization header, a malformed header, or a key that was revoked, mistyped, or belongs to a different environment.

Fix.

  1. Send the header as Authorization: Bearer br_live_…. The word Bearer, a single space, then the key.

  2. Confirm you are calling the right host. A key created in production does not work against https://test.api.batchrouter.com, and vice versa.

  3. Verify the key resolves by calling a cheap authenticated endpoint:

    Terminal window
    curl https://api.batchrouter.com/v1/auth/account \
    -H "Authorization: Bearer $BATCHROUTER_API_KEY"
  4. If it still fails, the key was likely revoked. Create a new one with POST /v1/auth/account/api-keys in the dashboard.

403 Forbidden — authenticated but not allowed

Section titled “403 Forbidden — authenticated but not allowed”

Problem. The key is valid (no 401) but the request returns 403.

Cause. The key authenticated, but it is not authorized for that resource — for example, requesting a batch that belongs to another organization, or calling an operator/admin-only path that is not part of the public surface.

Fix. Confirm the batchId belongs to the account that owns the key, and that you are calling a documented public endpoint. List your own batches to verify ownership:

Terminal window
curl https://api.batchrouter.com/v1/batches \
-H "Authorization: Bearer $BATCHROUTER_API_KEY"

Problem. POST /v1/batches returns 402 with error.code insufficient_credits.

Cause. Submitting a batch charges credits up front. Your balance does not cover the quoted cost. Quotes (POST /v1/quotes/model) are free and never return 402; the charge happens only at batch creation.

Fix.

  1. Buy credits in the dashboard at batchrouter.com/app/billing. There is no public billing-checkout endpoint — purchasing is done in the dashboard.

  2. To avoid hitting zero mid-pipeline, enable auto top-up so the platform refills your balance off-session when it drops below a threshold:

    Terminal window
    curl https://api.batchrouter.com/v1/billing/auto-topup \
    -H "Authorization: Bearer $BATCHROUTER_API_KEY"

    Configure it with PUT /v1/billing/auto-topup, and inspect past refills with GET /v1/billing/auto-topup/attempts. See the API reference for the request fields.

  3. Check current usage and remaining balance any time with GET /v1/usage.

Problem. A quote or batch submission returns 400 with error.code batch_preflight_failed.

Cause. Preflight runs before any credits are spent and before the provider quote. It checks the shape and feasibility of your request. The specific issues are listed under error.details.preflight.errors[] — each issue carries a category, a stable code, and an action describing how to fix it:

{
"error": {
"code": "batch_preflight_failed",
"message": "Batch failed preflight validation.",
"details": {
"preflight": {
"ok": false,
"errors": [
{
"category": "context_window",
"code": "preflight_context_window_exceeded",
"message": "Item exceeds the model context window.",
"action": "Reduce the input length or choose a model with a larger context window.",
"path": "item-42"
}
],
"warnings": []
}
}
}
}

Fix. Read error.details.preflight.errors[] and resolve each by category:

CategoryWhat it meansFix
jsonl_shapeAn item in your JSONL is malformed or missing required fields.Validate each line against the canonical item shape.
file_typeThe uploaded file content type is not supported.Upload JSONL via POST /v1/files using a supported content type (see the API reference).
context_windowAn item’s input is larger than the model’s context window.Shorten the input, split the item, or pick a model with a larger window via GET /v1/catalog/models.
tool_supportA requested hosted tool or capability is not available on the routable providers.Remove the unsupported tool, or constrain routing to a provider that supports it.
json_schemaA structured-output schema or runtime capability is invalid or unsupported.Correct the schema, or relax the output constraints.
webhookThe delivery webhook URL is unsafe or not HTTPS (https_required).Use a public HTTPS URL; internal, loopback, and metadata hosts are rejected.
routingNo eligible provider lane can satisfy the request under your routing constraints.Loosen routing_mode / privacy_tier, or check provider availability with GET /v1/providers.

409 Conflict — idempotency or terminal state

Section titled “409 Conflict — idempotency or terminal state”

Problem. POST /v1/batches returns 409, or a cancel/retry/redeliver call is rejected with 409.

Cause. Either an idempotency conflict — you reused an Idempotency-Key header value with a different request body — or the resource is already in a state that forbids the action (for example cancelling a batch that already reached a terminal status).

Fix.

  1. Reusing a key with the same body is fine — a true replay returns the original 202 response and the same bat_… id. Only a different payload under the same key conflicts. Generate a fresh, unique Idempotency-Key (a UUID works well) for each distinct submission, and reuse the same value only when safely retrying the identical request:

    Terminal window
    curl -X POST https://api.batchrouter.com/v1/batches \
    -H "Authorization: Bearer $BATCHROUTER_API_KEY" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{ "quote_id": "qlock_…" }'
  2. For a state conflict, fetch the batch first (GET /v1/batches/{batchId}) and check its status. You can only cancel a batch that has not yet reached a terminal status (completed, failed, cancelled, expired), and you can only redeliver one whose delivery actually failed.

Problem. Requests start returning 429.

Cause. You exceeded the request rate for your account. This is about how often you call the API, not how large a batch is — batching many items into one submission is the intended way to process volume.

Fix.

  1. Back off and retry with exponential backoff and jitter. If a Retry-After header is present, wait at least that long before retrying.

  2. Coalesce work: submit many items in a single POST /v1/batches instead of many small batches, and poll less aggressivelyGET /v1/batches/{batchId} every few seconds is plenty for a long-running batch.

  3. Prefer webhooks over tight polling loops so you are notified on completion instead of hammering the status endpoint.

See Rate limits for current limits and recommended backoff.

Problem. A batch is not progressing, or its status is failed.

Cause. Normal progression is pending → queued → routing → dispatched → processing → completing → completed. Time in routing/processing depends on your sla_tierstandard allows up to 24h, while flex may take longer and priority is faster. A failed terminal status means items could not be completed; the details live on the batch and its items.

Fix.

  1. Fetch the batch and read its status and any failure detail:

    Terminal window
    curl https://api.batchrouter.com/v1/batches/bat_123 \
    -H "Authorization: Bearer $BATCHROUTER_API_KEY"
  2. Inspect per-item status to find which items failed and why:

    Terminal window
    curl https://api.batchrouter.com/v1/batches/bat_123/items \
    -H "Authorization: Bearer $BATCHROUTER_API_KEY"
  3. If the batch failed for a transient reason, re-submit the failed work with POST /v1/batches/{batchId}/retry. If it is genuinely stuck and no longer needed, cancel it with POST /v1/batches/{batchId}/cancel.

  4. For completed batches, pull results with GET /v1/batches/{batchId}/results (paginated) or fetch a signed file via GET /v1/batches/{batchId}/artifact-url.

Problem. Your batch completed but your endpoint never got a delivery callback.

Cause. The most common reasons are a non-HTTPS or unreachable URL, signature verification failing on your side so you reject a delivery that did arrive, or no webhook being configured at all.

Fix.

  1. Confirm a webhook is configured — either per batch ({ "url", "secret" } at submission time) or as an org default via PUT /v1/auth/account/delivery-webhook. The URL must be HTTPS and publicly reachable; internal and loopback hosts are rejected at preflight.

  2. Check what BatchRouter actually attempted for the batch, including delivery attempts and responses:

    Terminal window
    curl https://api.batchrouter.com/v1/batches/bat_123/webhooks \
    -H "Authorization: Bearer $BATCHROUTER_API_KEY"
  3. Verify the signature correctly. Each call carries X-BatchRouter-Signature, a base64url HMAC-SHA256 over {timestamp}.{body} using your webhook secret, with the timestamp in X-BatchRouter-Timestamp. If your verification rejects valid deliveries, you will see attempts succeed on our side but never process them. Compare using a constant-time check:

    import { createHmac, timingSafeEqual } from "node:crypto";
    function verify(rawBody, timestamp, signature, secret) {
    const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("base64url");
    const a = Buffer.from(expected);
    const b = Buffer.from(signature);
    return a.length === b.length && timingSafeEqual(a, b);
    }
  4. While verifying signatures, sign over the raw request body bytes — re-serializing the JSON can change whitespace and break the HMAC.

  5. As a fallback, you do not need webhooks at all: poll GET /v1/batches/{batchId} until it reaches completed, then pull results. Webhooks are a convenience, not the only path.

If you have a reproducible failure, capture the error.code, the full error.details, and the batch id (bat_…) — those identify the exact cause. The interactive API reference lets you replay any request with your key, and the raw spec is at api.batchrouter.com/openapi.json.