Batch API¶
Run a large set of chat completion or embedding requests asynchronously, at a lower price than the synchronous API, through the OpenAI Batch API shape.
A batch is created from a file of requests, runs without a connection held open, and is read back from the result files it produces — exactly the OpenAI workflow, so the official OpenAI SDKs work by changing the base URL.
Why Choose the Batch API?¶
-
Lower Price per Token
Batched requests are billed at the published batch rate, well below the on-demand rate for the same model. -
Drop-in OpenAI Compatibility
client.batches.create(...)andclient.batches.retrieve(...)work unchanged, as does the JSONL input format. -
No Connection to Hold
Submit and walk away. Results stay readable through the Files API once the batch ends. -
Private AWS Backend
Requests and results are stored in your own S3 buckets — no traffic to third-party endpoints.
Available Endpoints¶
| Endpoint | Method | What It Does | MCP Tool |
|---|---|---|---|
/v1/batches |
POST |
Create a batch from an uploaded file | openai_batch |
/v1/batches |
GET |
List batches, newest first | openai_batch_list |
/v1/batches/{batch_id} |
GET |
Retrieve a batch and its counters | openai_batch_get |
/v1/batches/{batch_id}/cancel |
POST |
Cancel a batch that is still running | openai_batch_cancel |
Feature Compatibility¶
| Feature | Status | Notes |
|---|---|---|
| Creation | ||
input_file_id |
Must be uploaded with purpose="batch" |
|
endpoint |
/v1/chat/completions and /v1/embeddings |
|
completion_window |
24h, as upstream |
|
metadata |
Up to 16 key-value pairs, returned on every read | |
output_expires_after |
anchor: "created_at" and 1 hour to 30 days, counted from the moment the result files are written; omit it to keep them until deleted |
|
| Per-request body | ||
messages, max_tokens, sampling |
Same parameters as Chat Completions | |
response_format json_object |
Full support | |
tools / tool_choice / functions |
Refused when the batch is created — tool use is not available in a batch | |
response_format json_schema |
Refused when the batch is created | |
stream |
A batch has nothing to stream to | |
n above 1 |
Send one request per completion instead | |
prompt_cache_key / prompt_cache_breakpoint |
Accepted and ignored — a batch reads and writes no prompt cache, and the request is answered without one | |
input, dimensions |
Same parameters as Embeddings, one input per request |
|
encoding_format base64 |
Refused when the batch is created — batched vectors come back as numbers | |
| Lifecycle | ||
| Retrieve / poll | validating → in_progress → finalizing → completed |
|
| Cancel | cancelling then cancelled; requests already answered stay in output_file_id, and a batch that has ended is unchanged |
|
| List batches | Newest first, with an after cursor |
|
output_file_id / error_file_id |
Readable through the Files API | |
usage |
Token totals, reported once the batch ends | |
finalizing status |
Reported with finalizing_at while the results of a batch whose requests have run are being assembled; completed follows once they are readable |
Legend:
- Supported — Fully compatible with OpenAI API
- Partial — Supported with limitations
- Unsupported — Not available in this implementation
Content Guardrails and Batches
A request that a guardrail would apply to is refused rather than run unguarded. Send those requests without batching.
Prompt Caching and Batches
Batched requests neither read nor write a prompt cache, on any model. A request carrying a cache hint is still accepted and answered — the hint is dropped rather than the request — so a batch reports no cached tokens in usage.input_tokens_details. Nothing is lost by leaving the hint in: batched requests are already billed at the batch rate, and the cache discount was never available at that rate.
Model Support¶
Any chat or embedding model available for batch inference in your configured Amazon Bedrock regions can be used — the same identifiers as Chat Completions and Embeddings. To shortlist them, call search_models with route=openai_chat_completion&batch=true, or route=openai_embedding&batch=true for embeddings; each entry also carries a batch field.
The shortlist is a hint, not a rule
batch is advertised on a best-effort basis and never used to reject a request. A model it does not advertise — or says nothing about — may still run a batch, so submit the batch rather than ruling the model out; the answer you get back is the authoritative one.
A model that cannot serve batched requests is refused when the batch is created, naming the model. A model this deployment normally serves through another Amazon Bedrock endpoint is batched under the identifier the batch endpoint knows it by, so it needs nothing from you.
Workflow¶
1. Upload the requests¶
One JSON object per line, uploaded with purpose="batch". Every line names the same model, targets the same endpoint, carries a unique custom_id, and holds the request itself in body.
{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "amazon.nova-micro-v1:0", "messages": [{"role": "user", "content": "Summarize: ..."}]}}
{"custom_id": "req-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "amazon.nova-micro-v1:0", "messages": [{"role": "user", "content": "Summarize: ..."}]}}
Embedding requests are written the same way, against /v1/embeddings:
{"custom_id": "doc-1", "method": "POST", "url": "/v1/embeddings", "body": {"model": "amazon.titan-embed-text-v2:0", "input": "First passage of the corpus"}}
{"custom_id": "doc-2", "method": "POST", "url": "/v1/embeddings", "body": {"model": "amazon.titan-embed-text-v2:0", "input": "Second passage of the corpus"}}
Both samples above are abridged: a real file needs at least 100 requests, the minimum a batch carries.
from openai import OpenAI
client = OpenAI(base_url="https://your-host/v1", api_key="...")
requests_file = client.files.create(file=open("requests.jsonl", "rb"), purpose="batch")
2. Create the batch¶
batch = client.batches.create(
input_file_id=requests_file.id,
endpoint="/v1/chat/completions",
completion_window="24h",
)
Example request (curl):
curl -X POST "https://your-host/v1/batches" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_file_id": "file-06fvfg3lbdqarbad8kbo55g0sg5h3s4a",
"endpoint": "/v1/chat/completions",
"completion_window": "24h"
}'
Example response:
{
"id": "batch_06fvfg3lbdqarbad8kbo55g0sg5h3s4a",
"object": "batch",
"endpoint": "/v1/chat/completions",
"input_file_id": "file-06fvfg3lbdqarbad8kbo55g0sg5h3s4a",
"completion_window": "24h",
"status": "validating",
"created_at": 1786568013,
"expires_at": 1786654413,
"request_counts": {"total": 100, "completed": 0, "failed": 0},
"model": "amazon.nova-micro-v1:0"
}
3. Poll until it ends¶
batch = client.batches.retrieve(batch.id)
print(batch.status, batch.request_counts)
4. Read the results¶
results = client.files.content(batch.output_file_id).text
Each line pairs a custom_id with the completion it produced:
{"id": "batch_req_9f2c...", "custom_id": "req-1", "response": {"status_code": 200, "request_id": "batch_req_9f2c...", "body": {"id": "chatcmpl-req-1", "object": "chat.completion", "choices": [{"index": 0, "message": {"role": "assistant", "content": "..."}, "finish_reason": "stop"}], "usage": {"prompt_tokens": 22, "completion_tokens": 9, "total_tokens": 31}}}, "error": null}
Requests that failed are collected in a separate file, named by error_file_id.
Expiring the result files
Result files are kept until deleted, and are billed as stored objects
meanwhile. Pass output_expires_after when creating the batch to have both
files expire on their own — between 1 hour and 30 days, counted from the
moment they are written:
batch = client.batches.create(
input_file_id=requests_file.id,
endpoint="/v1/chat/completions",
completion_window="24h",
output_expires_after={"anchor": "created_at", "seconds": 7 * 24 * 3600},
)
Results Are Not in Request Order
Output lines may come back in any order, as upstream also warns. Match a result to its request with custom_id, never with the line number.
Limits¶
| Limit | Value |
|---|---|
| Minimum requests per batch | 100 (default quota) |
| Maximum requests per batch | 50,000 |
| Maximum input file size | 200 MB |
custom_id length |
64 characters |
| Distinct models per input file | 1 (upstream rule) |
| Processing window | 24 hours from creation |
A batch below the minimum, or past any of these caps, is refused when it is created and the message names the shortfall, rather than accepted and failed later.
The 100-request minimum is a quota default
100 is the default of the Amazon Bedrock quota Minimum number of records per batch inference job, which is set per model and adjustable for some of them — see Amazon Bedrock quotas. The gateway checks against that default, not against your account's own value, so a raised quota is enforced by Amazon Bedrock rather than here — a batch of 150 clears this check and is then refused by the backend — and a lowered one is not usable: fewer than 100 requests is still refused here.
Prerequisites¶
The Batch API is disabled until the deployment declares an AWS IAM service role that Amazon Bedrock assumes to read the requests and write the results:
AWS_BEDROCK_BATCH_ROLE_ARN— the service role.AWS_S3_BUCKET— the bucket holding the batch data.AWS_S3_BATCHES_PREFIX— the prefix it is stored under.
The permissions the role and the server need are listed in IAM Permissions.
While the role is unset, every batch endpoint answers 503.
Billing¶
Batched requests are billed at the published batch rate for the model, roughly half the on-demand rate. Usage is recorded once, when the batch ends. See Cost Management.
See Also¶
- Files API — upload the requests, download the results
- Chat Completions API — the per-request body
- Embeddings API — the per-request body of an embeddings batch
- Message Batches API — the Anthropic-shaped equivalent
- Configuration — enabling batches