- 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 returns400.
Build the batch
A batch has one model, one endpoint, and a list of requests. Each requestbody is the same body you send to the sync endpoint.
batch.json
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 withprovider.only when you send images. A batch with file URLs, inline files, or YouTube URLs that lands on a provider that can’t take them moves to another provider for the model that can. This happens only for batches billed through OpenRouter, not ones sent with your own provider key, when provider.allow_fallbacks is not false and, if you set provider.only, the new provider is in that list.
File parts use the endpoint’s own type:
input_file on /v1/responses, file on /v1/chat/completions, and document on /v1/messages. The entries below use the Responses shape so they drop into the batch above; on a /v1/chat/completions batch, send messages with image_url and file parts instead. The YouTube entry is the exception: it is a /v1/chat/completions line, so use it in a chat completions batch with a model that takes video input, such as google/gemini-2.5-flash.
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. If the provider chosen at submit can’t take file URLs, the batch moves to the cheapest other provider for the model that can, under the same conditions, as long as it costs at most twice the original provider’s price. It fails with No batch provider for model '<model>' that this batch can route to accepts file URLs; use the sync API. when no provider qualifies. Public YouTube URLs work only on Google AI Studio, as video_url parts on /v1/chat/completions. For a model that takes video input, a YouTube batch that lands on Google Vertex moves to Google AI Studio for the same model under the same conditions and price limit, and fails with No batch provider for model '<model>' that this batch can route to accepts YouTube URLs; use the sync API. when no provider qualifies.
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
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
Setcallback_url on the batch or Default batch webhook URL in your workspace settings. A batch-level URL overrides the workspace default and must use public HTTPS, and its hostname must resolve in DNS to public addresses only. A private or internal address, or a hostname that does not resolve, makes the submit fail with 400. If OpenRouter’s DNS resolver fails while checking the hostname, the submit returns 503; retry it. OpenRouter checks the hostname again before it hands the event to its webhook delivery service, and skips the webhook if the hostname now resolves to a private address. That service handles delivery retries; these checks are not repeated on each retry or redirect. Webhooks are delivered over IPv4, so the hostname needs at least one public A record; a host with only AAAA records never receives deliveries.
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)
- Verify the signature against the raw body, before you parse the JSON.
- Put the batch ID on a durable queue in one write, keyed by the event
id. Theidis part of the signed body, and a repeated delivery has the sameid, so a repeat adds no second job. - If the queue write fails, return a non-
2xxresponse so OpenRouter retries. A2xxtells OpenRouter the delivery succeeded. - From the queue, fetch the batch. The webhook can arrive a few seconds before
GETshows the final status. If the status does not match the event yet, wait and retry.
Option B: Poll
CallGET /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.
If a batch the provider ran ends failed, expired, or cancelled and the provider reports fewer requests than OpenRouter accepted, request_counts.total is the accepted number and the requests the provider did not report count as failed. Older batches that have no stored accepted count keep the provider’s counts.
Poll until final
validating, in_progress, and finalizing before it reaches a final status. If you use both a webhook and polling, process each batch once.
This example waits for results on a completed batch. Do not use it as written for a Google Vertex BYOK batch submitted with an input file: its results stay null, so the loop never ends. For that kind of batch, stop as soon as the status is final. See BYOK.
To check many batches at once, list them. List items have metadata only, and results is always null.
List batches still running
200 OK
Page with
after only; offset and before are unsupported. Use the ID cursor rather than timestamps: created_at has whole-second precision, while created_after preserves subsecond precision. A batch created later in the same second can still match created_after=<created_at>.Read the results
When the status iscompleted, results holds one entry per request. There is no separate download endpoint. The one exception is a Google Vertex BYOK batch submitted with an input file: its results stay null and, when Vertex reported a Cloud Storage output folder, output.uri names that folder in your bucket. See BYOK. Each entry has either a response or an error. Results are not in input order, so match them on custom_id.
Read the results
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. A Google Vertex BYOK batch submitted with an input file is the exception: OpenRouter serves no results for it, so GET keeps returning the batch and, when Vertex reported one, its output location. See BYOK.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 returns409. 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 allowingopenai/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
provider at the top level of the batch, next to model. It applies to every request. A provider field inside a request body does not pin the batch, so a chat batch with provider only in its bodies runs on the default provider.
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
Setendpoint to choose the request shape for every body in the batch. To mix shapes, submit separate batches.
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 reportusage.is_byok: true and the provider’s inference estimate in usage.cost_details.upstream_inference_cost.
When the BYOK-only switch is on for your account or workspace, batches run only on your own provider keys:
- Keys marked as fallback-only count, the same as sync requests.
- If none of your keys covers the model, the submit fails with
404instead of202 Accepted. - Keys for providers that can’t run a batch on your own key (Fireworks, Mistral, Sail Research, and Claude Platform on AWS) don’t count.
Google Vertex
Google Vertex
Vertex batches use a bucket in your GCP project for input and output. Leave out Read the
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.Accounts that OpenRouter has enabled for it can name an input file that is already in their own bucket, in place of requests: "input": { "uri": "gs://your-bucket/batches/input.jsonl" }. Send exactly one of input or requests. The URI names one object: no wildcard and no folder. Vertex reads the file directly as the job input. OpenRouter does not copy, convert, or read its contents, so the file must meet these rules:-
It is in Vertex’s own Gemini batch input format (one JSON object per line with a
request), not the{ custom_id, body }format ofrequests. -
Every line has a unique
custom_idthat does not look like a number. Vertex rewrites numeric-looking values ("000042"comes back as42), and OpenRouter cannot prefix IDs in a file it does not write. -
Your project’s Vertex AI service agent (
service-<PROJECT_NUMBER>@gcp-sa-aiplatform.iam.gserviceaccount.com) can read the object. Google’s batch guide names that agent as the reader of a Cloud Storage input file, and it is the same one that needs access to your batch bucket. If Vertex fails the job with a permission error on the input file, grant read access to the identity that the error names. -
The service account of your provider key can read the object’s metadata (
storage.objects.get, for example throughroles/storage.objectVieweron the bucket). OpenRouter uses it to check, before the job is created, that the object exists and is within Google’s size limit for a Cloud Storage batch input file (1 GB when this page was written). -
The batch uses the chat completions, responses, or messages endpoint. Other endpoints return
400at submit. - The object stays unchanged and readable until the batch ends. OpenRouter checks only its size and passes the URI on, so Vertex reads whatever the object holds when the job starts. An auto-provisioned batch bucket deletes objects 30 days after they are created, so an input file kept there can be deleted before you submit it.
202 Accepted, the batch ends failed without creating a Vertex job when the object is missing, your key cannot read it, or it is over the size limit. error.message says which. Fix the file or its permissions and submit again.OpenRouter does not know how many requests the file holds, so request_counts.total is 0 when the batch is accepted and stays 0 while it runs. The counts come from Vertex when the job ends. At submit, the batch adds nothing toward the limits on requests per batch and requests in progress. The limit on batches in progress applies. Google also limits how many requests one job can hold. OpenRouter does not count the lines, so Vertex applies that limit, not the submit.Vertex writes the output under openrouter-batch/output/ in your batch bucket, the same place as for other Vertex BYOK batches.When the Vertex job ends, OpenRouter reads the job output with your provider key, stores a copy, and bills from that copy. Once the copy is recorded, OpenRouter does not read your bucket again for this batch. If an attempt fails before then, the next attempt reads the output again. OpenRouter keeps the copy for 30 days as the record of what it billed. Vertex writes each request back on its output line, so that copy holds your requests as well as the answers. Each output line is billed once, from the token counts in that line’s usageMetadata, with the same BYOK fee as any other BYOK batch:- A line with a complete answer is billed whether or not it has a
custom_id. Lines that repeat acustom_idare each billed. - A line is not billed when it carries a Vertex error, is not valid JSON, has no
usageMetadata, or has no finish reason. A line that Vertex blocked, at the prompt or with a finish reason such asSAFETYorRECITATION, is not billed either, even when it reports token counts.
user value.OpenRouter does not serve the results of these batches. When the batch has ended, GET /api/v1/batches/{id} returns "results": null and, when Vertex reported a Cloud Storage output folder, an output field that names it:Completed batch submitted with an input file
predictions JSONL files in that folder from your bucket, as you would for a job you ran on Vertex yourself. The lines are in Vertex’s own batch output format. OpenRouter does not convert them to the results format, does not restore the order of your input lines, and adds no generation IDs. For a batch Vertex completed, output appears only once billing has read the output, so the batch can report finalizing for a short time first. A failed, cancelled, or expired batch also names the folder when Vertex reported one. That folder may hold rows Vertex exported before the job ended, and it can be empty. OpenRouter does not read or bill those rows. output is left out of batch lists.The output folder is yours. OpenRouter never deletes it: not when you delete the batch, and not after 30 days. Deleting the batch removes the Vertex job and OpenRouter’s billing copy of the output, and nothing in your bucket. If the folder is in a bucket that OpenRouter created for your key, that bucket’s own rule deletes objects 30 days after they are written, so copy what you need before then. Do not change that rule: OpenRouter checks it and refuses to use the bucket without it. To keep output longer, set your own bucket on the key. The status response keeps naming the folder after the files are gone.Input files are not enabled for any account yet.OpenRouter does not run content screening or moderation on a batch submitted this way, because it never reads the file’s lines. The per-request checks that requests gets at submit do not run either: Vertex decides what it accepts.Account-level limits still apply. The batch ends failed without creating a Vertex job when your account cannot run inference, cannot send image inputs, or must have its input moderated or its image inputs screened, and when your settings block the model or its author. A provider block in your settings does not apply to your own provider key, as on every BYOK request. OpenRouter cannot tell whether the file holds images, so it treats every input file as if it does.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.Vertex BYOK accepts PDFs in Cloud Storage the same way. Put the gs://your-bucket/report.pdf URI where a file URL goes: file_url on an input_file part, file_data on a file part, or the url of a document source. The PDF must be in a user message, the object name must end in .pdf (upper or lower case), and the model must list file input. Other file types, and gs:// files on batches billed through OpenRouter, are rejected. OpenRouter has confirmed that Vertex reads a gs:// PDF on its synchronous API, but not yet inside a batch job, so try a small batch first.Google reads the PDF as your project’s Vertex AI service agent (service-PROJECT_NUMBER@gcp-sa-aiplatform.iam.gserviceaccount.com), not as the service account in your key. Grant that agent roles/storage.objectViewer on the PDF’s bucket and keep the grant until the batch finishes, or the rows that reference the PDF fail.Anthropic
Anthropic
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.Alibaba
Alibaba
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.Keys with invalid characters
Keys with invalid characters
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
Content screening. A batch that names an
input file is not screened; see BYOK. A batch can fail validation after 202 Accepted when image screening rejects a request. The batch moves to status: "failed", its error identifies the request and reports This request contains prohibited content., and no requests in that batch are submitted to the provider. This refusal is terminal; do not retry the same content. If screening is temporarily unavailable, the batch usually continues to the provider without waiting for screening. For accounts where screening is required, the batch instead stays in validating while OpenRouter retries screening; keep polling the same batch instead of submitting a replacement, which could leave both batches eligible to run. Screening one batch has a time limit: when it runs out, the remaining requests usually continue unscreened, but where screening is required the batch fails with This batch has too many images to screen within the submit window. Split it into smaller batches. and will not succeed on retry, so split it into smaller batches.