ealeng

Episode 54 of 56 · Speed & cost

Prompt caching and the Batch API

Two ways to pay less for the same API calls, measured on gpt-5.4-nano. A long manual in front of every question: from the second call on, 1,792 of about 2,860 tokens came from the provider's cache and each call cost about half as much. One timestamp at the top of the prompt, and 0 of 15 calls were cached. Then 20 support tickets as one batch: done in 2.9 minutes, at half the list price, with categories that matched normal calls as often as normal calls matched each other.

Lab 12 · Inference & Serving11 min read
Video transcript7:59

0:00Cold open

0:01

A support assistant sends OpenAI’s model a long manual with every customer question.

0:08

The manual is the same every time. Only the question changes.

0:14

I sent five different questions in a row. The first call paid full price.

0:19

From the second call on, part of the manual was reused from the provider’s cache.

0:25

Each of those calls cost about half as much.

0:30

Then I added one line at the very top: the current time.

0:34

And nothing was cached. Not once in fifteen calls, over three runs.

0:41

One line of text, and the discount was gone. Why?

0:46Intro

0:47

Hands-on AI engineering: serving models, part five.

0:52

Today: two ways to pay less for the same API calls. Prompt caching, and the batch API.

0:59A few words first

1:01

I’m Lea, a machine learning engineer at an outdoor gear shop. First, a few words this video needs.

1:08

An API is how a program asks OpenAI’s model for an answer, over the internet.

1:15

We pay for every call.

1:18

The prompt is everything the program sends.

1:21

It starts with the system prompt: fixed instructions, sent every time. Here, that’s the manual.

1:28

Then comes the customer’s question.

1:32

Models read and write tokens. A token is a piece of a word.

1:37

You pay per token: input tokens for what you send, and output tokens for what comes back.

1:44

On this model, an output token costs about six times as much as an input token.

1:5101 · Prompt caching

1:53

So what is prompt caching? Think of a coffee shop.

1:57

The barista knows a regular’s usual order.

2:01

The moment the order starts the usual way, she starts making it.

2:06

If the order starts any other way, she starts from scratch.

2:11

Prompt caching is the same idea. The provider remembers the start of a prompt it just read, so it doesn’t have to read it again.

2:20

That saved work is the cache.

2:23

When the next prompt starts exactly the same way, that part is read from the cache.

2:29

On this model, cached tokens cost a tenth of the normal price.

2:34

It’s automatic. But it’s best-effort, so it can miss.

2:38

So check it. Every response says how many input tokens came from the cache.

2:4502 · Five calls

2:46

Now, real calls. I used OpenAI’s GPT-5.4-nano, on November second.

2:54

For this test, I used a sample assistant from a software company. Its manual is filler text.

3:01

I ran each caching test three times, to see how much the results vary.

3:07

Here are five calls from one run. On the first call, none of the prompt was cached.

3:14

From the second call on, part of each prompt came from the cache. And the cost per call fell by half.

3:21

Why only half, if cached tokens cost a tenth? Because only part of the prompt was cached.

3:28

Almost two-thirds of it, the same amount on every hit.

3:33

The rest of the prompt, and the answer, are still paid in full.

3:37

My runs don’t show why the cache stopped where it did.

3:42

And a cache can miss. Across the three runs, eleven of twelve repeat calls hit.

3:48

This one paid full price, even though its start was the same.

3:53

One more detail: my calls also sent a prompt cache key. OpenAI runs many machines, each with its own cache. This optional label sends calls with the same start to the same one.

4:07

So I check cached tokens. I don’t assume.

4:1203 · What breaks it

4:13

Now, the timestamp. Some apps put the current time at the top of the system prompt.

4:19

Here, the time goes down to the microsecond, so it’s new on every call.

4:24

A date that only changes once a day should hurt far less.

4:29

With the time at the start: zero of fifteen calls cached, in all three runs.

4:36

Back to the coffee shop. Every order now starts with something new, so the barista never recognises it.

4:44

Now the same timestamp, but at the end, after the manual. Will it be cached?

4:51

Yes. From the second or third call on, the same amount as before.

4:57

