GPT-5.6 Luna costs ~96% more than GPT-5.4 mini in a controlled multi-turn Responses API test

We wanted to migrate from GPT-5.4 mini to GPT-5.6 Luna. Based on the published prices, we expected a roughly proportional cost increase, but our measured cost per conversation is nearly double.

We reran our tests after the recently acknowledged GPT-5.6 accounting fix.

Setup:

  • Responses API, OpenAI Node SDK 6.46
  • Six realistic sequential turns
  • Turn 1 fresh; turns 2–6 use previous_response_id
  • One discarded warm-up, then five measured runs per configuration
  • reasoning.effort: low; reasoning.context omitted
  • No retries or cache-accounting overlap

Mean per six-turn conversation:

Model Input Cache read Cache write Output Estimated cost
GPT-5.4 mini 69,105 34,765 0 1,043 $0.0332
GPT-5.6 Luna 67,029 20,041 46,958 736 $0.0651

Luna used 3% fewer input tokens and 29% fewer output tokens, but cost 96% more because approximately 70% of its input was reported as cache writes.

We calculated Luna’s cost as:

ordinary input × $1.00/M + cache reads × $0.10/M + cache writes × $1.25/M + output × $6.00/M

Here, ordinary input is input_tokens - cached_tokens - cache_write_tokens.

Are we interpreting and pricing these usage fields correctly? If so, is this expected cache behavior when using previous_response_id? Is there a recommended configuration—particularly regarding prompt_cache_key—that avoids rewriting most of the context on each turn?

Since you are using previous response ID, where you would have these past call IDs stored (or at least reviewable in the platform site), you might go back to the “usage” and see and report on, granularly:

Of the “chat growth” turns:

  • How many cache hits, with “cached_tokens” in input details;
  • Were the remaining “write” all for the size of new messages and not much more?

What I expect of the API from the descriptions offered:

  • if you get a cache miss, that would also imply a new complete “write” store.
  • a cache hit should not have a write that has any overlap on the cached amount.

Thus, all the input tokens can be accountable in details, where details must merely document them:

  • “cached_tokens” - the amount costing 0.10
  • “cache_write_tokens” - the amount costing 1.25
  • a small remainder of tokens not stored or matching the stored cache.

We already know that all new input will be billed 25% higher than advertised.

  • But then, a much higher penalty can be inferred upon cache failure and the complete input again being “cache_write_tokens”. Something that cost 0 before.

prompt_cache_key per-conversation is being encouraged on implicit cache.
Also suggested is to evaluate quality at one reasoning effort step lower than you were using.

Luna does have an increased price also: $0.75->$1.00/ $4.50->$6.00 – and you might as well write $0.75->$1.25

Thanks — we have the per-turn data and it confirms the pattern you described.

Representative six-turn chain, shown as input / cache read / cache write:

  • Turn 1, fresh: 10,151 / 6,274 / 3,868
  • Turn 2, previous_response_id: 18,002 / 11,361 / 6,632
  • Turn 3: 8,503 / 0 / 8,500
  • Turn 4: 8,657 / 0 / 8,654
  • Turn 5: 8,868 / 0 / 8,865
  • Turn 6: 9,250 / 0 / 9,247

So after turn two, nearly the complete input is repeatedly billed as a cache write—not just the new message.

We also reconciled 26 Luna runs against the Admin Costs API: our estimate was $1.879371 and OpenAI billed $1.879372, so the pricing interpretation appears correct.

We used a stable key per test arm, not per conversation. Testing a separate key per conversation is the remaining useful experiment. We also tested reasoning.effort: none; it increased cost by 10.3% versus low.

Have you seen Luna retain cache reads after turn two when using a per-conversation key?

It appears you are not growing a conversation at turn 3 and after based on #1 or #2. The input is smaller.

  • The new cache method does not consider partial matches, and is degraded from that prior behavior with “memory” on gpt 5.4 and before.

Turn 3 might be seen as a “new chat” with its complete store “write”, but then the remainder of turns would need to be building on that. If indeed the turns 4-6 are you continuing chatting with unaltered earlier context, then the cache miss on 4 and after is a failure of the API if within the 30+ minute promise.

e.g. You made something where the complete larger size of input from turn 1 or turn 2 cannot be matched = no cache hit whatsoever.

