Half price, if you can wait
The Batch API is a flat 50% off input and output with no reuse requirement. The only question is whether the work genuinely needs to be synchronous.
The Batch API is a 50% discount on both input and output tokens, in exchange for asynchronous processing. No reuse requirement, no prompt restructuring, no quality difference — the same models, at half price, if you can wait.
It is the largest unconditional saving available on inference, and it is routinely left on the table because the pipeline was written synchronously and nobody revisited whether it needed to be.
Most "real-time" AI features are real-time because that is how the first version was written, not because anybody is waiting.
The rates
At published Anthropic pricing, per million tokens:
| Model | Standard in/out | Batch in/out |
|---|---|---|
| Claude Haiku 4.5 | $1 / $5 | $0.50 / $2.50 |
| Claude Sonnet 5 | $2 / $10 | $1 / $5 |
| Claude Opus 5 | $5 / $25 | $2.50 / $12.50 |
The discount is flat across input and output, which makes the arithmetic trivial: whatever you spend now, halve it. Google and OpenAI offer comparable batch discounts on their own APIs.
Worked, on a backfill
Reprocessing an archive of 400,000 documents after a prompt change — 3,000 input and 600 output tokens each, on Sonnet 5:
input: 400,000 × 3,000 = 1,200,000,000 tokens = 1,200 MTok
output: 400,000 × 600 = 240,000,000 tokens = 240 MTok
standard: 1,200 × $2 + 240 × $10 = $2,400 + $2,400 = $4,800
batch: 1,200 × $1 + 240 × $5 = $1,200 + $1,200 = $2,400
$2,400 saved for changing which endpoint you post to. A backfill has no user waiting on it by definition, so there is no trade being made at all here — this is the clearest case there is.
It stacks with caching
The discounts multiply rather than compete. A batch job with a large shared prefix — the same instructions and schema across every document — gets both:
batch cache read = base input × 0.1 × 0.5 = 5% of standard input rate
For Sonnet 5 that is $0.10/MTok against a $2/MTok standard rate. If your batch has a substantial stable prefix, structure it so the prefix is cached and submit it as a batch. Order matters: batch first, because it applies unconditionally, then cache if the prefix is large enough to matter.
What you give up
Latency. Providers quote a completion target measured in hours, and in practice most batches finish well inside it. But it is a target, not a guarantee, and you cannot plan around a specific completion time.
Immediate failure visibility. A malformed request in a synchronous call fails in 200ms. In a batch it fails when the batch is processed, which might be hours after you submitted 50,000 of them. Validate requests locally before submitting — schema-check every one, and run a handful synchronously as a canary first.
Cancellation and inspection. Once submitted, a batch is largely opaque until it completes. A bug discovered ten minutes after submission is a bug you have already paid for.
Ordering. Results come back keyed by your custom id, not in submission order. This is fine and it does mean every request needs a stable id you can join on.
The questions that decide it
Not "is this user-facing?" but:
Is a human blocked on the result? A user staring at a spinner needs a synchronous call. A user who uploaded 200 invoices and expects an email when processing finishes does not — and that second experience is often better, since it does not require them to keep a tab open.
Would a delay change any decision? Nightly scoring, weekly digests, archive reprocessing, eval runs: no decision is made faster because these completed sooner.
Is it already asynchronous and just not batched? The common case. Work already sitting behind a queue, processed one API call at a time. It is asynchronous in every respect except the billing.
That last category is where the easiest money is. If your pipeline pulls jobs off a queue and calls the API per job, the only thing between you and half price is accumulating them into a batch.
Eval runs, specifically
Eval suites are the most overlooked batch candidate. They are large, they are completely insensitive to latency, and teams run them constantly during development.
A 500-case eval suite run twenty times during a week of prompt iteration is 10,000 calls that nobody is waiting on in any real sense. At half price. The usual objection is the feedback loop — you want the result in two minutes, not two hours — which is a fair argument for the tight iteration loop and not for the full regression suite that runs on every merge.
Run the fast subset synchronously while iterating. Run the full suite as a batch in CI. The nightly job does not care.
Getting started without rewriting anything
The migration is usually smaller than expected, because the request bodies are identical — only submission and collection change:
// Same request objects you already build. Only the envelope differs.
const batch = await client.messages.batches.create({
requests: documents.map((doc) => ({
custom_id: doc.id, // your join key, on the way back
params: buildRequest(doc), // unchanged
})),
})
// Later — a webhook, or a poll on a schedule.
for await (const result of client.messages.batches.results(batch.id)) {
if (result.result.type === "succeeded") {
await store(result.custom_id, result.result.message)
} else {
await queueForRetry(result.custom_id, result.result.error)
}
}
The per-result error branch is not optional. Individual requests within a batch can fail independently, and a loop that assumes success silently drops them.
More in when the model bill triples, prompt caching with the arithmetic, and which feature tripled the bill. Rates from Anthropic's published pricing, checked 27 August 2026.