Why? The cache can only reuse a prompt’s start. Nothing after the first difference can be reused.

5:04

So the rule: stable parts first, changing parts last.

5:09

And watch for hidden changes at the top. For example, a list of tools, the actions your app lets the model call, sent in a different order each time. Then the start of the prompt changes too.

5:2304 · Too short

5:25

One more test. The same questions, but with one short line of rules instead of the manual.

5:32

That’s about sixty tokens.

5:35

Zero of fifteen calls cached.

5:38

Why? OpenAI’s docs say it only caches prompts of about a thousand and twenty-four tokens or more. This one is far below that.

5:48

So caching pays off for long starts that repeat: big instructions, tool lists, or a document you ask many questions about.

5:5805 · The batch API

6:00

The second discount is for work nobody is waiting on, like sorting yesterday’s support tickets.

6:07

A batch is many requests sent together, as one file.

6:11

Each line is one request, with its own name: a custom ID.

6:16

I sent twenty tickets as a batch. Each one was to be summed up and sorted into a category.

6:23

The batch finished in under three minutes.

6:27

But that’s not a promise. The only promise is: within twenty-four hours.

6:33

The reward: OpenAI charges half the list price for a batch, for the same tokens.

6:40

Are the answers the same as normal calls? Not word for word.

6:45

Their categories matched normal calls on eighteen of twenty.

6:51

But two normal runs matched each other just as often.

6:55

Language models vary from run to run, batch or not.

7:00

One catch: results can come back in any order. Mine came back in order this time, but OpenAI doesn’t promise it.

7:08

So match each result by its custom ID, never by its position.

7:1506 · What I do now

7:16

So here’s what I do now. In every prompt: stable parts first, changing parts last.

7:23

I log cached tokens on every call, because caching is best-effort.

7:28

And work that can wait, like nightly tests, goes out as a batch. Each result is matched by its custom ID.

7:37

Next time: reusing whole answers for questions that mean the same thing. And letting a cheaper model answer first.

7:45Summary

7:47

Prompt caching discounts a repeated start. One changing line at the top switches it off.

7:53

The batch API halves the price, for work that can wait.Lea: Thanks!

Key takeaways

  • ✓Prompt caching: when a new prompt starts exactly like one the provider just read, it reuses that start instead of reading it again. On gpt-5.4-nano, cached input tokens cost a tenth of the normal price ($0.02 against $0.20 per million). It's automatic but best-effort, so check `cached_tokens` in every response.
  • ✓Five questions behind the same ~2,860-token manual: the first call was never cached; from the second call on, every hit was exactly 1,792 tokens (63%), and the cost fell from 64.7¢ to about 32.4¢ per 1,000 calls. Only half, because the other 37% of the input and the whole answer are still paid in full. Across three runs, 11 of 12 repeat calls hit.
  • ✓The cache reuses a prompt's start, never past the first difference. A timestamp (to the microsecond) at the top: 0 of 15 calls cached, in all three runs. The same line at the end: cached from the second or third call. Stable parts first, changing parts last, and watch for hidden changes at the top, like a tool list in a different order each time.
  • ✓Short prompts aren't cached: a 60-token prompt got 0 of 15. OpenAI's docs say caching starts at about 1,024 tokens, so it pays off for long, repeated starts: big instructions, tool lists, a document you ask many questions about.
  • ✓The Batch API: many requests in one file, each with a `custom_id`, at half the list price, promised within 24 hours. 20 tickets finished in 2.9 minutes; 2.6¢ per 1,000 tickets at list price, 1.3¢ as a batch. Results can come back in any order, so match them by `custom_id`, never by position.
  • ✓Batch answers aren't word-for-word the same as normal calls, but neither are normal calls: categories matched normal calls on 18 of 20, and two rounds of normal calls matched each other on 18 of 20 too.

Many API calls repeat most of what they send. This chapter measures two ways to pay less for them: prompt caching, where the provider reuses a repeated start of the prompt, and the Batch API, half price for work that can wait. It also shows what quietly breaks the cache.

