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 32 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:
The manual first, then the question. Only the question changes from call to call.
| 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¢ |
Click a call to see its prompt and where its cost goes.
Call 2: the prompt
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?
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?).
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”.
All 20 matched. Matching by custom_id works whatever order the results come back in.
| Ticket | Answer 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_controlmarkers 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_tokenson 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_idand match results by it; resubmit the ones with no result.
Caveats
- One small model, one day.
gpt-5.4-nanoon 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
- Is everything that changes per call at the end of the prompt?
- Are tool lists and JSON in a fixed order?
- Is the shared start over about 1,024 tokens? If not, don't count on caching.
- Do you log
cached_tokens, and alert when it drops? - Which jobs can wait a day? Send those as batches.
- Does every batch request have a
custom_id, and do you match and retry by it? - 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.
Quiz
1.What does prompt caching reuse?
2.Cached tokens cost a tenth of the price. Why did each cached call cost about half, not a tenth?
3.A timestamp at the very top of the system prompt, new on every call. What happened to caching?
4.A 60-token prompt, sent 15 times. How many calls were cached, and why?
5.When is the Batch API the right choice?
6.A batch's results came back in the same order as the requests. Can you match them by position?
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 34, semantic caching and cascades: reusing a whole answer for a question that means the same thing, and letting a cheaper model answer first.