The way that you might stop some of the mandatory overbilling on this convoluted scheme is to insert an explicit cache breakpoint on only the first developer message - or a repeated message branch point which is over 1024 tokens.

In implicit mode, the breakpoint on the latest message uses one write slot, so up to the latest three explicit breakpoints can be written.

This is ambiguous, though. Does it mean that even though you set “explicit” on a turn, the remainder up to the end is a total “implicit” and you only cost yourself more with an additional write? "prompt_cache_options": {"mode": "explicit"} would seem to turn writing off unless you use “explicit then on turns”. You can use “explicit” on multiple turns - Can multiple explicit stores again send the value of “cache_write_tokens” above the actual “input_tokens”?

This would be what you want to do also on a call you do not intend for “chat”: set “explicit” early where it cannot be cached and doesn’t produce a cache write - and the API will behave in some manner and actually stop the overbilling of cache you will never use. A “foundation” cache write (or simply a minimal-cost mostly-off switch) might look like

{
  "model": "gpt-5.6-luna",
  "prompt_cache_key": "system_message_12_only",
  "prompt_cache_options": {
    "mode": "explicit",
    "ttl": "30m",
  },
  "input": [
    {
      "type": "message",
      "role": "developer",
      "content": [
        {
          "type": "input_text",
          "text": "You are ChatPro. Follow these instructions:\n\n{prompt}",
          "prompt_cache_breakpoint": {
            "mode": "explicit"
          }
        },
        {
          "type": "input_text",
          "text": "Produce a summary of this text pasted below..."
        }
      ]
    }
  ]
}

I might experiment a bit. Because otherwise, this whole scheme is anti-developer, who does not “chat” but produces on-demand 0-shot language products.

Commentary: the whole billing scheme is extremely stupid. Retrieving data back into a GPU was a 90% cost savings for us and OpenAI, but moving it out to GPU-local memory programmatically without inference cost is billed so much more. I don’t think so.


(Note: Also see vibe-coded “cache” documentation where the sympomatic AI models now cannot understand the purpose or speakers, where a user message in an example is the user saying, “Answer the current question.”), and they also use a model id “gpt-5.6” not offered on the models endpoint.

Thank you — this was helpful, particularly the explicit-breakpoint suggestion.

One clarification on the turn sizes: those rows aggregate every model iteration within each user turn, rather than showing one request’s context size. Turns 1–2 each made three model iterations because of tool calls; turns 3–6 made one. We verified that turns 2–6 each supplied the preceding previous_response_id, and Luna reported reasoning.context: all_turns.

We have now tested the configuration you suggested. We used prompt_cache_options.mode: explicit with one explicit breakpoint on the initial developer input block, then compared three six-turn chains sharing one arm key against three chains with a unique key per conversation:

  • Shared key: 35,145 mean cache-write tokens; $0.075325 per chain
  • Per-conversation key: 35,192 mean cache-write tokens; $0.076085 per chain

Cache writes changed by only 0.1%, and turns 3–6 still had zero cache reads except for one additional within-turn model iteration. The Admin Usage API exactly matched our token totals, and billed cost was $0.45423070 versus our $0.454231 estimate.

So neither conversation-scoped keys nor a single stable explicit breakpoint restored later-turn cache reads. We may still be missing a placement detail, but this now seems more likely to be provider behavior than conversation construction.

Is there another request field or breakpoint placement you would test, or does this look like something OpenAI should investigate?

Tool calls and responses also must be continuously replayed to maintain a “chat” session. That even means internal self-hosted tool calls. Which is problematic with self-management and a context unoffered for replay such as web search.

previous_response_id should be doing that for you, but it is also as likely to be technical debt not yet adapted, just as conversation ID was a very long time to even make it functional.

So, a re-interpretation of your chat might be something like:
(input / cache read / cache write) - individual internal turns

  1. sending 2000, 2 internal tool calls: (10,151 / 6,274 / 3,868) final
  • (2000/0/2000) +
  • (3000/3000/1000) +
  • (4800/3000/1800)…

I quickly realized that presenting you hypotheticals instead was a problem and that we can ask gpt-5.6 pro to solve.

Combined sequence

Turn 1
  (3,003 /     0 / 3,000)
+ (3,277 / 3,000 /   274)
+ (3,871 / 3,274 /   594)
= (10,151 / 6,274 / 3,868)

Turn 2
  (4,496 / 3,868 /   625)
