burn

Pricing

burn prices a request when it finishes and stores the result on the row. The price list is public, cached locally, and never guessed.

Where prices come from

Prices come from the CloudPrice LiteLLM-compatible model map:

https://ai.cloudprice.net/api/v1/litellm_model_prices.json?mode=chat

The download is free and needs no key. burn keeps the mode: "chat" entries and converts per-token costs to USD per 1 million tokens. The slimmed copy is written to ~/.burn/prices.json with a fetchedAt timestamp and the source URL.

Thanks to CloudPrice for publishing the list. The data stays on their servers; burn stores only the slimmed model map it needs for matching.

BURN_PRICE_URL points the fetch somewhere else, which is useful for a mirror or a test.

The 24 hour cache

  • If prices.json exists and is less than 24 hours old, burn uses it.
  • If it is missing or older than 24 hours, the proxy starts a background refresh. The request in flight is not delayed.
  • If the download fails, the cached copy stays in use. A failed refresh does not empty the price book.
  • A first run with no cache uses the bundled fallback list while the refresh runs.

The bundled fallback is src/prices.fallback.json, a small hand-maintained snapshot of about 30 popular OpenAI, Anthropic, Google, and DeepSeek models. It is transcribed from each provider's published pricing page so a first run or an offline machine still prices common traffic. It is not the full CloudPrice dataset.

Live prices replace the fallback within 24 hours of the first run, unless BURN_OFFLINE is set.

Matching a request to a price

burn only considers entries that belong to the provider that handled the request. It tries these in order:

  1. The exact key <provider>/<model>. This is how OpenRouter entries appear, for example openrouter/anthropic/claude-sonnet-4.
  2. The exact model name.
  3. The model name with a date or version suffix removed: -20250929, -2024-10-22, -10-22-2024, -v2, -001, -latest, and :free, :batch, :nitro, :floor, :extended, :online.
  4. The longest prefix match, where the next character in the model name is a separator such as -, _, ., :, @, /, or a space.

A direct OpenAI or Anthropic request is never priced from bedrock/, vertex_ai/, azure/, together_ai/, or similar keys, because those hosts charge their own rates. burn matches on the key prefix, not on the litellm_provider field, which is mislabeled on some entries upstream.

Cache and reasoning tokens

The price list can carry separate rates for cache reads, cache writes, and reasoning tokens. burn uses them when present.

Token typeIf the price list has a rateFallback
Cache read, OpenAI stylecache_read_input_token_cost0.5 × input rate
Cache read, Claude stylecache_read_input_token_cost0.1 × input rate
Cache write, Claude stylecache_creation_input_token_cost1.25 × input rate
Cache write, OpenAI stylecache_creation_input_token_cost1 × input rate
Reasoning tokensoutput_cost_per_reasoning_tokenPriced at the output rate, or 0 if no reasoning rate exists

OpenAI reports cached tokens inside the prompt token total, so burn subtracts them from input before pricing. Anthropic reports input, cache reads, and cache writes separately.

If a response reports reasoning tokens but the price list has no reasoning rate, the reasoning count is not charged twice. burn clamps it to the output token count and prices the normal output at the output rate.

Overrides

An override always wins over a fetched price. Set one with:

burn price set gpt-4o 2.5 10

The two numbers are USD per 1 million tokens: input first, output second. The override is stored in config.json and applies to future requests. It can also be written directly:

{
  "prices": {
    "my-model": { "in": 0.5, "out": 1.5 },
    "openai/my-model": { "in": 0.5, "out": 1.5 }
  }
}

burn checks the exact model key and then the <provider>/<model> key in config.json. An override has no cache-read, cache-write, or reasoning fields, so those tokens use the normal fallback multipliers based on the input and output rates.

Remove an override with:

burn price unset gpt-4o

Unpriced models

If no key matches, the request is stored with cost = NULL. burn does not estimate a price from another model. Reports count the row as unpriced and print a notice:

3 requests have no price. Run "burn price update" to fetch prices,
then "burn price recalc" to fill them in.

There are three ways to fix it:

  1. burn price update then burn price recalc, after the model appears in the public list.
  2. burn price set <model> <in> <out> for a model that will never be in the list, such as a private endpoint.
  3. Leave it unpriced and use the token counts. The row is still useful.

recalc

burn price update
burn price recalc

burn price update fetches a fresh list. burn price recalc selects every row whose cost is NULL, prices it with the current book, and fills in the ones it can. It never rewrites a row that already has a cost, so changing a price later does not rewrite history.

A row that still has no match after recalc stays unpriced. Recalc is safe to run repeatedly.

Offline use

Set BURN_OFFLINE=1 or BURN_OFFLINE=true to stop burn from fetching prices:

BURN_OFFLINE=1 burn today
BURN_OFFLINE=1 burn proxy

In offline mode, burn uses prices.json if it exists, otherwise the bundled fallback. burn price update refuses to run and says offline mode is on. Proxying, logging, reporting, the dashboard, and burn add all still work.

Accuracy

Prices are best effort, come from a third party, and change often. Providers bill with their own rules: batch discounts, long-context tiers, priority tiers, free tiers, region, and negotiated rates. burn's numbers are an estimate for watching trends and catching surprises, not an invoice.

Verify anything that matters against the provider's own usage page.