The source is Lab 12 (Inference & Serving), scripts 03_prompt_caching.py and 04_batch_api.py. The video reran the lab's own code (its prompt, questions, call settings and tickets) on 2 November 2026, with real calls to OpenAI's gpt-5.4-nano (snapshot gpt-5.4-nano-2026-03-17), and kept every call: three runs of each caching test, one batch of 20 tickets, and the same tickets twice as normal calls. All of it cost $0.025. The support assistant is a sample for a made-up software company, Ledgerly, and its "manual" is filler text standing in for a real one; costs are at list price. Chapter 53 covered streaming and slow calls; chapter 30 showed the same idea as prompt caching on your own server (prefix caching).

A few words first

  • The prompt is everything the app sends. It starts with the system prompt, fixed instructions sent every time (here, a manual that is nearly all of the ~2,860-token prompt), then the customer's question.
  • Tokens are pieces of words. You pay per token: input tokens for what you send, output tokens for what comes back.
  • On gpt-5.4-nano, per million tokens: input $0.20, cached input $0.02, output $1.25. An output token costs about six times an input token.

What prompt caching is

Think of a barista who knows a regular's usual. The moment the order starts the usual way, she starts making it. If it starts any other way, she starts from scratch.

The provider does the same: it remembers the start of a prompt it just read, so it doesn't have to read it again. When the next prompt starts exactly the same way, that part is read from the cache, and billed at a tenth of the price on this model. It's automatic, but best-effort: it can miss. Every response says how many input tokens came from the cache, in usage.prompt_tokens_details.cached_tokens, so you can check.

The lab's calls also send a prompt_cache_key. OpenAI runs many machines, each with its own cache; this optional label sends calls with the same start to the same one, which makes hits more likely. Each test series used a fresh prompt and key, so its first call always started cold.

Five calls, measured

Five different questions, each behind the same ~2,860-token prompt. Run 2 of 3:

Call Question Input tokens Cached Cost per 1,000 calls
1 How do I reset my password? 2,859 0 64.7¢
2 Can I get a refund after 30 days? 2,862 1,792 32.5¢
3 How do I add a teammate? 2,859 1,792 32.4¢
4 Why was my card declined? 2,858 1,792 32.4¢
5 How do I export my data? 2,859 1,792 32.4¢

From the second call on, each call cost about half as much. Why only half, if cached tokens cost a tenth? Because only part of the prompt was cached: 1,792 tokens, 63%, the same amount on every hit. The rest is paid in full:

Part of call 3 Tokens Cost per 1,000 calls
Cached input 1,792 3.6¢
The rest of the input 1,067 21.3¢
The answer 60 7.5¢
Total 32.4¢

The runs don't show why the cache stopped at 1,792 tokens. (The lab's notes say OpenAI's cache works in steps, the first 1,024 tokens and then 128 at a time; the recorded runs only show where it stopped.)

And a cache can miss. Across the three runs, 11 of 12 repeat calls hit. The one that didn't, call 3 of run 1, had the same start and paid full price. So check cached_tokens; don't assume.

Switch the prompt, the run, and the call:

Try it · Five calls, three runsLab 12 · script 03 · recorded gpt-5.4-nano calls, 2 Nov 2026
Prompt:
Run:

The manual first, then the question. Only the question changes from call to call.

CallQuestionInput tokensCachedCost per 1,000 calls
1How do I reset my password?2,859064.7¢
2Can I get a refund after 30 days?2,8621,79232.5¢
3How do I add a teammate?2,8591,79232.4¢
4Why was my card declined?2,8581,79232.4¢
5How do I export my data?2,8591,79232.4¢

Click a call to see its prompt and where its cost goes.

Call 2: the prompt

the manual (same every call)
question

Shaded: the 1,792 tokens read from the cache (63% of 2,862).

Call 2: cost per 1,000 calls like it

  • Cached input, 1,792 tokens3.6¢
  • The rest of the input, 1,070 tokens21.4¢
  • The answer, 60 tokens7.5¢
  • Total32.5¢

Prices on gpt-5.4-nano, per million tokens: input $0.20, cached input $0.02 (a tenth), output $1.25. A cached token is cheap, but the rest of the input and the answer are still paid in full, so a hit about halves the cost.

