Pricing
How Trodo turns tokens into dollars: report tokens or a cost, set per-model prices your team controls, and track cost per category.
Every LLM span carries a per-span cost in USD. The SDK never hardcodes prices: you report token counts (or a final cost), and Trodo computes the dollars from prices you control. This works on every ingestion path, the SDKs and OTLP /v1/traces alike.
How cost is resolved
Each LLM span takes its cost from the first source that applies, in priority order:
Explicit cost
A final USD number you send. Used as-is, never recomputed.
Team Model Price
Your configured rate for the model, from Configuration → Model Price.
Built-in default
Trodo's default price for that model.
Unset
Unknown model: cost left blank. Never a false $0.
In detail:
- Explicit
cost: a final USD number you send. Used as-is, never recomputed. - Token pricing: you send token counts and Trodo prices them, using your team's configured Model Price (Configuration → Model Price), then Trodo's built-in default, then left unset if the model is unknown.
Prices apply going forward. Changing a price affects new spans only; it does not retroactively recompute past runs.
Token categories
Tokens live in an open usage_details map. input and output are the defaults; you can also price cache_read, cache_write, reasoning, audio, image, or any custom key. Raw provider field names are accepted and normalised server-side:
| You send (raw provider field) | Normalised to |
|---|---|
prompt_tokens, promptTokenCount | input |
completion_tokens, candidatesTokenCount | output |
cache_read_input_tokens, prompt_tokens_details.cached_tokens, cachedContentTokenCount | cache_read |
cache_creation_input_tokens | cache_write |
reasoning_tokens | reasoning |
A custom category is only priced if its key matches a category you add in the Model Price UI.
Report cost from the SDK
The llm helper and auto-instrumentation capture usage for you, including cache and reasoning tokens, so most of the time you send nothing extra. When you need control, trackLlmCall and setLlm accept several forms.
// (a) Tokens only — priced from the model name; cache tokens captured automatically.
const answer = trodo.llm('answer', callAnthropic, {
model: 'claude-sonnet-4', provider: 'anthropic',
});
// (b) Raw provider usage object — auto-normalised.
await trodo.trackLlmCall({
model: 'gpt-4o', provider: 'openai',
usage: resp.usage, // { prompt_tokens, completion_tokens, prompt_tokens_details: { cached_tokens } }
prompt: body, completion: resp,
});
// (c) Explicit usage map + cache shorthands.
await trodo.trackLlmCall({
model: 'claude-sonnet-4', provider: 'anthropic',
usageDetails: { input: 1000, output: 500 },
cacheReadTokens: 200, cacheWriteTokens: 80,
});
// (d) Pin the final cost (skips server-side pricing).
await trodo.trackLlmCall({ model: 'gpt-4o', provider: 'openai', cost: 0.0123 });
// (e) Record a per-category cost breakdown.
await trodo.trackLlmCall({
model: 'gpt-4o', provider: 'openai',
costDetails: { input: 0.0003, output: 0.0005, cache_read: 0.00001 },
});
// Inside a manual span:
span.setLlm({ model: 'gpt-4o', provider: 'openai', usageDetails: { input: 1000, output: 500 }, cacheReadTokens: 200 });# (a) Tokens only — priced from the model name; cache tokens captured automatically.
answer = trodo.llm('answer', call_anthropic, model='claude-sonnet-4', provider='anthropic')
# (b) Raw provider usage object — auto-normalised.
trodo.track_llm_call(model='gpt-4o', provider='openai', usage=resp['usage'],
prompt=body, completion=resp)
# (c) Explicit usage map + cache shorthands.
trodo.track_llm_call(model='claude-sonnet-4', provider='anthropic',
usage_details={'input': 1000, 'output': 500},
cache_read_tokens=200, cache_write_tokens=80)
# (d) Pin the final cost (skips server-side pricing).
trodo.track_llm_call(model='gpt-4o', provider='openai', cost=0.0123)
# (e) Record a per-category cost breakdown.
trodo.track_llm_call(model='gpt-4o', provider='openai',
cost_details={'input': 0.0003, 'output': 0.0005, 'cache_read': 0.00001})
# Inside a manual span:
s.set_llm(model='gpt-4o', provider='openai',
usage_details={'input': 1000, 'output': 500}, cache_read_tokens=200)Fields
trackLlmCall / track_llm_call and setLlm / set_llm accept:
| Field (Node / Python) | Meaning |
|---|---|
model, provider | Used to match a price |
inputTokens / input_tokens, outputTokens / output_tokens | Scalar token counts |
cacheReadTokens / cache_read_tokens, cacheWriteTokens / cache_write_tokens | Folded into cache_read / cache_write |
usageDetails / usage_details | Open { category: count } map |
usage | Raw provider usage object (auto-extracted into the map) |
cost | Final USD cost. Highest priority; never recomputed |
costDetails / cost_details | Per-category USD breakdown, rolled into the run's cost breakdown |
To override how the llm helper reads usage, pass extractUsage (scalar in/out) or extractUsageMap (open map) in its options.
cost is the only field that overrides token pricing. cost_details records a per-category breakdown that rolls up into the run's total_cost_details, but it does not by itself pin the span's total. To set an exact total, send cost.
Configure model prices in the app
No code required. In the app, go to Configuration → Model Price.
- Add a model by name and set USD-per-1,000-token prices for any category: Input, Output, Cache Read, Cache Write, or a custom key. Match a model by exact name or by prefix, and optionally scope a rule to one provider (leave it blank to match any).
- Auto-detected models — models seen in your spans (last 30 days) with no rule appear automatically tagged Unpriced. Open one and set prices to start pricing it; nothing is stored until you save.
- Aliases and merge — fold model-name variants (for example
gpt-4o-2024-11-20) under one canonical model so they're priced and charted together. - Priority — when more than one rule could match, the most specific wins (exact over prefix, provider-specific over any-provider), with
prioritybreaking ties. - Drill-down — open a model to see its calls, cost, latency (average and p95), tokens, and error rate over time.
Models without a configured price fall back to Trodo's built-in defaults, so cost still appears out of the box. Configure prices only to override: negotiated rates, self-hosted or fine-tuned models, and cache pricing.
Via the API
The same rules are available at /api/config/model-prices for automation:
| Method | Path | Purpose |
|---|---|---|
GET | /api/config/model-prices | List your team's price rules |
POST | /api/config/model-prices | Create a rule |
PATCH | /api/config/model-prices/:id | Update a rule |
DELETE | /api/config/model-prices/:id | Delete a rule |
GET | /api/config/model-prices/metrics | Per-model usage time series |
A rule is { provider?, model_pattern, match_type, prices: { input, output, cache_read, … }, priority, aliases }. Every change is written to an append-only history for audit and rollback.
Run rollups
Each run sums its spans into total_cost, and merges the per-category breakdowns into total_cost_details, so a run's cost always matches the spans that explain it.
Next
- Add manual spans for
setLlmand thellmhelper - Raw HTTP / non-OTel clients for
trackLlmCall