BatchRouter Docs

Batch status lifecycle

Every batch state BatchRouter reports — received through completed, the terminal states, and whether to keep polling or stop at each one.

Every batch you create moves through a fixed sequence of states, from acceptance to a single terminal outcome. You observe this progression by polling GET /v1/batches/{batchId} and reading the state field of the returned batch object. This page is the reference for what each state means, which ones are terminal, and what your client should do at each step.

The lifecycle field is named state, not status, and the canceled state is spelled canceled (one l). The OpenAI-compatible surface (/v1/openai/v1/*) is separate: it projects each batch onto OpenAI's own status vocabulary (validating, in_progress, finalizing, completed, failed, expired, cancelling, cancelled). See OpenAI compatibility.

The same values are accepted as a filter on GET /v1/batches?state=... (or state=active for every non-terminal state), so they name both a batch's current state and a list filter.

The state machine

A batch advances through up to seven non-terminal states in order, then settles into exactly one of four terminal states. It never moves backward.

received → validated → priced → queued → dispatching → running → finalizing → completed
                                                                              ‖
                                                          failed · canceled · expired

You will not necessarily observe every intermediate state — a fast batch can pass through several between two polls. Treat the sequence as the guaranteed order, not as a guarantee that you will see each one. In practice a newly created batch is already priced when POST /v1/batches returns.

Only four states mean "stop polling": completed, failed, canceled, and expired. Everything else means the batch is still in flight — keep polling.

State reference

StateTerminal?MeaningWhat to do
receivedNoThe batch record has been created and is being finalized.Keep polling.
validatedNoIntake validation is complete; the batch is not yet priced.Keep polling.
pricedNoThe batch was accepted and priced (POST /v1/batches returned 202 with this state), and is waiting to be queued for routing.Keep polling.
queuedNoQueued for routing; waiting for the router to dispatch it to a provider lane.Keep polling.
dispatchingNoWork is being handed to the chosen provider lane(s) (or workflow-product lane) for your items.Keep polling.
runningNoThe provider is actively running your items; counts shows per-item progress. This is usually the longest phase.Keep polling, at a relaxed interval.
finalizingNoProvider work is done; BatchRouter is assembling results, billing, and delivery artifacts.Keep polling.
completedYesAll processing finished. Results are available. Note that individual items may still have failed — check counts.failed and per-item statuses.Stop polling. Fetch results.
failedYesThe batch could not complete (for example, no eligible lane, or an unrecoverable execution error).Stop polling. Inspect the batch; retry if appropriate.
canceledYesThe batch was canceled via POST /v1/batches/{batchId}/cancel. Work already dispatched may have completed before cancellation took effect.Stop polling.
expiredYesThe batch did not complete before its SLA deadline (deadline_at).Stop polling. Resubmit if you still need the work.

completed, failed, canceled, and expired are the four terminal states; the batch-detail endpoint documents polling until batch.state is one of them. Reaching a terminal state is final; a batch never leaves it.

Polling for a terminal state

The recommended pattern is to poll GET /v1/batches/{batchId} until batch.state is terminal, backing off between attempts so you do not poll a long-running batch too aggressively.

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

Prefer webhooks over tight polling when you can. Register a per-batch webhook ({url, secret}) on the create call, or set an org-wide default with PUT /v1/auth/account/delivery-webhook, and BatchRouter notifies you on terminal transitions. Deliveries are signed with X-BatchRouter-Signature (HMAC-SHA256). Polling remains the fallback. See the API reference for the webhook payload shape.

Per-item statuses

A completed batch does not mean every item succeeded. The batch-level state describes the job as a whole; each item carries its own outcome. When you read results from GET /v1/batches/{batchId}/results, each row carries its own status (completed or failed) alongside an output (success payload) and an error (failure detail):

  • A successful item has status: "completed" and a populated output; its error is null.
  • A failed item has status: "failed" and a populated error; its output is null.

So a terminal completed batch can contain a mix of succeeded and failed items. Always check items individually rather than assuming a completed batch means total success. For the exact result row fields, see the API reference.

If you point an existing openai SDK at the drop-in OpenAI-compatible surface (/v1/openai/v1/*), result rows are projected to OpenAI's per-line shape instead — { id, custom_id, response: { status_code, request_id, body }, error } — where a populated response marks success and a populated error marks failure. That OpenAI projection is served by GET /v1/openai/v1/files/{fileId}/content, not by the native /results endpoint.

If you only need successes and want to re-run the failures, use POST /v1/batches/{batchId}/retry after the batch is terminal. See Manage batches for retry, cancel, and redeliver semantics.

Reading results once terminal

Once a batch reaches completed, fetch the output:

  1. Page through results with GET /v1/batches/{batchId}/results (paginated), or

  2. Download the whole artifact via GET /v1/batches/{batchId}/artifact-url, which returns a short-lived signed URL to the full output file.

If retention garbage collection has reclaimed a result file's bytes, the artifact is gone — the file, not the batch, is reported as expired. On the OpenAI-compatible download path, this is distinguishable: GET /v1/openai/v1/files/{fileId}/content returns a 410 for a reclaimed (expired) artifact — distinct from a never-existed 404. See Get your results for the full retrieval walkthrough.

Next steps

On this page