Skip to content
Back to news

The price rides on the frame that carries the tokens

A streamed response cannot carry cost in a header, so the last chunk before [DONE] now carries it under `x_lobstack`, beside the usual `usage` object.


A streamed answer's headers are written before the provider has reported a single token, so x-lobstack-cost-usd — which the buffered path does carry — cannot exist on that path. For three months the trailing usage chunk went out with token counts only, under a comment claiming a client could read cost from it.

What that comment cost

Lob Bot believed it. Finding no cost, it fell back to its own bundled rate card: six models against the 26 we serve. Every request the router sent to Gemini, Grok, DeepSeek or Qwen priced at exactly $0.00 in the desktop app's Spend view while the Console billed it correctly.

Two numbers disagreeing is worse than either being wrong on its own, because it makes the ledger look like it is lying.

The frame now carries what it always claimed to

usage keeps the exact OpenAI shape, so an SDK that has never heard of us parses it unchanged and ignores the rest. The money hangs off x_lobstack beside it.

{
  "object": "chat.completion.chunk",
  "model": "gemini-3.8-flash",
  "choices": [],
  "usage": { "prompt_tokens": 1000, "completion_tokens": 500, "total_tokens": 1500 },
  "x_lobstack": {
    "request_id": "2f1c…",
    "served_model": "gemini-3.8-flash",
    "requested_model": "claude-opus-5",
    "routed": true,
    "cost_usd": 0.003281,
    "savings_usd": 0.018594,
    "priced": true,
    "baseline_model": "claude-opus-5",
    "baseline_reason": "named",
    "baseline_cost_usd": 0.021875
  }
}

Read the price from here rather than pricing the token counts against your own copy of our rate card. A local copy drifts — we serve 26 models today and retire or reprice some of them every few weeks — and a client that cannot price the model our router chose will show $0.00 for a request that is on your bill.

null, never zero

cost_usd is null when the served model is not in the registry or the stream died before it could be metered, and priced tells you which case you are in. A UI renders 0 as free and null as unknown, and only one of those is honest about a charge that is real.

The three baseline fields travel together or not at all, and savings_usd never travels without baseline_reason.

Metered exactly once

flush() returns its meter and is called before the frame rather than only from the finally block. It is idempotent, so the failure paths still meter exactly once. The frame is built by a function in stream.ts rather than inline in the route, so the build guards can hold the real thing: reintroducing a ?? 0 fallback fails two of them.

Routing and quota headers are present on both paths. The full field list is in Metering & cost.


All posts