<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrey Altrouter</title>
    <description>The latest articles on DEV Community by Andrey Altrouter (@altrouter).</description>
    <link>https://dev.to/altrouter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4074301%2F0600ae16-ff26-459e-8ddd-ace73be1a5a1.png</url>
      <title>DEV Community: Andrey Altrouter</title>
      <link>https://dev.to/altrouter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/altrouter"/>
    <language>en</language>
    <item>
      <title>You typed 6,000 tokens and got billed for 282,000</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Fri, 11 Sep 2026 06:03:44 +0000</pubDate>
      <link>https://dev.to/altrouter/you-typed-6000-tokens-and-got-billed-for-282000-2l42</link>
      <guid>https://dev.to/altrouter/you-typed-6000-tokens-and-got-billed-for-282000-2l42</guid>
      <description>&lt;p&gt;The first LLM bill that surprises you is almost never the one for a big job. It's the one for a chat feature that "barely gets used."&lt;/p&gt;

&lt;p&gt;The reason is a single line most of us write on autopilot: &lt;code&gt;messages.append(...)&lt;/code&gt;. It looks like you're adding one message. You're actually re-buying every message that came before it.&lt;/p&gt;

&lt;p&gt;Two words first, since the whole argument lives in them. A &lt;strong&gt;token&lt;/strong&gt; is roughly ¾ of a word; models bill per million of them, at two different prices — &lt;strong&gt;input tokens&lt;/strong&gt; (everything you send) and &lt;strong&gt;output tokens&lt;/strong&gt; (what the model writes back), with output usually 5–6× the price of input. And the API is &lt;strong&gt;stateless&lt;/strong&gt;: it remembers nothing between calls. The "memory" in your chat bot is you, resending the entire transcript on every single turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cost grows with the square of the turn count
&lt;/h3&gt;

&lt;p&gt;Say a system prompt of 500 tokens, user messages of 200, and replies of 400. On turn &lt;code&gt;k&lt;/code&gt;, what you send is the system prompt, plus everything already said, plus the new question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input(k) = 500 + 600·(k − 1) + 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;600·(k − 1)&lt;/code&gt; is the killer. It grows every turn, and you pay it &lt;em&gt;again&lt;/em&gt; every turn. Sum it across a conversation of &lt;code&gt;n&lt;/code&gt; turns and the total input is roughly &lt;code&gt;300·n²&lt;/code&gt; — quadratic, not linear.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Turns&lt;/th&gt;
&lt;th&gt;Input tokens billed&lt;/th&gt;
&lt;th&gt;Output tokens billed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;34,000&lt;/td&gt;
&lt;td&gt;4,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;282,000&lt;/td&gt;
&lt;td&gt;12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1,104,000&lt;/td&gt;
&lt;td&gt;24,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Doubling the conversation from 30 turns to 60 didn't double the cost. It nearly quadrupled it. And the user in that 30-turn conversation typed 6,000 tokens of questions — you were billed for &lt;strong&gt;282,000&lt;/strong&gt;, a factor of 47.&lt;/p&gt;

&lt;h3&gt;
  
  
  What that costs in actual dollars
&lt;/h3&gt;

&lt;p&gt;Numbers as of 2026-09-03, on &lt;code&gt;claude-sonnet-5&lt;/code&gt; at Anthropic's list price of $2.00 per 1M input and $10.00 per 1M output.&lt;/p&gt;

&lt;p&gt;One 30-turn conversation: 282,000 input = $0.564, 12,000 output = $0.12. &lt;strong&gt;$0.68 a conversation&lt;/strong&gt; — and 82% of it is history you already paid for.&lt;/p&gt;

&lt;p&gt;Ten thousand of those a month is $6,840. That's the bill people describe as "coming out of nowhere," because the intuitive estimate — count the questions and answers, 18,000 tokens a conversation — lands at about $0.09. Off by 7×.&lt;/p&gt;

&lt;p&gt;Note which side the money is on. Everyone tunes &lt;code&gt;max_tokens&lt;/code&gt; and trims the model's replies; output is $0.12 of that $0.68. The bill is in the input, and the input is your own transcript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cap the history and the curve goes flat
&lt;/h3&gt;

&lt;p&gt;The fix is a &lt;strong&gt;sliding window&lt;/strong&gt;: keep the system prompt, keep the last N exchanges, drop the rest. Once the window is full, &lt;code&gt;input(k)&lt;/code&gt; stops growing, and quadratic becomes linear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SYSTEM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keep_turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;System prompt + the last N user/assistant pairs.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SYSTEM&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keep_turns&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

&lt;span class="c1"&gt;# Before sending, see what you're actually paying for:
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;
&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input tokens this turn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the same 30-turn conversation, an 8-turn window bills 143,400 input tokens instead of 282,000 — &lt;strong&gt;49% off, from four lines of code.&lt;/strong&gt; Print that number on every request for one afternoon and you will know whether you have this problem, which is more than most teams can say.&lt;/p&gt;

&lt;p&gt;If the tail of the conversation genuinely matters, summarize it instead of dropping it: fold turns 1–20 into a 150-token summary with a cheap model and prepend that. Same shape, less amnesia.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't fix
&lt;/h3&gt;