All three runs: did each call hit the cache?

Run 1misshitmisshithit
Run 2misshithithithit
Run 3misshithithithit
Calls 2–5 that hit, 3 runs
11 of 12
Every hit
1,792 tokens

Every first call starts cold (a fresh prompt and cache key per series). Every hit was exactly 1,792 tokens, about 63% of the prompt; the runs don't show why the cache stopped there.

What breaks it

Some apps put the current time at the top of the system prompt. In this test it's the exact time, down to the microsecond, so it's new on every call:

Where the time goes Calls 2–5 cached, 3 runs Every first call
Nowhere (same start) 11 of 12 not cached
At the start, before the manual 0 of 12 (0 of 15 counting first calls) not cached
At the end, after the manual 10 of 12, from the 2nd or 3rd call not cached

With the time first, every prompt is different from its first line, and the cache has nothing to reuse. Moved to the end, the long manual is the same start again, and calls were cached from the second or third one, the same 1,792 tokens as before. The cache can only reuse a prompt's start, never past the first difference.

So the rule is: stable parts first, changing parts last. Instructions, tool definitions and reference documents go first; the date, the user's details and the question go last. A date that changes once a day should hurt far less than a time that changes every call, but that wasn't tested here.

Watch for hidden changes at the top, too: a list of tools (the actions your app lets the model call) built in a different order each run, for example from a Python set, or JSON whose keys come out in a different order. The start changes, and the cache misses, with nothing in the code that looks like a timestamp.

Too short to cache

The same five questions with one short line of rules instead of the manual, 56–63 tokens: 0 of 15 calls cached. OpenAI's docs say it only caches prompts of about 1,024 tokens or more. (The lab didn't measure the cutoff itself.)

So caching pays off for long starts that repeat: big instructions, tool lists, or a document you ask many questions about. A short prompt is cheap anyway: 8.6–8.8¢ per 1,000 calls here.

The Batch API

The second discount is for work nobody is waiting on, like sorting yesterday's support tickets or a nightly test run. A batch is many requests sent together as one file, one request per line, each with its own name, a custom_id:

{"custom_id": "ticket-000", "method": "POST", "url": "/v1/chat/completions",
 "body": {"model": "gpt-5.4-nano", "reasoning_effort": "none", "max_completion_tokens": 60, "messages": [...]}}

Twenty support tickets went in, each to be summed up in at most 8 words and given a category (billing, bug, account, feature_request, security):

  • All 20 completed, none failed, in 2.9 minutes from creation to completion. That's not a promise: the only promise is within 24 hours.
  • Half the list price for the same tokens. This batch's tokens (1,039 in, 254 out) come to 2.6¢ per 1,000 tickets at list price, 1.3¢ as a batch. The half is OpenAI's documented batch price applied to these tokens, not a bill.
  • Results can come back in any order. These came back in order this time, but OpenAI doesn't promise it, so match each result by its custom_id, never by its position.

Are batch answers the same as normal calls? Not word for word. But neither are normal calls:

Compared Same category
Batch vs normal calls, round 1 18 of 20
Batch vs normal calls, round 2 18 of 20
Normal round 1 vs normal round 2 18 of 20

Word for word, the two rounds of normal calls matched on 0 of 20. Language models vary from run to run, batch or not. The disagreements were on tickets between two categories: slow search (bug or feature request?), a profile picture that won't change (bug or account?), switching to annual billing mid-cycle (billing or feature request?).

Try it · 20 tickets as one batchLab 12 · script 04 · recorded batch, 2 Nov 2026
Done
20 of 20
Took
2.9 min
Promised
24 h
Per 1,000 tickets
1.3¢

2.6¢ per 1,000 tickets at list price, 1.3¢ at OpenAI's batch price (half), for this batch's own tokens (1,039 in, 254 out). Minutes is from creation to completion; the only promise is “within 24 hours”.

Results came back:
Match each result:

All 20 matched. Matching by custom_id works whatever order the results come back in.