+ (6,503 / 4,493 / 2,007)
+ (7,003 / 3,000 / 4,000)
= (18,002 / 11,361 / 6,632)

Turn 3
  (8,503 / 0 / 8,500)
= (8,503 / 0 / 8,500)

Combined totals:

Total input:  10,151 + 18,002 + 8,503 = 36,656
Cache read:    6,274 + 11,361 +     0 = 17,635
Cache write:   3,868 +  6,632 + 8,500 = 19,000
Other input:       9 +      9 +     3 =     21

Of the 36,635 cache-accounted token appearances:

  • 17,635, or 48.14%, were retrieved from cache.
  • 19,000, or 51.86%, were written to cache.

Diagnostic conclusion

Turn Cache-miss finding
Turn 1 No miss is required. A cold initial write followed by two complete hits explains the report exactly.
Turn 2 At least one miss is mathematically required under a growing, noncompacted prompt model. At least 1,513 tokens must represent rewriting rather than genuinely new growth.
Turn 3 A complete miss is required. Zero tokens were read and the entire 8,500-token cacheable prompt was written as a new entry.

Thus, under this baseline:

  • 10,500 token appearances were shifted from discounted cache reads into cache writes.
  • Actual read share was 48.14%, versus a reuse-perfect 76.80%.

Conclusion: The API management and the cache mechanism is underperforming and billing you for its failures if you did nothing other than input new messages.


Creative self-management solutions to drop will now run up against this cache write policy as well, as well as I expect any automatic server-side compaction would be impactful on matches.

Thank you, this breakdown is extremely helpful. It exposes something our turn-level rollups obscured: caching sometimes works within the tool loop, but matching partially or completely resets later. The 10,500-token shift you identified represents about $0.0121 of additional cost at Luna’s cache-write versus cache-read rates.

For context, these requests used the same prompt_cache_key, supplied previous_response_id on every continued turn, stayed below 15 requests/minute per key, and completed well within the 30-minute TTL. OpenAI’s Admin Costs API also matched the reported billing. Testing a separate key per conversation and explicit caching did not restore the missing later-turn reads.

We agree this points toward cache matching/continuation underperforming rather than an error in our cost calculation. We plan to capture per-provider-call response IDs and usage to identify the exact transition where reuse fails.

One question: do you believe manually replaying tool calls and results should be necessary even when using previous_response_id, or are you suggesting that as an experiment to isolate a possible Responses API implementation issue?

Thanks again for taking the time to analyze this so carefully.

The response ID should be furnishing EVERYTHING previously sent as input.

Using a conversation ID as the server-side chat persistence mechanism shall also do the same.

Input parameter would only be what is new: a user message string, or it can take the full form of input message as you’ll likely want to code to anticipate image and file inputs.

You could simulate tool call and response that never happened just as you can send them for self-management, but internal tool calls and even your returned developer function responses should all be internal to OpenAI’s chat storage and replayed before “input”.

Sending a new user input is the most likely case to not receive a hit, by routing-to-server failure or over-subscription, but iterative internal tool calling cache hits must almost be a guarantee OpenAI can offer.

Thank you, Jay. Your explanation led us directly to the cause.

We ran a paired six-turn diagnostic using only new input on each request. With stable top-level instructions, previous_response_id achieved cache hits on 15/15 later user turns and 18/18 tool-loop continuations. The Conversations API produced the same result.

We then repeated the test while rebuilding a growing chat snapshot inside instructions. Both state mechanisms fell to 0/15 user-turn hits while retaining 18/18 tool-loop hits.

That isolates the problem: Responses was replaying the prior state correctly, including tool activity. Our application was invalidating the reusable prefix by putting mutable chat context in top-level instructions.

We changed the production path to keep instructions stable and append the current database-backed context as a developer input item. In the controlled GPT-5.4 mini comparison, the cache-read rate improved from 50.5% to 75.1% and estimated cost fell 37%. Three repeat runs averaged 73.7% cache reads and remained 35.9% cheaper than the old layout.

After applying the same strategy and bounding retrieval, Luna’s measured cost premium over GPT-5.4 mini fell from the misleading 96% result to 16.7%, while Luna was 9.9% faster end to end and scored 4.80 versus 4.28 in our blinded quality review.

Your guidance materially changed our diagnosis and production implementation. Thank you for taking the time to help us work through it.