&lt;p&gt;A window shrinks the token count. It does nothing to the other multiplier — the price per token — and your bill is the product of both. That's the case for a gateway: &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt; resells the same models 10–25% below the vendors' own list prices (&lt;code&gt;claude-sonnet-5&lt;/code&gt; at $1.69 per 1M input against Anthropic's $2.00), over the OpenAI-compatible API, so it's a &lt;code&gt;base_url&lt;/code&gt; change. The 30-turn conversation above comes to $0.58 instead of $0.68. Its honest gap on this exact topic: our usage log stores prompt tokens per &lt;em&gt;request&lt;/em&gt;, with no conversation id, so it can't point at the conversation that went quadratic. That grouping has to happen in your app.&lt;/p&gt;

&lt;p&gt;Prompt caching is the other half of the answer, and it works well here — the growing prefix is stable — but it has its own break-even point and is not automatic.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one number to take away
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Input tokens in a chat scale as n², and the user's typing is a rounding error inside them.&lt;/strong&gt; Before you optimize anything else, log the input-token count of every request and sort descending. The top of that list is your bill.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>622 of our 5,087 LLM API calls never returned an answer</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Fri, 11 Sep 2026 06:01:29 +0000</pubDate>
      <link>https://dev.to/altrouter/622-of-our-5087-llm-api-calls-never-returned-an-answer-ale</link>
      <guid>https://dev.to/altrouter/622-of-our-5087-llm-api-calls-never-returned-an-answer-ale</guid>
      <description>&lt;p&gt;Every tutorial about LLM pricing counts calls. You send a request, you get an answer, you multiply. The arithmetic is clean because it assumes something that isn't true: that a call returns an answer.&lt;/p&gt;

&lt;p&gt;Between 28 June and 10 September 2026 our gateway logged 5,087 chat completions. 622 of them — 12.2% — ended with no answer for the caller. That is not an outage. That is the normal weather of a production LLM client, and nobody puts it in the cost model.&lt;/p&gt;

&lt;p&gt;Two words before the numbers. A &lt;strong&gt;token&lt;/strong&gt; is roughly ¾ of a word; you are billed per million, separately for &lt;strong&gt;input&lt;/strong&gt; (what you send) and &lt;strong&gt;output&lt;/strong&gt; (what the model writes back). And every failure arrives as an &lt;strong&gt;HTTP status code&lt;/strong&gt; — a three-digit number where 4xx means "your request was wrong" and 5xx means "something on our side broke". That distinction turns out to be the whole article.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually fails
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Share of all calls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no provider available&lt;/td&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;6.88%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;invalid request&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;2.40%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client closed connection&lt;/td&gt;
&lt;td&gt;499&lt;/td&gt;
&lt;td&gt;1.51%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;timeout&lt;/td&gt;
&lt;td&gt;504&lt;/td&gt;
&lt;td&gt;0.94%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;out of credit&lt;/td&gt;
&lt;td&gt;402&lt;/td&gt;
&lt;td&gt;0.28%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest slice isn't your code. It's 503 — the upstream vendor had no capacity for that model at that second. The second biggest &lt;em&gt;is&lt;/em&gt; your code: a malformed request, an unsupported parameter, a role the endpoint doesn't accept.&lt;/p&gt;

&lt;p&gt;That split matters because most retry wrappers don't make it. The default shape everyone copies — catch the exception, sleep, try again, three times — treats a 400 exactly like a 503. A 503 has a real chance of succeeding on the next attempt. A 400 will fail identically until the heat death of the universe. Of our 622 failures, 137 were terminal in that way: 400, 402, 404. Retried three times each, that is 411 requests that could never have produced anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one failure that costs real money
&lt;/h3&gt;

&lt;p&gt;Here is the part that surprised me. A rejected request is cheap. A 400 never reaches the model, so no tokens are generated and nothing is billed. A 503 means the request never started. You lose latency, not dollars.&lt;/p&gt;

&lt;p&gt;Timeouts and cancellations are different. A 504 means the model &lt;em&gt;was&lt;/em&gt; generating — you just stopped waiting. A 499 means your own user hit stop, or your HTTP client's deadline fired, after the answer had already started coming back. The tokens exist. Somebody generated them. In our logs that's 2.45% of all calls, and it is the only category where the meter was actually running.&lt;/p&gt;

&lt;p&gt;Then you retry, and the second attempt pays for the entire input again. Not the remainder — the whole prompt, from the system message down. This is why the number to track is &lt;strong&gt;attempts per answer&lt;/strong&gt;, not calls. At &lt;code&gt;gpt-5.6&lt;/code&gt;'s official $2.50 per million input tokens, our median prompt of 1,290 tokens costs about a third of a cent per attempt. Trivial, until a job with a 40,000-token context retries twice under load and you've bought that context three times for one answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to change today
&lt;/h3&gt;

&lt;p&gt;Two lines of policy, and they're both about classification rather than volume.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;RETRYABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_budget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_input_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;spent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;APIError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;spent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approx_input_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;RETRYABLE&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;spent&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_input_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry budget exhausted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First: retry only 429 and 5xx. Everything in the 4xx family except 429 is a bug report addressed to you, and sleeping on it won't fix your JSON. Second: cap the retry budget in &lt;strong&gt;tokens&lt;/strong&gt;, not attempts. Three attempts on a 500-token prompt and three attempts on a 40,000-token prompt are the same line of code and an eighty-fold difference in what you bought. The budget is the thing that scales; the attempt count isn't.&lt;/p&gt;

&lt;p&gt;One more, cheaper still: turn on &lt;strong&gt;streaming&lt;/strong&gt;, where tokens arrive as they're produced instead of all at once at the end. A timeout on a streamed response leaves you holding a partial answer you can show or salvage. A timeout on a blocking call leaves you holding nothing, having paid the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't tell you
&lt;/h3&gt;

&lt;p&gt;Our own gateway records zero tokens and zero charge for all 622 of those events, because we only meter what the upstream returns in its usage block — and a request that died mid-generation doesn't return one. So I can show you exactly how often calls fail, and I cannot show you what an abandoned generation cost upstream. That's a real gap in our metering, not a clean bill of health, and it's the number I'd most like to have.&lt;/p&gt;

&lt;p&gt;Your own logs probably have the same hole. Fill it before you trust any cost model built on call counts. Count attempts.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your model reads 1M tokens and writes back 8K — and the gap lands on your input bill</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Fri, 04 Sep 2026 06:50:20 +0000</pubDate>
      <link>https://dev.to/altrouter/your-model-reads-1m-tokens-and-writes-back-8k-and-the-gap-lands-on-your-input-bill-omi</link>
      <guid>https://dev.to/altrouter/your-model-reads-1m-tokens-and-writes-back-8k-and-the-gap-lands-on-your-input-bill-omi</guid>
      <description>&lt;p&gt;The first number you see on a model page is the context window: 200K, 400K, a million. It reads like capacity — "I can feed it my whole repo." True, and beside the point.&lt;/p&gt;

&lt;p&gt;The number that decides your bill is the other one, printed smaller: how much the model is allowed to write in a single reply. It's usually 10 to 30 times smaller than the context window, and it quietly turns one job into a loop.&lt;/p&gt;

&lt;p&gt;Three words first, because the whole argument lives in them. A &lt;strong&gt;token&lt;/strong&gt; is roughly ¾ of a word — models bill per million of them, at one price for &lt;strong&gt;input&lt;/strong&gt; (what you send) and a higher one for &lt;strong&gt;output&lt;/strong&gt; (what the model writes). The &lt;strong&gt;context window&lt;/strong&gt; is how many tokens one request may contain. And &lt;strong&gt;max output tokens&lt;/strong&gt; — &lt;code&gt;max_tokens&lt;/code&gt; in most APIs — is the ceiling on a single reply. When the reply hits it, the API doesn't crash: it stops mid-sentence and sets &lt;code&gt;finish_reason: "length"&lt;/code&gt;. You are billed in full for the truncated half.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gap is real, and it changes with the route
&lt;/h3&gt;

&lt;p&gt;Here is the same family of models, seen from two places — the numbers on the left are what one OpenAI-compatible gateway exposes, the ones on the right are Anthropic's own published limits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model / route&lt;/th&gt;
&lt;th&gt;Context window&lt;/th&gt;
&lt;th&gt;Max output&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3 Pro, via gateway&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;32,000&lt;/td&gt;
&lt;td&gt;31×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5, via gateway&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;24×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5, first-party API&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;128,000&lt;/td&gt;
&lt;td&gt;8×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same model name, a 15× difference in how much it will write per call. The cap is not a property of the model alone — it's a property of the model &lt;em&gt;plus the road you reach it by&lt;/em&gt;, and nobody puts it in the marketing copy. Check it yourself: the Models API returns it, and any gateway's &lt;code&gt;/v1/models&lt;/code&gt; should too.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the cap actually costs
&lt;/h3&gt;

&lt;p&gt;I ran the arithmetic on a real corpus: the 46 Russian posts in our blog's &lt;code&gt;content/&lt;/code&gt; directory, 138,108 tokens counted with &lt;code&gt;o200k_base&lt;/code&gt;, the tokenizer behind the current GPT models. The job: produce an English version of all of it.&lt;/p&gt;

&lt;p&gt;Reading it is free of drama — 138K fits in every context window in the table above, once. Writing it back is the problem. Output is roughly as long as input, so with an 8,192-token cap the job takes 17 calls; with 128,000 it takes 2.&lt;/p&gt;

&lt;p&gt;Seventeen calls wouldn't matter if each one were cheap. But the obvious way to keep 17 chunks consistent is to hand the model the whole corpus every time — and now you've sent 138K tokens seventeen times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="n"&gt;CORPUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;138_108&lt;/span&gt;          &lt;span class="c1"&gt;# tokens of material you need rewritten
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8_192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CORPUS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;naive_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;CORPUS&lt;/span&gt;      &lt;span class="c1"&gt;# full context resent every call
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cap &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; calls, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;naive_input&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1e6&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;M input tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cap 8192: 17 calls, 2.35M input tokens&lt;/code&gt; against &lt;code&gt;cap 128000: 2 calls, 0.28M&lt;/code&gt;. At GPT-5.6's official $2.50 per million input and $15 per million output, that's &lt;strong&gt;$7.94 versus $2.76&lt;/strong&gt; for byte-identical work. The output bill never moved — 138K tokens either way. The cap multiplied the &lt;em&gt;input&lt;/em&gt; side, 8.4×.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix is boring and it works
&lt;/h3&gt;

&lt;p&gt;Stop resending the whole thing. Send the chunk you're rewriting plus a short shared brief — a glossary, a style note, the previous chunk's last paragraph. Input then totals about one corpus, not seventeen: &lt;strong&gt;$2.42&lt;/strong&gt; for the same job, cheaper than the 128K version, because the big cap wasn't the point. Cheap context makes resending feel free; it isn't.&lt;/p&gt;

&lt;p&gt;Two habits worth building the same week. Read &lt;code&gt;finish_reason&lt;/code&gt; on every call and treat &lt;code&gt;"length"&lt;/code&gt; as an error, not a shrug — a truncated answer that your code cheerfully parses is worse than a crash. And set &lt;code&gt;max_tokens&lt;/code&gt; explicitly instead of inheriting a default: gateways often impose their own (ours truncates at 4,096 when you omit it), and "the answer got cut off" is the most common first bug in LLM code.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;Chunking costs you consistency — terminology drifts between chunk 4 and chunk 12, and a shared glossary only narrows the drift. Prompt caching pushes the resend price down a lot when the prefix is stable, which changes the arithmetic above but not its direction; I can't quote our own hit rates, because our usage records don't split cache tokens out yet. And nothing here helps when a single indivisible answer genuinely exceeds the cap — then the cap is a hard wall and you need a route with a bigger one.&lt;/p&gt;

&lt;p&gt;For reference, the per-token prices I used are the vendors' official ones. We resell the same models 10–25% below those (GPT-5.6 at $2.13/$12.75 against the official $2.50/$15.00), which shaves the bill by a fixed percentage — it does not change the multiplier. A 15% discount on seventeen redundant calls is still seventeen redundant calls.&lt;/p&gt;

&lt;p&gt;Go look up the output cap of whatever model you're calling right now. If you don't know it, you're not budgeting — you're guessing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your JSON output costs 2.6 more than the same data as CSV</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Thu, 03 Sep 2026 08:45:16 +0000</pubDate>
      <link>https://dev.to/altrouter/your-json-output-costs-26x-more-than-the-same-data-as-csv-4a44</link>
      <guid>https://dev.to/altrouter/your-json-output-costs-26x-more-than-the-same-data-as-csv-4a44</guid>
      <description>&lt;p&gt;Everyone writes &lt;code&gt;response_format={"type": "json_object"}&lt;/code&gt; and moves on. It's the obvious choice: your code needs structured data, JSON is structured data, done.&lt;/p&gt;

&lt;p&gt;Then the extraction job runs on a hundred thousand rows and the bill arrives, and a surprising share of it went to curly braces, colons and the word &lt;code&gt;subscription_status&lt;/code&gt; retyped ten thousand times.&lt;/p&gt;

&lt;p&gt;Two words first, since the argument lives in them. A &lt;strong&gt;token&lt;/strong&gt; is roughly ¾ of a word — models bill per million of them. And they bill at two different prices: &lt;strong&gt;input tokens&lt;/strong&gt; (what you send) and &lt;strong&gt;output tokens&lt;/strong&gt; (what the model writes back), with output typically 5–6× the price of input. Formatting you ask the model to &lt;em&gt;produce&lt;/em&gt; lands on the expensive side.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same 200 records, five formats
&lt;/h3&gt;

&lt;p&gt;I generated 200 synthetic customer records — id, name, email, city, amount, signup date, status — and counted them with OpenAI's &lt;code&gt;o200k_base&lt;/code&gt; tokenizer, the one behind the current GPT models. Same data every time, only the wrapper changed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Tokens per row&lt;/th&gt;
&lt;th&gt;vs pretty JSON&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JSON, &lt;code&gt;indent=2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;77.8&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON, compact&lt;/td&gt;
&lt;td&gt;52.8&lt;/td&gt;
&lt;td&gt;−32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSONL&lt;/td&gt;
&lt;td&gt;53.8&lt;/td&gt;
&lt;td&gt;−31%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSV&lt;/td&gt;
&lt;td&gt;29.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−62%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TSV&lt;/td&gt;
&lt;td&gt;29.8&lt;/td&gt;
&lt;td&gt;−62%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pretty-printed JSON costs &lt;strong&gt;2.6× what CSV costs&lt;/strong&gt; for byte-identical information. Nothing about the data changed. The model did the same work. You just asked it to type more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the tokens actually go
&lt;/h3&gt;

&lt;p&gt;Two places, and both are avoidable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indentation is 25 tokens a row.&lt;/strong&gt; That's the gap between pretty and compact JSON — 32% of the bill, spent on whitespace that no parser needs and no human will read, because this output goes straight into &lt;code&gt;json.loads&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key names are another 29 tokens a row.&lt;/strong&gt; &lt;code&gt;"customer_id":&lt;/code&gt;, &lt;code&gt;"email_address":&lt;/code&gt;, &lt;code&gt;"subscription_status":&lt;/code&gt; — the schema, re-typed for every single record. Across 200 rows that's 5,800 tokens spent restating a header you already knew (net 23 a row once CSV's own commas are counted back in).&lt;/p&gt;

&lt;p&gt;The tempting fix is renaming fields to &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;. I measured that too: it takes compact JSON from 52.8 to 44.8 tokens a row. Eight tokens, in exchange for a payload nobody can debug. Dropping the keys entirely — one header line, then rows — saves 23 net. CSV is short-keys done properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it costs in dollars
&lt;/h3&gt;

&lt;p&gt;Take a bulk extraction job on &lt;code&gt;gpt-5.6&lt;/code&gt; at its list price of $15.00 per 1M output tokens, producing 1,000 rows per call, 100 calls a day.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pretty JSON: 77,765 output tokens per call → &lt;strong&gt;$1.17 a call, $117 a day&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;CSV: 29,885 output tokens per call → &lt;strong&gt;$0.45 a call, $45 a day&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's &lt;strong&gt;$2,155 a month&lt;/strong&gt; for the indentation and the repeated field names. The prompt is one sentence different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Count yours before you argue about it
&lt;/h3&gt;

&lt;p&gt;Don't take my row numbers — yours have different field names and different value lengths, and both move the ratio. Fifteen lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;

&lt;span class="n"&gt;enc&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# current GPT models
&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample_output.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# 100+ real rows
&lt;/span&gt;
&lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json pretty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json compact&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
                   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getvalue&lt;/span&gt;&lt;span class="p"&gt;())]:&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/row&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the gap is under 20%, your values are long enough that the wrapper doesn't matter — keep JSON and go optimize something else. If it's the 60% I measured, your records are short and mostly schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't fix