TicketAnswer it was given
ticket-000The dashboard returns a 500 error for everyone in our company since 9am.ticket-000Dashboard returns 500 error for all users · bug
ticket-001I was charged twice for March, $49 each time. Please refund one.ticket-001Charged twice in March; request refund · billing
ticket-002Could you add dark mode? Our designers keep asking.ticket-002Add dark mode to the app · feature request
ticket-003I accidentally pushed our API key to a public GitHub repo an hour ago.ticket-003Accidental public exposure of API key · security
ticket-004How do I add a new user to our workspace?ticket-004Request instructions to add new workspace user · account
ticket-005The CSV export puts dates in the wrong format, but I can fix them in Excel.ticket-005CSV export date format incorrect · bug
ticket-006My account is locked and I need to submit payroll today.ticket-006Account locked; urgent payroll submission needed · account
ticket-007We were billed $2,400 instead of $240 for our annual plan.ticket-007Overcharged: billed $2,400 instead of $240 · billing
ticket-008Search is slow, about 5 seconds per query, but it works.ticket-008Search performance is slow (~5s/query) · feature request
ticket-009After yesterday's sync, 3 months of project history disappeared.ticket-009Project history for three months disappeared after sync · bug
ticket-010Nobody on our team can upload files since your update.ticket-010Upload files broken after update · bug
ticket-011Can I switch from monthly to annual billing mid-cycle?ticket-011Mid-cycle switch from monthly to annual billing? · billing
ticket-012Please remove my former employee's access.ticket-012Former employee access removal request · account
ticket-013Your mobile app would be better with an offline mode.ticket-013Offline mode request for the mobile app · feature request
ticket-014I think someone logged into my account from another country.ticket-014Account access suspected from another country · security
ticket-015The editor crashes every time I open a document.ticket-015Editor crashes when opening any document · bug
ticket-016Refund request: I cancelled during the trial but was charged $15.ticket-016Refund request after trial cancellation charge · billing
ticket-017We need SSO with Okta by Friday or we'll cancel.ticket-017SSO with Okta required by Friday · feature request
ticket-018I can't change my profile picture. Not a big deal.ticket-018Unable to change profile picture · bug
ticket-019One of our admins typed their password into a fake login page.ticket-019Admin entered password into phishing page · security

Also in the lab

