Skip to main content
A batch is one job holding many requests to one model. Opper runs it on the provider’s batch API, which costs about half the synchronous price and returns the results within 24 hours. You send the requests inline in one JSON body, poll the batch, and read the results when it ends.
Batch currently runs on OpenAI and Anthropic models, in the US. It is not available in the EU yet.

Create a batch

Each body is the normal request for that endpoint and goes to the provider as it is. Opper sets model in each body for you, so you can leave it out. For a Claude batch, each body is an Anthropic Messages request:
A body in another format than the model’s batch endpoint takes (a chat completions body for a Claude model, for example) answers 400 naming the endpoints the model takes. stream: true and callback_url also answer 400. The response is 202 with the batch object, in status validating. Every custom_id and body is checked before anything is accepted: if one line is malformed, the whole batch is refused. A body the provider rejects later comes back as that line’s error in the results.

Poll the batch

The batch object carries the fields of OpenAI’s batch object plus OpenRouter’s, so a client written for either can read it:
There is no cancel endpoint. Most batches finish in minutes; poll every 30 to 60 seconds.

Read the results

Once the batch has ended (completed, expired or cancelled), GET /v3/batches/{id} includes results inline. Each line is OpenAI’s batch result line, with the provider’s response body as it is:
Results are not in request order: match them by custom_id. For a large batch, page through GET /v3/batches/{id}/results instead of reading them all in one response:
limit is 1 to 10,000 (default 1,000). Pass next_cursor back as cursor until has_more is false. Before the batch has ended this answers 409, and after the results window it answers 410.

End to end in code

The OpenAI SDK’s client.batches uploads a JSONL file first and will not work against /v3/batches. Use plain HTTP:

List and delete batches

DELETE works only once the batch has ended; before that it answers 409. It deletes the batch and its stored results right away, instead of waiting for the results window.

Coming from OpenRouter

The request body is the same. Change the base URL and the key:
The batch object and result lines have the fields OpenRouter returns. callback_url is not supported yet and answers 400; poll instead.

Find the models you can batch

In the catalog, batch is a service tier with its own endpoints and prices:
A model with no batch endpoint answers 404 on submit. Batch endpoints follow your compliance rules: if your rules exclude them, the batch is refused.

Data handling

Batch is not zero data retention. Your requests and results have to sit at rest while the job runs, and OpenAI’s and Anthropic’s batch APIs are outside their zero data retention programs.
  • At the provider. The provider holds the requests and results while the batch runs. Opper deletes the provider’s copy as soon as it has collected the results.
  • At Opper, input. Requests are staged until the provider accepts the job, then deleted.
  • At Opper, results. Kept for your project’s retention period after the batch ends, at most 30 days (24 hours if your project’s retention is 0), or until you DELETE the batch. results_expire_at on the batch object says when. After that, results answer 410 and the batch object keeps only its counts and cost (results_deleted: true).

Limits

Billing

A batch is billed once, when it finishes, at the batch endpoint’s price: about half the synchronous price. Each successful line is priced from its usage exactly like the synchronous call it stands for, and the batch is charged as one usage event. usage.cost on the batch object is the total. An expired or cancelled batch is billed only for the lines that finished. A batch that never started, or failed, is not charged. You need a positive balance to submit a batch, as for any call.
Last modified on October 27, 2026