&lt;/h3&gt;

&lt;p&gt;Three honest limits. This is a &lt;strong&gt;bulk&lt;/strong&gt; trick: for a single object with five fields, the difference is pennies and JSON's error-tolerance is worth more. &lt;strong&gt;Tool calling and JSON mode force JSON on you&lt;/strong&gt; — the model's structured-output path emits it and you can't ask for CSV there, so the win is limited to plain-text completions you parse yourself. And CSV needs real escaping: commas and newlines inside values will corrupt a naive &lt;code&gt;split(",")&lt;/code&gt;, so use &lt;code&gt;csv.reader&lt;/code&gt;, or TSV, which measured identically and collides with far less.&lt;/p&gt;

&lt;p&gt;Also, I counted with OpenAI's tokenizer. Anthropic and Google tokenize differently, so the &lt;em&gt;ratio&lt;/em&gt; holds directionally but the per-row numbers won't transfer exactly — recount on the model you actually bill.&lt;/p&gt;

&lt;p&gt;Format work shrinks the token count. It does nothing to the other multiplier, the price per token, and your bill is the product of the two. That's the gateway's half: &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt; resells the same models 10–25% below the vendors' list prices — &lt;code&gt;gpt-5.6&lt;/code&gt; at $12.75 per 1M output against OpenAI's $15.00 — over the OpenAI-compatible API, so it's a &lt;code&gt;base_url&lt;/code&gt; change. Its honest gap here: we don't expose a token-counting endpoint, so the measurement above still has to run locally against the vendor's own tokenizer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one number to take away
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;77.8 versus 29.9 tokens per row.&lt;/strong&gt; Before you tune anything else about an extraction pipeline, look at what format you asked for — and whether anybody was ever going to read the indentation you're paying output price to generate.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The same six-second AI video costs $0.27 or $3.20</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Wed, 02 Sep 2026 04:34:44 +0000</pubDate>
      <link>https://dev.to/altrouter/the-same-six-second-ai-video-costs-027-or-320-5e4d</link>
      <guid>https://dev.to/altrouter/the-same-six-second-ai-video-costs-027-or-320-5e4d</guid>
      <description>&lt;p&gt;If you call a text model, you know roughly what a request costs. Everyone prices per million tokens, so you can put two vendors in one column and subtract.&lt;/p&gt;