The lab's README and checklist answers record the lab's own earlier run of the same scripts, with slightly different counts:

  • Fewer calls per test. The scripts send 5 calls with the same start, and 3 each for the timestamp and short-prompt tests (the video's rerun used 5 everywhere, three times). In that run, too, 1,792 of about 2,860 tokens were cached from the second call; the timestamp at the end was cached from the third call; a 61-token prompt was never cached.
  • The batch. The lab's run finished in about 2.5 minutes, $0.00028 instead of $0.00057 for normal calls. The script estimates the normal price from the batch's own tokens and says "same answers" without checking; the video's rerun sent the tickets as normal calls twice to check, which is where 18 of 20 comes from.
  • Failures. A request in a batch can fail on its own. Look at the batch's error file and resubmit just the failed custom_ids (the practice below does the "which ones" part).
  • Other providers. The checklist answers note that Anthropic's caching needs explicit cache_control markers and charges extra to write the cache; the stable-first rule is the same. Not tested in the lab.

What to do

  • Order every prompt: stable parts first, changing parts last. Instructions, tools and documents, then the date, the user and the question.
  • Keep the stable part byte-for-byte identical: tools in a fixed order, JSON with sorted keys, no IDs or times at the top.
  • Log cached_tokens on every call. Caching is best-effort; a drop to zero is a sign something at the top started changing.
  • Don't expect caching on short prompts. Under about 1,024 tokens, there's nothing to cache.
  • Send work that can wait as a batch: nightly tests, labeling, re-scoring old conversations. Not anything a user is waiting on.
  • Give every batch request a custom_id and match results by it; resubmit the ones with no result.

Caveats

  • One small model, one day. gpt-5.4-nano on 2 November 2026, at list price. Other models have other cache prices and steps; Anthropic's caching works differently.
  • Filler text. The manual stands in for a real one. A real manual of the same length should cache the same way, but that wasn't tested.
  • Small samples. Three runs of five calls per test, one batch of 20. "11 of 12" means the cache usually hit, not that it hits 92% of the time.
  • A cache key helps. Every caching call sent a prompt_cache_key, which makes hits more likely. Without one, expect more misses.
  • Speed wasn't measured. Cached and uncached calls took similar, mixed times (cached 0.69–3.12 s); the lab doesn't claim caching made calls faster.
  • The batch price is OpenAI's documented discount applied to the tokens, not a billed amount, and 2.9 minutes was one batch on one day.

The checklist

  1. Is everything that changes per call at the end of the prompt?
  2. Are tool lists and JSON in a fixed order?
  3. Is the shared start over about 1,024 tokens? If not, don't count on caching.
  4. Do you log cached_tokens, and alert when it drops?
  5. Which jobs can wait a day? Send those as batches.
  6. Does every batch request have a custom_id, and do you match and retry by it?
  7. Did you compare batch answers with a second round of normal calls, not just one, before deciding they differ?

Practice: price a call, find the break, match a batch

Price a call with and without the cache, count hits after the first call, find how much of a prompt can be reused, and match a batch's results by custom_id. The tests use the recorded runs and expect the video's numbers: 64.7¢ and 32.4¢ per 1,000 calls, 11 of 12 hits, 0 with the time first and 10 with it last, 1.3¢ for the batch, and 18 of 20 three ways.

Try it · Practice in PythonRuns in your browser · ⌘/Ctrl + Enter to run
cache_practice.py69 lines

Quiz

Try it · Check your understanding0 of 7 answered
  1. 1.What does prompt caching reuse?

  2. 2.Cached tokens cost a tenth of the price. Why did each cached call cost about half, not a tenth?

  3. 3.A timestamp at the very top of the system prompt, new on every call. What happened to caching?

  4. 4.A 60-token prompt, sent 15 times. How many calls were cached, and why?

  5. 5.When is the Batch API the right choice?

  6. 6.A batch's results came back in the same order as the requests. Can you match them by position?

  7. 7.The batch's categories matched normal calls on 18 of 20 tickets. Does the batch give worse answers?

Check your understanding

Try each question before opening its answer.

How do you find out whether a call was cached?

Read cached_tokens in the response's usage (usage.prompt_tokens_details.cached_tokens). Caching is automatic but best-effort: 11 of 12 repeat calls hit in these runs, and the one that didn't had the same start.

Cached tokens cost 90% less. Why did a cached call cost only about 50% less?

Only 1,792 of about 2,860 input tokens were cached. The other 1,067 input tokens and the 60-token answer were paid in full, and output costs about six times as much as input: 3.6¢ + 21.3¢ + 7.5¢ = 32.4¢ per 1,000 calls, against 64.7¢.

Why does a timestamp at the top break caching, but not at the end?

The cache reuses a prompt's start, never past the first difference. A new time on the first line makes every prompt different from the start: 0 of 15 cached. At the end, the long manual is the same start on every call, and it was cached from the second or third call.

Your tool list is built from a Python set. Why could that silently break caching?

A set's order isn't fixed, so the tools can come out in a different order from run to run. They sit near the top of the prompt, so the start changes and the cache misses. Sort them, or keep them in a list.

When would you not use the Batch API?

When someone is waiting on the answer, or when each step needs the previous answer right away. The batch here took 2.9 minutes, but the only promise is within 24 hours.

Why match batch results by custom_id?

Results can come back in any order. These came back in order, but OpenAI doesn't promise it, and matching by position would give tickets each other's answers. The ID also shows which requests have no result, so you can resubmit just those.

The batch's categories matched normal calls on 18 of 20 tickets. Is the batch less accurate?

Nothing here says so. Two rounds of normal calls matched each other on 18 of 20 too, and word for word on 0 of 20. The model varies from run to run, batch or not.

Next up: Chapter 55, semantic caching and cascades: reusing a whole answer for a question that means the same thing, and letting a cheaper model answer first.

Learning something useful?

New labs, articles, and videos on evaluating and testing AI systems come out regularly. Follow along so you don't miss the next one.