You log what each LLM call costs, right next to the request, in a DECIMAL(10,2) column like every other amount of money in your app. A month later the dashboard says the new feature cost $0.00. The vendor's invoice says $1,320. Nothing leaked. One call cost less than half a cent, and your database rounded it away.
A cent is too big for one LLM call
Model APIs bill per token, a chunk of text about three-quarters of an English word. Input tokens are what you send and output tokens are what the model writes back; output costs more. Prices are quoted per million tokens, and the ones vendors publish themselves are called list prices. Every response also carries a usage field: the exact number of input and output tokens you were billed for.
Take a modest call: 1,000 tokens in, 200 out. On GPT-5.6, at $2 per million input tokens and $12 per million output, that's $0.002 + $0.0024 = $0.0044. A money column keeps two digits after the point, so anything under half a cent becomes $0.00. PostgreSQL does exactly that when you insert 0.0044 into a DECIMAL(10,2) column, and raises no error. The habit comes from payments, where an amount is a whole number of cents. A single LLM call is usually a fraction of one.
Four of five models log as free
I priced the same call on five models. List prices come from the catalog of the LLM gateway I work on, read on 27 October 2026. A month here is 10,000 calls a day for 30 days.
| Model, list price per 1M in / out | One call costs | Logged as | Month, spent | Month, logged |
|---|---|---|---|---|
| claude-haiku-5.5, $0.10 / $0.50 | $0.0002 | $0.00 | $60 | $0 |
| gpt-5.6-mini, $0.20 / $1.20 | $0.00044 | $0.00 | $132 | $0 |
| gemini-3.8-flash, $0.75 / $3.75 | $0.0015 | $0.00 | $450 | $0 |
| gpt-5.6, $2 / $12 | $0.0044 | $0.00 | $1,320 | $0 |
| claude-opus-5.5, $4 / $20 | $0.008 | $0.01 | $2,400 | $3,000 |
Four models log as free. The fifth, Claude Opus 5.5, logs 25% too much, because $0.008 rounds up to a whole cent. Don't count on the errors cancelling out: one feature sends calls of roughly the same size, so they all round the same way. Your vendor's dashboard still shows the right total, but it can't tell you which feature or which user spent it. Only your log can, and here it says nobody did.
The log can also jump when almost nothing changed. On GPT-5.6 with 200 output tokens, a call with 1,200 input tokens costs $0.0048 and logs as $0.00; with 1,400 it costs $0.0052 and logs as $0.01. Make your instructions 200 tokens longer, and the monthly line goes from $0 to $3,000 while the real bill moves from $1,440 to $1,560.
Store millionths, round once
The fix is to round where you read, not where you write. Two ways work. Store the raw facts, the model name and the two token counts from usage, and turn them into money when you build the report; when a price changes, you can redo the math. Or store the cost as a whole number of micro-dollars, millionths of a dollar. Tokens times the price per million already gives micro-dollars: 1,000 × $0.20 + 200 × $1.20 is 440 micro-dollars for one GPT-5.6 mini call.
PRICES = { # list price, USD per 1M tokens: (input, output), 27 Oct 2026
"claude-haiku-5.5": (0.10, 0.50),
"gpt-5.6-mini": (0.20, 1.20),
"gemini-3.8-flash": (0.75, 3.75),
"gpt-5.6": (2.00, 12.00),
"claude-opus-5.5": (4.00, 20.00),
}
def cost_micro_usd(model: str, input_tokens: int, output_tokens: int) -> int:
"""Cost of one call in millionths of a dollar. Store this, not cents."""
price_in, price_out = PRICES[model]
return round(input_tokens * price_in + output_tokens * price_out)
CALLS_A_DAY = 10_000
for model in PRICES:
micro = cost_micro_usd(model, 1_000, 200)
logged = round(micro / 1_000_000, 2) * CALLS_A_DAY # round each call, then add
spent = micro * CALLS_A_DAY / 1_000_000 # add, then convert once
print(f"{model:17} logged ${logged:7.2f} spent ${spent:7.2f} a day")
It prints $0.00 logged against $2.00, $4.40, $15.00 and $44.00 actually spent a day on the first four models, and $100 against $80 on Opus. In your app, feed it the two counts from each response's usage: prompt_tokens and completion_tokens in OpenAI-style APIs. In the database, that's a BIGINT (64-bit integer) column, say cost_micro_usd, which holds up to $9.2 trillion. A float column would keep the fractions too, but floats added in a different order can give slightly different totals; integers add up exactly.
What this doesn't solve
An exact sum of your rows still isn't the invoice. It's only as right as your token counts and your price table. Discounted tokens, such as a repeated prompt the vendor serves from its cache, have their own price; give them their own column. A call that times out mid-answer may return no usage, and then your log never learns what it cost. And no gateway can fix this for you, including the one I work on: the rounding happens in your database, after the response has arrived. Once a month, compare your sum with the vendor's invoice.
The takeaway
A typical LLM call costs less than the smallest amount your money column can hold. Log tokens or micro-dollars for every call, and round once, when you add them up.
Top comments (1)
Strong point on rounding, and micro-dollars + round-once is the right default. The line I'd push hardest is the one under "what this doesn't solve": cached tokens needing their own price. One "cached tokens" column still isn't enough, because cache-read and cache-write are two different events with opposite cost behavior. A cache-read is the cheap case (often ~0.1x input). A cache-write happens when the prefix breaks — a tool list that changed between calls, a reconnect, a moved template — and you re-pay the whole prefix at write rate (can be ~2x input). Blend them and the monthly total looks calm while the variance that actually decides the invoice vanishes.
So I'd log three counts off the usage field, not one: fresh input, cache-read, cache-write. Rounding underlogs all three the same way you describe, but cache-write is where a few calls carry most of the cost, and it's exactly the series a blended column hides. Reconciling only the blended sum against the invoice once a month shows you the gap but not which calls caused it. Same discipline as yours — store the raw facts, derive money later — just one level finer on the usage field.