← All posts

Engineering

Batch your AI calls, halve the bill.

Manpreet SinghHead of AI7 min readIntermediate
Share
On this page
  1. 01Why bulk jobs run through the same door as a live chat
  2. 02What actually changes: submit the whole job, come back later
  3. 03Build one: tag last night's ticket queue while everyone's asleep
  4. 04Two things that break the first time you try it
  5. 05Where it's the wrong tool

Somewhere in your company, a script is probably running a for loop against an LLM API right now — one ticket, one review, one document at a time, waiting for each reply before it sends the next. It's tagging four thousand support tickets overnight, or writing product descriptions for a catalog nobody will read until tomorrow. Around ticket eighteen hundred it hits a rate limit, the script dies, someone reruns it from the top the next morning, and the bill for all those calls — including the failed half — lands at full price. None of that had to happen.

Why bulk jobs run through the same door as a live chat

The API endpoint everyone learns first is built for a conversation: send one message, wait a few seconds, get one reply. It's the example in every tutorial, so it's also what people reach for when the real job is "classify four thousand tickets by tomorrow morning" — a job where nobody is watching a spinner. Run through the conversational endpoint, that job pays the same per-token price as a live chat, and competes for the same requests-per-minute limit as one, even though nothing about it needs an answer inside three seconds.

What actually changes: submit the whole job, come back later

Anthropic's Message Batches API takes a different shape. Instead of one request in, one reply out, you submit up to 100,000 requests in a single call (or 256 MB of them, whichever limit you hit first), and the system works through them asynchronously — each one independently, not in a queue behind the others. Most batches finish within an hour; every batch is guaranteed a result, or an expiry, within 24 hours. Every request in the batch carries a custom_id you invent, so you can match a result back to the ticket, review, or document it belongs to. All of it is charged at 50% of the normal per-token price, on every model Anthropic currently offers, for both the prompt and the reply — and requests that error out, get canceled, or expire before the model ever runs aren't billed at all. A crashed job doesn't cost you the crash.

Build one: tag last night's ticket queue while everyone's asleep

Take that ticket-tagging job. Instead of a for loop calling the model once per ticket, submit all of them as one batch.

scripts/classify-tickets.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const tickets = await loadUnprocessedTickets(); // however you pull today's queue

const batch = await client.messages.batches.create({
  requests: tickets.map((ticket) => ({
    custom_id: ticket.id,
    params: {
      model: "claude-sonnet-5",
      max_tokens: 20,
      system:
        "Classify this support ticket into exactly one category: billing, bug, feature-request, or account-access. Reply with only the category.",
      messages: [{ role: "user", content: ticket.body }],
    },
  })),
});

console.log(`Submitted ${tickets.length} tickets as batch ${batch.id}`);

Then, separately — in a scheduled job an hour later, not in the same script waiting on the line — check on it and collect what came back.

scripts/collect-ticket-tags.ts
const batchId = "msgbatch_..."; // logged by the script above

let status = await client.messages.batches.retrieve(batchId);
while (status.processing_status !== "ended") {
  await new Promise((resolve) => setTimeout(resolve, 60_000));
  status = await client.messages.batches.retrieve(batchId);
}

for await (const result of await client.messages.batches.results(batchId)) {
  if (result.result.type === "succeeded") {
    const category = result.result.message.content[0].text.trim();
    await tagTicket(result.custom_id, category);
  }
}

Two things that break the first time you try it

  • Results come back in any order, one line per request in a single file — always match a result to its ticket with custom_id, never by counting lines.
  • A batch can end with some requests marked expired if demand across Anthropic's system is heavy and the 24-hour window runs out before your slot comes up. A job with a real deadline needs a buffer, not a submission five minutes before it.
  • max_tokens: 0, the setting used to pre-warm a prompt cache, isn't allowed inside a batch — a cache entry written mid-batch would likely expire before your next request could use it anyway.

Common mistake

Teams treat the batch discount and prompt caching as alternatives — pick one. They stack. Batch a job that also reuses a large shared system prompt across every request, and the two discounts compound. Anthropic reports cache hit rates inside batches anywhere from 30% to 98%, depending on how steady the request stream is — best-effort, not guaranteed, since batch requests run concurrently rather than back-to-back.

Where it's the wrong tool

None of this belongs anywhere a person is waiting on the reply — a support chat widget, an in-editor autocomplete, an agent step blocking the next step of its own pipeline. Batch is for the request that was always going to run overnight or over a lunch break, not for anything currently rendering a spinner in front of a human. It's also not worth the extra moving parts — the batch object, the poll, the results file — for a few dozen calls; loop those synchronously and move on. The question changes the moment the same bulk shape starts repeating: nightly ticket triage, a monthly catalog refresh, a weekly evaluation run against your own test set. That's where the discount and the higher throughput earn the extra code.

Nobody would design a system where a background job competes with live customers for the same request queue and pays the same price for the privilege. Most teams have exactly that system today, because the synchronous endpoint is the one everybody learns first.
Manpreet Singh, Head of AI

The for loop still works. That's the trap — nothing about it throws an error or shows up in a code review as wrong. It just quietly costs twice what the same job costs one API call away, and nobody notices until someone finally reads the pricing page instead of the quickstart. Most of the bulk work companies now hand to a model — tagging, summarising, evaluating, screening — was never a conversation to begin with. It shouldn't be billed like one.

Whether a workflow you're running today is one of those overnight-shaped jobs quietly paying live-chat prices is exactly the kind of thing we look at in an AI Opportunity Assessment.

Next step

Want this built into your business, not just explained?

Our AI Opportunity Assessment maps where AI saves you time and money, and prices the build — $199, a written report, 7–10 days. If the answer is that AI is not worth it for you yet, we will say so in writing.