&lt;p&gt;Images and video have no such habit. Four vendors, four units, and nothing on the pricing pages lines up. So most teams pick a video model the way they pick a font, then find the number at the end of the month.&lt;/p&gt;

&lt;h3&gt;
  
  
  The units don't line up
&lt;/h3&gt;

&lt;p&gt;Here is what you actually get quoted, all of it real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per second of output.&lt;/strong&gt; Seedance, Hailuo, Wan, PixVerse, Kling. You pay for duration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per clip, flat.&lt;/strong&gt; Veo 3.1 charges one price whether you asked for 4, 6 or 8 seconds. Duration is free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per image.&lt;/strong&gt; Most image models: one price per generation, resolution included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per image, with a resolution tier.&lt;/strong&gt; GPT Image 2 and Nano Banana Pro charge separately for a 4K render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four units means four different mistakes. A per-second model looks cheap in the docs and isn't, because your clips are 10 seconds. A flat-rate model looks expensive and isn't, because you were going to generate the maximum length anyway. And a "per image" price tells you nothing until you know whether your resolution is included in it.&lt;/p&gt;

&lt;p&gt;None of this is hidden. It's just spread across four vendor sites in four shapes, and nobody does the arithmetic before shipping.&lt;/p&gt;

&lt;h3&gt;
  
  
  One six-second clip, five prices
&lt;/h3&gt;

&lt;p&gt;So do the arithmetic. Same job — one 6-second, text-to-video clip, no retries — at vendor list prices as recorded on 2026-08-27. Every model here supports a 6-second output, so nothing is being compared across durations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Metered as&lt;/th&gt;
&lt;th&gt;List price&lt;/th&gt;
&lt;th&gt;One 6s clip&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Veo 3.1&lt;/td&gt;
&lt;td&gt;per clip&lt;/td&gt;
&lt;td&gt;$3.20 / clip&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$3.20&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.5&lt;/td&gt;
&lt;td&gt;per second&lt;/td&gt;
&lt;td&gt;$0.473 / s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2.84&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wan 2.7 Video&lt;/td&gt;
&lt;td&gt;per second&lt;/td&gt;
&lt;td&gt;$0.10 / s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.60&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hailuo 2.3&lt;/td&gt;
&lt;td&gt;per second&lt;/td&gt;
&lt;td&gt;$0.0467 / s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.28&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PixVerse V6&lt;/td&gt;
&lt;td&gt;per second&lt;/td&gt;
&lt;td&gt;$0.045 / s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.27&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twelve times, top to bottom, for the same six seconds. For comparison, the gap people actually argue about in text — Claude Opus 4.5 at $25 per 1M output tokens against Claude Sonnet 5 at $10 — is 2.5x. Video is where the money is, and it's the surface nobody benchmarks in dollars.&lt;/p&gt;

&lt;p&gt;"They're not the same quality" is the fair objection, and it's true — Veo lands shots that Hailuo doesn't. The point isn't that they're interchangeable. It's that a 12x spread deserves a deliberate decision, and right now it usually gets a default.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two traps inside the units
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Flat-rate clips punish short videos.&lt;/strong&gt; Veo 3.1 costs $3.20 whether you generate 4 seconds or 8. That's $0.80 per second at 4 seconds and $0.40 at 8. If your product shows 4-second loops, you are paying double per second of what you ship. Generate at the maximum length and trim in ffmpeg — same bill, more material.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 4K surcharge exists on some models and not others.&lt;/strong&gt; Taking two models off one price list, so the comparison is apples to apples: GPT Image 2 charges &lt;strong&gt;2.2x&lt;/strong&gt; its standard-resolution price for a 4K render, while Seedream V4 charges exactly the same for both. So "just render everything at 4K" is free on one model and more than doubles the bill on the next, with the same line of code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Normalize before you compare
&lt;/h3&gt;

&lt;p&gt;One function, and every price page becomes one column. Feed it the numbers off the vendor docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clip_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Cost of n outputs, normalized. unit: per_second | per_clip | per_image&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_second&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;            &lt;span class="c1"&gt;# per_clip and per_image ignore duration
&lt;/span&gt;
&lt;span class="n"&gt;catalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;veo-3.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="mf"&gt;3.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_clip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seedance-2.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.473&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_second&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wan-2.7-video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_second&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hailuo-2.3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="mf"&gt;0.0467&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_second&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pixverse-v6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="mf"&gt;0.045&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_second&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clip_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clip_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; per 1000 clips&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with your own clip length and your own monthly volume before you compare anything. At 1000 clips a month the table above spans $270 to $3,200 — that's a hiring decision, decided by a string in a config file.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't fix
&lt;/h3&gt;

&lt;p&gt;Normalizing tells you the cheapest way to buy a given model. It doesn't change the price of the model itself. That's the second lever: &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt; resells the same media models below the vendors' own list prices — Hailuo 2.3 at $0.0375 per second against MiniMax's $0.0467, Veo 3.1 at $2.55 a clip against Google's $3.20 — through one API with the same parameters.&lt;/p&gt;

