Skip to main content
This guide submits a batch, waits for it with a webhook or by polling, reads the results, and deletes the batch. For what batch is and when to use it, see the Batch API overview. Before you start, you need:
  • An OpenRouter API key.
  • Credits for the estimated cost. OpenRouter checks the estimate against your balance before it accepts the batch. Batches on your own provider key (BYOK) and invoiced accounts skip this check.
  • A model from the batch models list. Use its normal slug, for example anthropic/claude-haiku-5.5. A model with no batch variant returns 400.

Build the batch

A batch has one model, one endpoint, and a list of requests. Each request body is the same body you send to the sync endpoint.
batch.json
Put endpoint, model, provider, completion_window, and callback_url before requests. OpenRouter reads the body as a stream so it can accept large batches without buffering them. If requests comes first, the submit returns 400.

Add images and files

Batch accepts text, public image URLs, and some file URLs. It returns text and embeddings only. What works depends on the provider that runs the batch, so pin a provider with provider.only when you send images or files. File parts use the endpoint’s own type: file on /v1/chat/completions, input_file on /v1/responses, and document on /v1/messages. On Google Vertex, image URLs must use HTTPS, end in .png, .jpg, .jpeg, .webp, .heic, or .heif, and not point at Google API, Googleusercontent, or Cloud Storage HTTPS hosts. A provider can also turn off image URLs for one model. File URLs work only on models that list file input.
Unknown parameters are dropped before the request reaches the provider, the same as the sync API.
The provider fetches each URL when it runs the batch, which can be hours after you submit. Keep the URL public until the batch finishes. A request that fails these checks moves the batch to failed, and error.message names the request’s custom_id.

Submit the batch

Endpoint
202 Accepted
Save the id. A 202 means OpenRouter stored the batch. It checks every request next. If a check fails, the batch moves to failed and error.message says why. provider is the provider that runs the batch. expires_at is the time after which that provider stops working on the batch, or null if the provider has no hard deadline. Most providers allow 24 hours. Google AI Studio allows 48 hours, and Google Vertex allows 96 hours. If OpenRouter moves the batch to another provider for capacity before it starts, later reads show the new provider. expires_at changes to the new provider’s window, still counted from created_at.

Wait for the batch to finish

Most batches finish in minutes, and nearly all finish within 24 hours. Use a webhook to find out when a batch finishes, and keep a slow poll as a backup.
Google Vertex can queue a batch for up to 72 hours while it waits for capacity, then process it for up to 24 hours. A Vertex batch that is still validating or in_progress after 24 hours is healthy.

Option A: Wait with a webhook

Set callback_url on the batch, or set Default batch webhook URL in your workspace settings. A callback_url on the batch overrides the workspace default. The URL must be public HTTPS. A private or internal address makes the submit fail with 400. OpenRouter sends one POST when the batch reaches a final status:
Webhook body
The event holds only the batch ID. Fetch the batch to get the status and results. A repeated delivery of the same event has the same id and idempotency key. To get a signing secret, open your workspace settings and select Generate signing secret under Webhook signing secret. Video generation webhooks use the same secret.
Webhook receiver (Express)
  1. Verify the signature against the raw body, before you parse the JSON.
  2. Put the batch ID on a durable queue in one write, keyed by the event id. The id is part of the signed body, and a repeated delivery has the same id, so a repeat adds no second job.
  3. If the queue write fails, return a non-2xx response so OpenRouter retries. A 2xx tells OpenRouter the delivery succeeded.
  4. From the queue, fetch the batch. The webhook can arrive a few seconds before GET shows the final status. If the status does not match the event yet, wait and retry.
To see a real delivery before you write a receiver, set callback_url to a temporary Webhook.site URL. Webhook.site shows the body and headers. It does not verify the signature.

Option B: Poll

Call GET /api/v1/batches/{id} until the status is final: completed, failed, expired, or cancelled. Once a minute is enough. Back off if you get 429.
Poll until final
The status moves through validating, in_progress, and finalizing before it reaches a final status. If you use both a webhook and polling, process each batch once. To check many batches at once, list them. List items have metadata only, and results is always null.
List batches still running
200 OK
Batches are listed newest first. Every API key in a workspace sees the same batches, including batches submitted with other keys.
Page with after only. There is no offset or before parameter. Do not page by timestamp: created_at is in whole seconds, but the created_after filter keeps subsecond precision. A batch created later in the same second can still match created_after=<created_at>.

Read the results

When the status is completed, results holds one entry per request. There is no separate download endpoint. Each entry has either a response or an error. Results are not in input order, so match them on custom_id.
Read the results
Each response.body has the same shape as the sync endpoint’s response. Its id is the generation ID. To report a bad generation, submit that ID through Report Feedback.
OpenRouter deletes requests and results 30 days after you submit. After that, GET on the batch returns 410 Gone. Save the results you need before then.

Delete the batch

To remove the stored requests and results sooner, delete the batch after you save the results. You can only delete a finished batch. A batch that is still running returns 409. There is no cancel endpoint yet.
Shell
200 OK
OpenRouter also deletes provider-side files, whatever upstream.status says: This includes input left behind by a submission that failed. upstream is left out when no provider was assigned. A 200 means every cleanup step finished, and a later GET returns 404. A 5xx means cleanup stopped part-way. Send the same request again to resume. Deleting a batch does not refund it. Billing, generation, and audit records stay. To delete a BYOK batch, the provider key it ran on must still be enabled. Otherwise the request returns 409.

Choose a provider

A batch runs on one provider. By default, OpenRouter picks the cheapest provider that serves the model in batch mode, after your account’s provider allowlist, data policy, guardrails, and BYOK keys apply. Guardrail model lists match a batch model by its base model, so allowing openai/gpt-5-nano also allows openai/gpt-5-nano:batch. Providers at the same price share traffic, and a BYOK key is preferred over cheaper platform providers. If the chosen provider has no capacity, OpenRouter moves the batch to the next cheapest allowed provider and bills that provider’s price.
Pin to Google Vertex
If none of the providers in only serves the model in batch mode, the submit returns 404. Embeddings batches do not accept provider.

Use other endpoints

Set endpoint to choose the request shape for every body in the batch. To mix shapes, submit separate batches.
For embeddings, input is a string, an array of strings, a token array, or an array of token arrays. Multimodal input, input_type, and provider are not supported on embeddings batches. Each result body is a standard embeddings response, with one embedding per input string, ordered by index:
Embeddings result

BYOK

If you have a provider key, batches route through it the same as sync requests. The provider bills you for inference, and OpenRouter charges only the BYOK fee. Completed batches report usage.is_byok: true and the provider’s inference estimate in usage.cost_details.upstream_inference_cost.
Vertex batches use a bucket in your GCP project for input and output. Leave out bucket, and OpenRouter creates a private bucket on the first batch in each region. You can also give an existing bucket. See Google Vertex API keys for permissions.Vertex BYOK also accepts gs://your-bucket/image.jpg image URLs. Your project’s Vertex AI service agent must be able to read the image until the batch finishes. Image names must end in .png, .jpg, .jpeg, .webp, .heic, or .heif.
An Anthropic key declared for the US data region sends inference_geo: "us" on Claude 4.6 and later models, the same as sync requests.
The key’s endpoint_url must be a DashScope OpenAI-compatible base that ends in /v1, for example https://dashscope.aliyuncs.com/compatible-mode/v1. Other URLs return 400 at submit.
A key that contains characters that cannot go in an HTTP header, such as a Unicode dash or an invisible character from copy and paste, returns 400 at submit. Fix the key in your provider key settings and resubmit. Google Vertex service-account JSON keys are exempt.

Common errors