On Anthropic's first-party API, the Message Batches API accepts a large set of requests, processes them asynchronously, and returns results at a discount. That endpoint is not supported on Amazon Bedrock. But be precise about what is missing: AWS offers its own batch inference feature for Bedrock — a different mechanism where you upload inputs in InvokeModel or Converse format to Amazon S3, Bedrock processes them asynchronously, and outputs are delivered back to S3, at a 50% discount compared to on-demand pricing. So "no batch options on Bedrock" is wrong; the accurate statement is that Anthropic's batch endpoint is absent and AWS's S3-based alternative exists in its place.
When AWS batch inference isn't enough
AWS batch inference is the first thing to evaluate, because the discount is real and the operational burden is low. But it has documented constraints: it does not support tool calling or structured output (response_format), it is not supported for provisioned models, and it runs only on the bedrock-runtime endpoint — not the newer bedrock-mantle endpoint that serves "Claude in Amazon Bedrock." If your workload uses tools, needs structured outputs, targets the current Messages-API surface, or needs per-item retry logic and progress visibility that S3-in/S3-out doesn't give you, a self-managed asynchronous pattern is the workaround. The trade-off is that you pay standard on-demand token rates rather than the batch discount.
The queue-and-worker pattern
The building blocks are boring, well-understood AWS services:
| Component | Role |
|---|---|
| Amazon SQS | Holds one message per work item; provides retries via visibility timeout and a dead-letter queue for items that repeatedly fail |
| AWS Lambda | Workers triggered by the queue; each invocation pulls a message, calls Claude, writes the result |
| Amazon DynamoDB | Job-tracking table: one record per item with status (queued, running, done, failed), timestamps, and a pointer to the output |
| Amazon S3 | Stores inputs and outputs too large for a queue message or a database record |
A submitter enqueues items and writes a queued record per item; workers process items and update status; a small status endpoint or query answers "how far along is job X?" by counting states in DynamoDB. The worker's core is a normal Bedrock call:
from anthropic import AnthropicBedrockMantle
client = AnthropicBedrockMantle(aws_region="us-east-1")
def handle_item(item): # called per SQS message
response = client.messages.create(
model="anthropic.claude-haiku-4-5-20251001-v1:0",
max_tokens=1024,
messages=[{"role": "user", "content": item["prompt"]}],
)
return response.content[0].text # write to S3/DynamoDB
Three details that make or break it
Concurrency must respect your quotas. Bedrock meters Claude by tokens per minute, and the bedrock-mantle endpoint reserves your input tokens plus max_tokens against the input quota at admission, returning a 429 when the reservation doesn't fit. Cap Lambda's reserved concurrency so your aggregate throughput stays under quota, and back off on throttling errors — SQS's visibility timeout gives you retry-with-delay almost for free. Note that bedrock-runtime and bedrock-mantle quotas are tracked separately even for the same model.
Make workers idempotent. SQS delivers at-least-once, so a worker may see the same item twice. Use a conditional DynamoDB update ("set to running only if still queued") or tolerate overwriting an identical result, so duplicates are harmless rather than double-billed downstream actions.
Set timeouts deliberately. Long generations can take minutes; AWS SDK clients default to a 1-minute read timeout, and AWS recommends raising it substantially for Claude workloads. Size the Lambda timeout and the SQS visibility timeout above your worst-case generation time, or long items will be redelivered while still in flight.
Where to go next
See handling Bedrock throttling for the retry mechanics, quota increase requests before you scale up, and the feature-gap overview for what else differs from the first-party API.