&lt;p&gt;The honest gap, on this exact topic: for most video models our catalog flags its own per-second price as &lt;strong&gt;approximate&lt;/strong&gt;, because the upstream meters in credits rather than seconds. Trust the charge that lands in your usage log over any table, including ours and including this one. Video generation is also asynchronous everywhere — you POST a job and poll for it — so there's no partial billing to inspect mid-render if a clip comes back unusable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one number to take away
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pick a unit, convert everything into it, then choose.&lt;/strong&gt; One clip length, one resolution, one column of dollars. It's twenty minutes of arithmetic against a 12x spread, and it's the only comparison the vendor pages will never do for you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Prompt caching has a break-even point, and it's 22%</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Sun, 16 Aug 2026 08:46:58 +0000</pubDate>
      <link>https://dev.to/altrouter/prompt-caching-has-a-break-even-point-and-its-22-7d</link>
      <guid>https://dev.to/altrouter/prompt-caching-has-a-break-even-point-and-its-22-7d</guid>
      <description>&lt;p&gt;Every guide to cutting LLM costs eventually says the same thing: turn on prompt caching. Almost none of them mention that caching can make your bill &lt;strong&gt;larger&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It has a break-even point, it is computable, and most teams never check which side of it they're on.&lt;/p&gt;

&lt;p&gt;Two words first, because the whole argument lives in them. A &lt;strong&gt;token&lt;/strong&gt; is roughly ¾ of a word; models bill per million of them. &lt;strong&gt;Prompt caching&lt;/strong&gt; stores the front part of your prompt — the system prompt, the tool definitions, the documents you resend every time — so the model doesn't reprocess it on the next request. It matches on an exact prefix: change one byte anywhere near the start and everything after it is a miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache writes cost more than plain tokens
&lt;/h3&gt;

&lt;p&gt;Here is the part that gets skipped. Caching does not have one price, it has two, and one of them is a penalty.&lt;/p&gt;

&lt;p&gt;On Anthropic's API, relative to the normal input price per token:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Multiplier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Normal input token&lt;/td&gt;
&lt;td&gt;1.0×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache &lt;strong&gt;write&lt;/strong&gt;, 5-minute TTL&lt;/td&gt;
&lt;td&gt;1.25×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache &lt;strong&gt;write&lt;/strong&gt;, 1-hour TTL&lt;/td&gt;
&lt;td&gt;2.0×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache &lt;strong&gt;read&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;0.1×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;TTL&lt;/strong&gt; is time-to-live: how long the entry survives after it was last touched. Five minutes by default.&lt;/p&gt;

&lt;p&gt;So a cache hit is a 90% discount, and a cache miss is a 25% &lt;em&gt;surcharge&lt;/em&gt;. Every request that doesn't find a warm entry writes a new one and pays extra for the privilege. Caching is a bet: you're wagering that the entry you just paid 1.25× to create will be read at least a couple of times before it expires.&lt;/p&gt;

&lt;h3&gt;
  
  
  The break-even hit rate
&lt;/h3&gt;

&lt;p&gt;Your &lt;strong&gt;hit rate&lt;/strong&gt; is the share of cacheable tokens served from cache rather than written to it. Call it &lt;code&gt;h&lt;/code&gt;, the write multiplier &lt;code&gt;W&lt;/code&gt;, and the read multiplier &lt;code&gt;R&lt;/code&gt;. Caching pays off when the blended cost drops below plain input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1 − h)·W + h·R  &amp;lt;  1
h  &amp;gt;  (W − 1) / (W − R)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plug the numbers in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5-minute cache:&lt;/strong&gt; (1.25 − 1) / (1.25 − 0.1) = &lt;strong&gt;21.7%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1-hour cache:&lt;/strong&gt; (2 − 1) / (2 − 0.1) = &lt;strong&gt;52.6%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below roughly a 22% hit rate, the five-minute cache is costing you money. The one-hour TTL doubles the write cost, so it needs more than half your requests to hit before it earns its keep — it exists for bursty traffic with gaps longer than five minutes, not as a default upgrade.&lt;/p&gt;

&lt;p&gt;Make it concrete. A support bot on &lt;code&gt;claude-sonnet-5&lt;/code&gt; ($2.00 per 1M input tokens at list price) with a 20K-token prefix and 200 requests a day. Uncached input: $8.00/day. At a 90% hit rate: $1.72/day. At a 15% hit rate — requests spread far enough apart that most entries expire unread — $8.62/day. Same code, same feature flag, a bill that moved 8% in the wrong direction.&lt;/p&gt;

&lt;p&gt;"But my traffic is steady" is a reasonable objection, and if it's true you're fine. The teams that get burned are the ones with low-volume production traffic, a per-user prefix that's never shared, or a nightly batch job spread thin across an hour.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure your actual hit rate
&lt;/h3&gt;

&lt;p&gt;Don't estimate it. Every response reports it. Run your real traffic, in its real order, through this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# your real requests, in real order
&lt;/span&gt;    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SYSTEM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache_control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ephemeral&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}],&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;
    &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache_creation_input_tokens&lt;/span&gt;
    &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache_read_input_tokens&lt;/span&gt;
    &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;

&lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;billed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.25&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hit rate &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | billed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;billed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x uncached&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;hit&lt;/code&gt; comes back at 0% across repeated requests with an identical prefix, something is silently invalidating it — a timestamp in the system prompt, a UUID, an unsorted &lt;code&gt;json.dumps&lt;/code&gt;, a tool list assembled in a different order. The cache key is the exact bytes.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;cache_creation_input_tokens&lt;/code&gt; is 0 too, your prefix is below the minimum cacheable length. That threshold is per-model and it is not monotonic across generations: 512 tokens on Claude Opus 5, 1024 on Opus 4.8 and Sonnet 5, 4096 on Opus 4.6 and Haiku 4.5. A 3K-token prompt caches on one and silently doesn't on the other, with no error either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't fix
&lt;/h3&gt;

&lt;p&gt;A good hit rate cuts the token count. It does nothing to the other multiplier — the price per token — and your bill is the product of both. That's where a gateway comes in: &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt; resells the same models at 10–25% below the vendors' own list prices (&lt;code&gt;claude-sonnet-5&lt;/code&gt; at $1.69 per 1M input against Anthropic's $2.00), through the OpenAI-compatible API, so switching is a &lt;code&gt;base_url&lt;/code&gt; change. Its honest gap on this exact topic: our usage records don't break out cache-read tokens yet, so the measurement above has to come from the provider's response body rather than our dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one number to take away
&lt;/h3&gt;

&lt;p&gt;Caching is not free money, it's a bet at fixed odds: &lt;strong&gt;22% hit rate on the five-minute cache, 53% on the one-hour one.&lt;/strong&gt; Print your hit rate before you argue about breakpoint placement. If it's under the line, the cheapest change you can make today is turning caching off.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The cheap model is only cheap for half your tasks</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Sat, 15 Aug 2026 04:55:32 +0000</pubDate>
      <link>https://dev.to/altrouter/the-cheap-model-is-only-cheap-for-half-your-tasks-29ac</link>
      <guid>https://dev.to/altrouter/the-cheap-model-is-only-cheap-for-half-your-tasks-29ac</guid>
      <description>&lt;p&gt;Most of us pick a model the same way: read a leaderboard, pick the best one we can afford, ship it. Then the bill arrives and the "cheap" model turns out not to be the cheap one.&lt;/p&gt;

&lt;p&gt;The reason is that there is no such thing as a cheap model. There is only a model that is cheap &lt;strong&gt;for the shape of your traffic&lt;/strong&gt; — and the ranking reorders when the shape changes.&lt;/p&gt;

&lt;p&gt;Quick vocabulary, because the whole argument lives in two words. A &lt;strong&gt;token&lt;/strong&gt; is roughly ¾ of a word; models bill per million of them. &lt;strong&gt;Input tokens&lt;/strong&gt; are what you send (prompt, files, chat history); &lt;strong&gt;output tokens&lt;/strong&gt; are what the model writes back. They have different prices, and the gap between them is not the same for every model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The number you don't have yet
&lt;/h3&gt;

&lt;p&gt;Every model's price is two numbers, and every vendor publishes the ratio between them without commenting on it. Here it is, list prices per 1M tokens, snapshot taken 12 Aug 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input $/1M&lt;/th&gt;
&lt;th&gt;Output $/1M&lt;/th&gt;
&lt;th&gt;Output is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gemini-3.1-flash-lite&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;td&gt;6× input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;grok-4.3&lt;/td&gt;
&lt;td&gt;1.25&lt;/td&gt;
&lt;td&gt;2.50&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2× input&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-haiku-4-5&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;td&gt;5× input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-3.5-flash&lt;/td&gt;
&lt;td&gt;1.50&lt;/td&gt;
&lt;td&gt;9.00&lt;/td&gt;
&lt;td&gt;6× input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-sonnet-5&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;10.00&lt;/td&gt;
&lt;td&gt;5× input&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at row two. Grok 4.3 charges only twice as much for writing as for reading, where everyone else charges five or six times. That single number decides whether it is expensive or a bargain — and which one it is depends entirely on you.&lt;/p&gt;

&lt;p&gt;So measure your own ratio before you compare anything. Every OpenAI-compatible response already carries it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# add it up over a few hundred real requests, then:
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tok_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tok_out&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tok_in&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_in&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tok_out&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_out&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_out&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# claude-haiku-4-5
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_out&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# grok-4.3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Same two models, opposite verdict
&lt;/h3&gt;

&lt;p&gt;Take Haiku 4.5 and Grok 4.3 and run them through two ordinary jobs, per 1,000 requests, at the list prices above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classification&lt;/strong&gt; — you send 4,000 tokens of document and get back one word (50 tokens). Almost all of the bill is reading.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;claude-haiku-4-5: &lt;code&gt;4M × $1.00 + 0.05M × $5.00&lt;/code&gt; = &lt;strong&gt;$4.25&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;grok-4.3: &lt;code&gt;4M × $1.25 + 0.05M × $2.50&lt;/code&gt; = &lt;strong&gt;$5.12&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Haiku wins by 17%. Now the same two models on &lt;strong&gt;code generation&lt;/strong&gt; — 1,500 tokens in, 2,500 tokens out. Now almost all of the bill is writing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;claude-haiku-4-5: &lt;code&gt;1.5M × $1.00 + 2.5M × $5.00&lt;/code&gt; = &lt;strong&gt;$14.00&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;grok-4.3: &lt;code&gt;1.5M × $1.25 + 2.5M × $2.50&lt;/code&gt; = &lt;strong&gt;$8.12&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grok wins by 42%. Nothing changed except the shape of the traffic. Any blog post that tells you which model is cheapest, without asking what your job looks like, is guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  When the upgrade is nearly free
&lt;/h3&gt;

&lt;p&gt;The same arithmetic works in the other direction, and this is the part that surprised me. Compare gemini-3.5-flash ($1.50 / $9.00) with claude-sonnet-5 ($2.00 / $10.00). On that RAG workload — 8,000 in, 700 out, per 1,000 requests — Flash costs $18.30 and Sonnet costs $23.00. You are one quarter away from a model in a completely different class, while the word "flash" in the name suggests you are saving a fortune.&lt;/p&gt;

&lt;p&gt;Then add the multiplier nobody puts in the spreadsheet: &lt;strong&gt;retries&lt;/strong&gt;. If the cheap model fails one call in five and you re-run those on the expensive one, you pay for the cheap attempt &lt;em&gt;and&lt;/em&gt; the expensive one. At a 20% failure rate that Flash job is really $18.30 + 20% × $23.00 = $22.90 — the discount is gone, and you also shipped worse latency.&lt;/p&gt;

&lt;p&gt;Prices below list change the same arithmetic without changing its shape. I work on &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt;, which bills the same vendor models under list — Claude Sonnet 5 at $8.50 per 1M output against the official $10.00, Grok 4.3 at $2.12 against $2.50 — so the crossover points move, but you still have to know your own ratio to find them. It also does not host embedding models, and no discount will rescue a model that keeps failing your task.&lt;/p&gt;

&lt;h3&gt;
  
  
  The actual decision order
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measure &lt;code&gt;prompt_tokens&lt;/code&gt; / &lt;code&gt;completion_tokens&lt;/code&gt;&lt;/strong&gt; on a few hundred real requests. This takes ten minutes and everything else depends on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If input dominates (10:1 or more)&lt;/strong&gt;, rank candidates by input price only. Output price is noise; ignore the headline number everyone quotes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If output dominates (below 2:1)&lt;/strong&gt;, rank by output price only — and specifically look for models with a low output-to-input multiple.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price the next tier up on your own mix&lt;/strong&gt; before assuming you can't afford it. If it lands under ~1.5× and it removes a retry, it is cheaper, not dearer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run this per endpoint, not per app.&lt;/strong&gt; Your classifier and your code generator are different workloads and deserve different models.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;This is arithmetic about price, not about quality. It will not tell you whether Grok 4.3 writes code you'd merge — only you can judge that, on your tasks. It ignores latency, rate limits, and prompt caching, which can each move the answer more than the price gap does. And the numbers are a snapshot from 12 Aug 2026; every one of them will be stale within a quarter, though the method won't be.&lt;/p&gt;

&lt;p&gt;Pick the model for the task, not for the app. And before you argue about which one is cheaper, go print your two usage numbers — the argument is usually already settled by them.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your LLM gateway takes a cut. Seventeen lines of Python tell you how big.</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Fri, 14 Aug 2026 05:18:57 +0000</pubDate>
      <link>https://dev.to/altrouter/your-llm-gateway-takes-a-cut-seventeen-lines-of-python-tell-you-how-big-45jc</link>
      <guid>https://dev.to/altrouter/your-llm-gateway-takes-a-cut-seventeen-lines-of-python-tell-you-how-big-45jc</guid>
      <description>&lt;p&gt;You wanted to try three models from three vendors, so you did the sensible thing: pointed one client at a gateway, put one key in the environment, and stopped thinking about it. A &lt;strong&gt;gateway&lt;/strong&gt; here is a service that speaks the OpenAI API and forwards your requests to Anthropic, Google, OpenAI, xAI and the rest, so switching models is a string change instead of a new SDK.&lt;/p&gt;

&lt;p&gt;What almost nobody checks after that: what the gateway charges for the exact same tokens the vendor would have sold you directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you actually buy from a gateway
&lt;/h3&gt;

&lt;p&gt;Three things, and they are worth very different amounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One key and one invoice.&lt;/strong&gt; Real, and boring. It saves you three signups and three billing pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Routing and fallback.&lt;/strong&gt; If a provider returns 529 or a region goes dark, the gateway retries elsewhere. Worth something on production traffic, nothing on a weekend project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A price per token.&lt;/strong&gt; This is the one that varies by an order of magnitude between services, and the one nobody puts in a comparison table — because there is no single "gateway price". There is a price per model, and a service can sit below vendor list on one model and far above it on another.&lt;/p&gt;

&lt;p&gt;Vocabulary, in case it is new: models are billed per &lt;strong&gt;million tokens&lt;/strong&gt; (a token is roughly ¾ of a word), input and output priced separately, with output typically 3–10× more expensive. The &lt;strong&gt;list price&lt;/strong&gt; is what the vendor publishes on its own pricing page. That list price is your baseline — a gateway is either below it, at it, or taking a markup on top.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seventeen lines that answer it
&lt;/h3&gt;

&lt;p&gt;Many gateways publish per-model pricing in their models endpoint. If yours does, this reads it and prices out &lt;em&gt;your&lt;/em&gt; token mix, not a benchmark's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;GATEWAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.altrouter.ai/v1/models/public&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# your gateway's models endpoint
&lt;/span&gt;&lt;span class="n"&gt;LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;15.00&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;  &lt;span class="c1"&gt;# vendor list $/1M in, out
&lt;/span&gt;&lt;span class="n"&gt;MIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# your monthly millions of tokens: input, output
&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GATEWAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price-check&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;inp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pricing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_per_1m_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pricing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion_per_1m_usd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;LIST&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;inp&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LIST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;mine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIX&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;inp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;MIX&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
    &lt;span class="n"&gt;vendor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIX&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;MIX&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; gateway $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mine&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;8.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  list $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;8.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mine&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;vendor&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run on 2026-08-14 against the endpoint in the snippet, it prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpt-5.6            gateway $  935.09  list $ 1100.00  -15.0%
claude-sonnet-5    gateway $  677.91  list $  800.00  -15.3%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your gateway does not publish prices in that endpoint — plenty don't — use the invoice method instead: take last month's charge, divide by the input and output tokens the gateway logged, and you have your real effective $/1M. That number is the only one that matters, and it is the one you compare to the vendor's page.&lt;/p&gt;

&lt;p&gt;The obvious objection: &lt;em&gt;the mix is made up&lt;/em&gt;. It is — replace &lt;code&gt;MIX&lt;/code&gt; with your own two numbers before you believe any of the output. The ratio between input and output tokens is workload-specific, and since output costs several times more, a chat workload and a document-summarizing workload rank gateways differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  The four things I'd check before switching
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Price per model, on your mix.&lt;/strong&gt; The script above. A gateway that is cheap on a small model can be expensive on the large one you actually run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether &lt;code&gt;base_url&lt;/code&gt; is genuinely the only change.&lt;/strong&gt; Test streaming, tool calls, and the &lt;code&gt;usage&lt;/code&gt; object in the response. Those are where compatibility usually breaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What happens on a provider outage.&lt;/strong&gt; Ask for the retry and fallback behaviour explicitly, then unplug a model and watch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your exit.&lt;/strong&gt; If leaving means rewriting anything beyond a URL and a key, that is not a gateway, that is a platform.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Disclosure, since the URL in the snippet is mine: I work on &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt;, which is why I could hardcode an endpoint that publishes its prices. It resells vendor models below list — Claude Sonnet 5 at $1.69 / $8.50 per 1M against the official $2.00 / $10.00, Opus 4.5 at $3.74 / $18.75 against $5.00 / $25.00, discounts running 10–25% by model, prices as of August 2026. Point the script at whatever you use now; the arithmetic does not care.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this does not tell you
&lt;/h3&gt;

&lt;p&gt;Price, and only price. The script says nothing about latency, uptime, rate limits, or whether prompt caching is passed through at the vendor's discount — check that separately, it can outweigh a 15% price gap. And the honest gaps on my side: no embedding models, no data residency options, no SLA of our own beyond the upstream vendor's. If you are on one vendor and never plan to leave, buy direct — you save exactly the middleman's margin.&lt;/p&gt;

&lt;p&gt;Run the seventeen lines on your own gateway this week. Whatever the number is, it is better to know it than to assume the convenience was free.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>python</category>
    </item>
    <item>
      <title>Your LLM bill has two levers. Your team only pulls one.</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Thu, 13 Aug 2026 07:19:53 +0000</pubDate>
      <link>https://dev.to/altrouter/your-llm-bill-has-two-levers-your-team-only-pulls-one-g95</link>
      <guid>https://dev.to/altrouter/your-llm-bill-has-two-levers-your-team-only-pulls-one-g95</guid>
      <description>&lt;p&gt;Every guide to cutting LLM costs is about the same thing: send fewer tokens. Trim the system prompt, cache the prefix, retrieve less, route easy requests to a small model. All of it works. And all of it is one half of the equation.&lt;/p&gt;

&lt;p&gt;Your bill is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bill = tokens × price_per_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two factors. Multiplication. Every cost-cutting article I have read attacks the left one and treats the right one as a law of physics. It is not — it is a number on a pricing page, and pricing pages are not the only place those models are sold.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two levers, in the same units
&lt;/h3&gt;

&lt;p&gt;A quick vocabulary check, because the whole argument lives in these words. A &lt;strong&gt;token&lt;/strong&gt; is roughly ¾ of a word — models are billed per million of them. &lt;strong&gt;Input tokens&lt;/strong&gt; are what you send (prompt, files, chat history), &lt;strong&gt;output tokens&lt;/strong&gt; are what the model writes back. Output costs 3–10× more per token, which is why it usually dominates the bill. &lt;strong&gt;Prompt caching&lt;/strong&gt; means the provider stores your unchanged prefix and re-reads it at a large discount, often around 10% of the input price — you pay a small premium the first time to write the cache, then read it cheaply.&lt;/p&gt;

&lt;p&gt;Now, one workload. A team runs a coding agent, and last month it burned &lt;strong&gt;200M input and 40M output tokens&lt;/strong&gt; on Claude Sonnet 5. At list price ($2.00 in / $10.00 out per 1M):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;200 × $2.00 + 40 × $10.00 = $400 + $400 = $800&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lever one — fewer tokens.&lt;/strong&gt; Say 70% of that input is a stable prefix (the same repo files, the same instructions) and you make it cacheable. That 140M drops to roughly $0.20 per 1M instead of $2.00. Input becomes &lt;code&gt;140 × $0.20 + 60 × $2.00 = $148&lt;/code&gt;. Output does not cache — it is generated fresh every time. New total: &lt;strong&gt;$548&lt;/strong&gt;, a 31% cut. That is a good month of engineering work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lever two — cheaper tokens.&lt;/strong&gt; Same tokens, same model, price reduced 15%. $800 becomes &lt;strong&gt;$680&lt;/strong&gt;. Zero engineering work.&lt;/p&gt;

&lt;p&gt;Neither is the interesting part. The interesting part is that they multiply:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Monthly bill&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;List price, no caching&lt;/td&gt;
&lt;td&gt;$800.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching only&lt;/td&gt;
&lt;td&gt;$548.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cheaper price only&lt;/td&gt;
&lt;td&gt;$680.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Both&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$465.80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pulling both levers is a 42% cut. Pulling the one everybody writes about is 31%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the second lever gets ignored
&lt;/h3&gt;

&lt;p&gt;Because it does not feel like engineering. Caching is a diff you can review, benchmark, and put in a changelog. "We pay less per token" is a procurement decision, so it falls between the team that owns the code and the team that owns the invoice, and nobody optimizes it.&lt;/p&gt;

&lt;p&gt;It is also the lever with no quality tradeoff. Every token-side optimization trades something: caching adds cache-invalidation bugs, retrieval trims context the model might have needed, routing to a smaller model risks a worse answer that costs more in retries. Changing the price per token changes exactly one thing — the price per token. The weights, the context window, and the output are identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the price is actually negotiable
&lt;/h3&gt;

&lt;p&gt;Disclosure before the numbers: I work on &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt;, a gateway that resells the same vendor models below their list price — Claude Sonnet 5 at $1.69 / $8.50 per 1M against the official $2.00 / $10.00, Opus 4.5 at $3.74 / $18.75 against $5.00 / $25.00, GPT-5.6 at $2.13 / $12.75 against $2.50 / $15.00. Discounts run 10–25% depending on the model, the API is OpenAI-compatible, so switching is a &lt;code&gt;base_url&lt;/code&gt; change. Prices as of August 2026.&lt;/p&gt;

&lt;p&gt;The honest gaps, so you hear them from me and not from the comments: no embedding models, no data residency options, no SLA of our own beyond what the upstream vendor delivers, and no invoicing for legal entities yet. If any of those is a requirement, this lever is closed for you and lever one is all you have.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to do this week
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pull last month's spend and split it into input, output, and cached input.&lt;/strong&gt; If you cannot produce that split, you cannot price anything — in/out prices differ by 3–10×, and cached input by another 10×.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiply your actual token counts by two or three candidate prices.&lt;/strong&gt; Not benchmark tokens — yours. The ratio is workload-specific.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do the caching work anyway.&lt;/strong&gt; The levers multiply; skipping one to take the other is leaving the smaller half on the table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The point is not that one lever beats the other. It is that you are running an optimization with two variables and holding one constant for no reason other than habit.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your model benchmark measures everything except the bill</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:30:17 +0000</pubDate>
      <link>https://dev.to/altrouter/your-model-benchmark-measures-everything-except-the-bill-4i8o</link>
      <guid>https://dev.to/altrouter/your-model-benchmark-measures-everything-except-the-bill-4i8o</guid>
      <description>&lt;p&gt;Every model benchmark I read ranks the same two things: how good the output was, and sometimes how many tokens it took. Almost none of them print the number you actually get charged for.&lt;/p&gt;

&lt;p&gt;That gap matters, because the two rankings are not the same list. A model can win on quality, lose on tokens, and still be the cheapest way to finish your task — or the opposite. You cannot tell which without doing the multiplication yourself.&lt;/p&gt;

&lt;p&gt;Here is the multiplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  The formula
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cost = (input_tokens / 1M) × price_in + (output_tokens / 1M) × price_out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole thing. Two numbers from the benchmark, two numbers from the vendor's pricing page. The reason it is worth writing down is that every term in it moves independently, and benchmarks only ever report the first two.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;Take one coding task. Say it consumes &lt;strong&gt;40K input tokens and 12K output tokens&lt;/strong&gt; — the shape of a real agent run with a few files in context and a couple of iterations. These numbers are an assumption for the example, not a measurement; substitute your own from your logs.&lt;/p&gt;

&lt;p&gt;Vendor list prices per 1M tokens, as of August 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;in $&lt;/th&gt;
&lt;th&gt;out $&lt;/th&gt;
&lt;th&gt;Cost of one task&lt;/th&gt;
&lt;th&gt;100 tasks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4.5&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;td&gt;25.00&lt;/td&gt;
&lt;td&gt;$0.500&lt;/td&gt;
&lt;td&gt;$50.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6&lt;/td&gt;
&lt;td&gt;2.50&lt;/td&gt;
&lt;td&gt;15.00&lt;/td&gt;
&lt;td&gt;$0.280&lt;/td&gt;
&lt;td&gt;$28.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;10.00&lt;/td&gt;
&lt;td&gt;$0.200&lt;/td&gt;
&lt;td&gt;$20.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok 4.5&lt;/td&gt;
&lt;td&gt;2.00&lt;/td&gt;
&lt;td&gt;6.00&lt;/td&gt;
&lt;td&gt;$0.152&lt;/td&gt;
&lt;td&gt;$15.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;td&gt;$0.100&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3 Flash&lt;/td&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;$0.056&lt;/td&gt;
&lt;td&gt;$5.60&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nine times between the top and the bottom row. No leaderboard shows you that spread, because no leaderboard multiplies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap in the cheap column
&lt;/h2&gt;

&lt;p&gt;Now the part that makes the table dangerous if you read it naively.&lt;/p&gt;

&lt;p&gt;Token counts are not a property of the task. They are a property of the &lt;strong&gt;model on&lt;/strong&gt; that task. A weaker model that misunderstands the requirement, retries twice, re-reads the whole file and asks a clarifying question burns three or four times the tokens of a model that gets it in one pass.&lt;/p&gt;

&lt;p&gt;Run the arithmetic again. Haiku 4.5 is five times cheaper per token than Sonnet 5. If it needs four times the tokens to converge, one task costs $0.40 instead of $0.10 — and Sonnet, at $0.20, is now &lt;strong&gt;twice as cheap&lt;/strong&gt; as the "cheap" model.&lt;/p&gt;

&lt;p&gt;This is why "just use a smaller model" is advice, not a strategy. The only way to know is to log input and output tokens per task, per model, and multiply. Both numbers, separately: output usually dominates the bill, and it is the one that explodes when a model starts thinking out loud.&lt;/p&gt;

&lt;p&gt;Same reason time-to-finish deserves a column. A model that produces correct code after forty minutes of tool calls is not free just because each call is small.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Log tokens per task, split into input and output.&lt;/strong&gt; If your framework does not expose them, most gateways do. Without the split you cannot price anything, because in/out prices differ by 3–10×.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclude cache reads or price them separately.&lt;/strong&gt; Cached input is usually an order of magnitude cheaper; mixing it in makes your numbers look better than reality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price your top three candidates on your own tasks&lt;/strong&gt;, not on a public benchmark's tasks. The token ratio is workload-specific — long-context refactoring and short algorithmic prompts produce completely different bills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recompute quarterly.&lt;/strong&gt; Model prices move, and new models land every few weeks at a different point on the curve.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do this once and the model choice stops being a debate about vibes. It becomes a number with a currency symbol on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the price itself is negotiable
&lt;/h2&gt;

&lt;p&gt;One more term in that formula is worth attention, and it is the one everybody treats as fixed: &lt;code&gt;price_in&lt;/code&gt; and &lt;code&gt;price_out&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vendor list price is not the only price those models sell at. I work on &lt;a href="https://altrouter.ai" rel="noopener noreferrer"&gt;altrouter.ai&lt;/a&gt;, a gateway that resells the same models below the vendor's list — Claude Opus 4.5 at $3.74 / $18.75 per 1M against the official $5.00 / $25.00, Sonnet 5 at $1.69 / $8.50 against $2.00 / $10.00, and so on across 44 models on an OpenAI-compatible API. The discount is a standing price, shown next to the official one on every model page, not an intro offer.&lt;/p&gt;

&lt;p&gt;Plug it into the same example: the 100-task run on Opus 4.5 costs $37.44 instead of $50.00. Same weights, same tokens, same code — only the third and fourth term of the formula changed.&lt;/p&gt;

&lt;p&gt;I am obviously not a neutral party on that last paragraph, so treat it the way you should treat the rest of this post: as arithmetic you can check. The formula is public, the list prices are public, and your token counts are in your own logs.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>cost</category>
    </item>
  </channel>
</rss>
