Exact, auditable LLM API costs across 13 providers
v1.3.0 prices usage for OpenAI, Anthropic, Google Gemini, Vertex AI, Azure OpenAI, Amazon Bedrock, OpenRouter, DeepSeek, Together AI, Groq, Fireworks AI, DeepInfra and Mistral — with Decimal precision and prices synced automatically from each provider's official pricing API or documentation.
Overview
Application code names a provider and a model. Everything provider-specific — model id formats, global vs regional endpoints, batch/flex/priority tiers, cache-write TTLs, long-context thresholds, peak hours and scheduled price changes — is resolved by the library.
- Provider-agnostic API:
calculate_cost,estimate_response_cost,compare_costs - Itemized results with the conditions applied, any assumptions, and the official source of every price
- Rich pricing model: cache reads and 5-minute/1-hour cache writes, audio, image and reasoning tokens, tool calls
- Bundled catalog: no network calls on the pricing path; deterministic and offline-friendly
- Automated, fail-closed updates from official sources, reviewed through pull requests
- OpenAI response API (
estimate_cost,estimate_cost_typed) unchanged - Agent tooling:
CostTracker, a local accounting proxy, Claude Code & Codex status lines
Installation
pip install openai-cost-calculator
Package name on PyPI uses dashes; import name uses underscores. The pricing catalog ships inside the package.
Quick start
from openai_cost_calculator import calculate_cost
cost = calculate_cost(
provider="aws-bedrock",
model="anthropic/claude-opus-5-5",
input_tokens=12_000,
output_tokens=800,
)
print(cost.total) # Decimal('0.064')
print(cost.conditions) # {'service_tier': 'standard', 'region': 'global'}
print(cost.assumptions) # ('assumed region=global (pass region=... to price another option)',)
print(cost.source_url) # the official source the price came from
The same response can cost different amounts depending on who bills it:
from openai_cost_calculator import estimate_response_cost
message = { # an Anthropic Messages API response (dict or SDK object)
"type": "message",
"model": "claude-sonnet-5",
"usage": {"input_tokens": 400_000, "cache_read_input_tokens": 0, "output_tokens": 0},
}
estimate_response_cost(message, provider="anthropic").total # Decimal('0.8')
estimate_response_cost(message, provider="bedrock").total # Decimal('0.8') global endpoint
estimate_response_cost(message, provider="vertex", region="us").total # Decimal('0.88') multi-region +10%
Price a request
Token counts are disjoint: input_tokens excludes cache reads
(cached_input_tokens) and cache writes (cache_write_tokens, or
cache_write_1h_tokens for Anthropic's 1-hour cache).
from openai_cost_calculator import calculate_cost
cost = calculate_cost(
provider="anthropic",
model="claude-opus-5-5",
input_tokens=10_000,
cached_input_tokens=100_000,
cache_write_tokens=10_000,
output_tokens=2_000,
)
for item in cost.items:
print(item.dimension, item.quantity, item.unit_price, item.cost)
# input 10000 4 0.04
# cached_input 100000 0.2 0.02
# cache_write 10000 5 0.05
# output 2000 20 0.04
print(cost.total) # Decimal('0.15')
For audio, image or reasoning tokens and tool calls, pass a Usage:
from openai_cost_calculator import Usage, calculate_cost
usage = Usage(input_tokens=2_000, input_audio_tokens=50_000, output_audio_tokens=20_000)
calculate_cost("openai", "gpt-realtime-2.1", usage=usage).total # Decimal('2.888')
Usage.from_totals(total_input_tokens=..., cached_input_tokens=..., output_tokens=...).
Long-context tiers
Several providers charge a higher rate for every token once a request's input crosses a threshold
(OpenAI ≥272K, Gemini and Vertex >200K). Cached and cache-write tokens count toward the threshold, and
cost.min_input_tokens shows which tier applied:
cost = calculate_cost("openai", "gpt-6-sol", input_tokens=300_000)
cost.total, cost.min_input_tokens # (Decimal('1.2'), 272000) — $4/1M instead of $2/1M
Tiers, regions & time
from datetime import date, datetime, timezone
calculate_cost("openai", "gpt-6-luna", input_tokens=100_000, service_tier="batch") # Decimal('0.005') batch tier
calculate_cost("anthropic", "claude-sonnet-5", input_tokens=1_000_000, region="us") # Decimal('2.2') inference_geo=us
calculate_cost("azure", "gpt-6-astra", input_tokens=100_000, region="data-zone") # Decimal('1.1') Data Zone deployment
calculate_cost("bedrock", "us.anthropic.claude-opus-5-5", input_tokens=1_000) # regional rate, explained in assumptions
calculate_cost("vertex", "gemini-3.8-flash", input_tokens=1_000_000, region="us-central1") # non-global Gemini price
calculate_cost("gemini", "gemini-3.8-flash", input_tokens=1_000_000, at=date(2027, 1, 2)) # Decimal('1.5') scheduled price
calculate_cost("deepseek", "deepseek-flash", input_tokens=1_000_000,
at=datetime(2026, 9, 26, 8, 0, tzinfo=timezone.utc)) # Decimal('0.15') off-peak
When you don't specify a condition that affects the price, the provider default is used and reported in
cost.assumptions. When a condition is unavailable, you get an error listing the alternatives
instead of a guess:
PricingUnavailableError: bedrock/anthropic.claude-opus-5-5 has no price for region=ap-south-1, service_tier=standard;
available: region=eu-central-1; region=global; region=us-east-1; region=us-west-2
OpenAI's "Fast mode" can be requested as service_tier="fast" or "priority".
Price a provider response
estimate_response_cost reads usage from any supported wire format — OpenAI Chat Completions and
Responses (also used by Azure, OpenRouter, Groq, Together, Fireworks, DeepInfra and Mistral), DeepSeek,
Anthropic Messages, Gemini usageMetadata and Bedrock Converse — as SDK objects or JSON dicts.
The billing provider is passed separately, because the same format is billed differently by different providers.
from openai import OpenAI
from openai_cost_calculator import estimate_response_cost
client = OpenAI()
resp = client.responses.create(model="gpt-6-sol", input="Summarize the release notes")
cost = estimate_response_cost(resp, provider="openai") # cache writes and reasoning tokens itemized
estimate_response_cost(converse_response, provider="bedrock", model="anthropic.claude-sonnet-5")
estimate_response_cost(gemini_response, provider="gemini")
Compare providers, inspect prices
from openai_cost_calculator import Usage, compare_costs, get_model_pricing, list_models
for cost in compare_costs("anthropic/claude-opus-5-5", usage=Usage(input_tokens=50_000, output_tokens=2_000)):
print(cost.provider, cost.resolved_model, cost.total)
# anthropic claude-opus-5-5 0.24
# bedrock anthropic.claude-opus-5-5 0.24
# openrouter anthropic/claude-opus-5.5 0.24
# vertex claude-opus-5-5 0.24
get_model_pricing("bedrock", "global.anthropic.claude-opus-5-5").prices # every tier and region
list_models("gemini")
Supported providers
| Provider id | Aliases | Pricing source |
|---|---|---|
openai | OpenAI pricing docs (Markdown rendition) | |
anthropic | claude | Claude API pricing docs (Markdown rendition) |
gemini | google-gemini, google-ai-studio | Gemini API pricing docs (Markdown rendition) |
vertex | vertex-ai, google-vertex | Vertex AI generative AI pricing page |
azure | azure-openai, azure-ai | Azure Retail Prices API |
bedrock | aws-bedrock, amazon-bedrock | AWS Price List API |
openrouter | OpenRouter models API | |
deepseek | DeepSeek API docs | |
together | together-ai | Together serverless model catalog |
groq | GroqCloud models page | |
fireworks | fireworks-ai | Fireworks serverless pricing docs |
deepinfra | DeepInfra model list API | |
mistral | mistral-ai | Mistral API pricing page |
Model identifiers are accepted in each provider's own format — for example
us.anthropic.claude-opus-5-5 (Bedrock inference profile), publishers/anthropic/models/claude-opus-5-5
(Vertex), models/gemini-3.8-flash (Gemini API) or canonical ids such as
anthropic/claude-opus-5-5. Unknown models get "did you mean" suggestions.
The Cost object
| Field | Meaning |
|---|---|
total | Exact Decimal total in USD (rounded(8) for display) |
items | Line items: dimension, quantity, unit price, cost |
provider, resolved_model, canonical_model | Who billed it and which offering was priced |
conditions | Service tier, region and period in effect |
min_input_tokens | Long-context tier applied (0 for the base tier) |
assumptions | Defaults that affected the price |
source_url, verified_at | Provenance of the price |
to_breakdown() | Projection onto the three-bucket CostBreakdown |
Error handling
All pricing errors derive from PricingError (a ValueError) and say what to do next.
| Error | Meaning |
|---|---|
UnknownProviderError | Lists the supported providers |
UnknownModelError | Includes "did you mean" suggestions |
AmbiguousModelError | An identifier matches several offerings; lists them |
PricingUnavailableError | The model exists, but not for the requested tier, region or date |
MissingRateError | Usage contains a dimension the model has no price for |
UsageError | Negative, non-integer or inconsistent usage |
from openai_cost_calculator import PricingError, calculate_cost
try:
calculate_cost("openai", "gpt-6-sun", input_tokens=1_000)
except PricingError as e:
print(e)
# no pricing for model 'gpt-6-sun' on provider 'openai'; did you mean: gpt-6-luna, gpt-6-sol, ...?
OpenAI response API
The original API for OpenAI SDK responses is unchanged and remains supported.
from openai import OpenAI
from openai_cost_calculator import estimate_cost, estimate_cost_typed
client = OpenAI()
resp = client.chat.completions.create(
model="gpt-6-luna",
messages=[{"role": "user", "content": "Hi there!"}],
)
cost = estimate_cost_typed(resp)
print(cost.total_cost) # Decimal, e.g. 1,200 in / 300 out → Decimal('0.00027')
print(estimate_cost(resp)) # legacy 8-decimal strings
# {'prompt_cost_uncached': '0.00012000',
# 'prompt_cost_cached' : '0.00000000',
# 'completion_cost' : '0.00015000',
# 'total_cost' : '0.00027000'}
Both functions accept Chat Completions, Responses API objects and streams.
CostBreakdown
CostBreakdown(
prompt_cost_uncached: Decimal,
prompt_cost_cached: Decimal,
completion_cost: Decimal,
total_cost: Decimal
)
Use .as_dict(stringify=True|False) for 8-dp strings or raw Decimals. The provider-agnostic
Cost itemizes cache writes and audio separately; Cost.to_breakdown() converts it to this shape.
Streaming
Pass the stream directly; the last chunk that carries .usage is used.
stream = client.chat.completions.create(
model="gpt-6-sol",
messages=[{"role": "user", "content": "Hi!"}],
stream=True,
stream_options={"include_usage": True},
)
print(estimate_cost_typed(stream).total_cost)
Pricing utilities (OpenAI response API)
The OpenAI response API reads the published data/gpt_pricing_data.csv (refreshed at most every 24 hours).
That file is generated from the catalog, so it is kept current by the same official-source pipeline.
If it cannot be downloaded, the last good copy and then the bundled catalog are used instead of failing.
| Function | Description |
|---|---|
refresh_pricing() | Re-download the CSV now. |
set_offline_mode(True) | Never touch the network; only local overrides are used. |
add_pricing_entry(name, date, …) | Add or override one row; supports minimum_tokens tiers. |
add_pricing_entries([...]) | Bulk add or override rows. |
clear_local_pricing() | Drop all in-process overrides. |
Tracking agent turns
Use CostTracker when one user-visible turn makes many OpenAI calls and you want a running total.
from openai import OpenAI
from openai_cost_calculator import CostTracker
tracker = CostTracker()
client = tracker.wrap(OpenAI())
with tracker.turn("Refactor auth") as turn:
client.chat.completions.create(
model="gpt-6-luna",
messages=[{"role": "user", "content": "Plan the refactor"}],
)
client.responses.create(
model="gpt-6-sol",
input=[{"role": "user", "content": "Summarize the changes"}],
)
print(turn.total_cost)
print(turn.cost_by_model)
print(tracker.session_total)
| API | What it tracks |
|---|---|
CostTracker | Stateful tracker for a whole session. |
tracker.wrap(client) | Wraps Chat Completions and Responses API calls on an OpenAI SDK client. |
tracker.turn(label) | Creates a Turn context for one visible agent turn. |
turn.total_cost | Total Decimal cost for calls made inside that turn. |
turn.cost_by_model | Per-model Decimal totals for the turn. |
tracker.session_total | Running Decimal total across the session. |
stream_options={"include_usage": True} so the final usage chunk can be costed.Zero-config tracking with the proxy
Run the local proxy and point any OpenAI-compatible agent, IDE or SDK client at it.
pip install openai-cost-calculator[proxy]
openai-cost-calculator proxy --port 8100 --auth-mode api-key
Set the client base_url to http://127.0.0.1:8100/v1 and read accumulated costs at
http://127.0.0.1:8100/_occ/costs. --auth-mode auto routes Codex ChatGPT logins to the
ChatGPT backend and API keys to the OpenAI Platform; --upstream URL selects a trusted OpenAI-compatible upstream.
For Claude Code, run openai-cost-calculator proxy --port 8100 --protocol anthropic-messages; Anthropic
Messages traffic is priced with separate cache-write and cache-read rates.
Add --database PATH for a durable SQLite ledger that survives restarts and can be shared by several proxy processes.
X-OCC-Session to separate agent sessions and X-OCC-Turn to group calls within a visible turn.
The proxy relays streaming and WebSocket traffic unchanged; accounting is a side effect and never alters a response.
The full guide is in docs/INTEGRATIONS.md.
Show cost inside Claude Code & Codex
Claude Code
Proxy-backed status line and lifecycle hooks, with current-turn and session totals:
openai-cost-calculator claude install --proxy-url http://127.0.0.1:8100
openai-cost-calculator proxy --port 8100 --protocol anthropic-messages
OCC Turn $0.0124 · Session $0.0837
Subscription (OAuth) sessions are labelled API-eq, because token pricing is an API-equivalent estimate
rather than an amount billed. openai-cost-calculator claude status shows the same totals from the CLI.
The lighter transcript-based adapter reads Claude Code's own reported cost:
openai-cost-calculator install claude-code
💰 $0.0123 session · last 8.5k->1.2k tok (cache 2.0k) · Opus 5.5 · ctx 14%
💰 This turn cost $0.0041 (12.3k in / 1.1k out)
Codex
openai-cost-calculator install codex --proxy-url http://127.0.0.1:8100 --session default
openai-cost-calculator proxy --port 8100 --auth-mode auto
The installer adds a managed ~/.codex/config.toml block with an openai_cost_calculator
provider using wire_api = "responses" and Codex's existing ChatGPT or API-key login. Add
--websockets to opt in to WebSocket transport. Each agent-turn-complete notification
consumes one checkpoint:
💰 $0.0188 session · last $0.0032
💰 Turn $0.0032 · gpt-6-sol 1.2k->500
Uninstall
openai-cost-calculator claude uninstall
openai-cost-calculator uninstall claude-code
openai-cost-calculator uninstall codex
Automated pricing updates
Prices are data, not code. A weekly GitHub Actions job fetches every provider's official source, parses it, validates it, diffs it against the checked-in catalog and applies only changes that pass every safety check. Anything ambiguous is left unchanged and listed for review in the pull request it opens.
- Extraction guard: a source that suddenly returns far fewer models is treated as broken.
- Validation and sanity checks catch unit errors and implausible values.
- Magnitude guard: price moves beyond 3× need review.
- Corroboration: an independent community table can hold a suspicious change back.
- Removals are never automatic; additions only from official sources.
Details: docs/PRICING_UPDATES.md.
Command line
openai-cost-calculator pricing cost bedrock us.anthropic.claude-opus-5-5 --input 12000 --output 800
openai-cost-calculator pricing models anthropic
openai-cost-calculator pricing providers
openai-cost-calculator pricing sync --provider openai --dry-run # maintainers
openai-cost-calculator pricing stale --max-age-days 45
openai-cost-calculator pricing validate
Cookbook — custom model prices
Price any model, including self-hosted ones, by passing your own catalog to the provider-agnostic API:
from decimal import Decimal
from openai_cost_calculator import PricingCatalog, calculate_cost
from openai_cost_calculator.catalog import ModelPricing, PriceSet, ProviderPricing, Source
inhouse = ProviderPricing(
provider="openai", # reuse OpenAI's identifier handling
sources=(Source(id="contract", kind="manual", url="https://example.com/contract"),),
models=(
ModelPricing(id="gpt-6-sol", source="contract", prices=(
PriceSet(rates={"input": Decimal("1.6"), "cached_input": Decimal("0.16"), "output": Decimal("8")}),
PriceSet(rates={"input": Decimal("3.2"), "cached_input": Decimal("0.32"), "output": Decimal("12")},
min_input_tokens=272_000),
)),
),
)
calculate_cost("openai", "gpt-6-sol", input_tokens=10_000, catalog=PricingCatalog([inhouse]))
Or teach the OpenAI response API a price row:
from openai_cost_calculator import add_pricing_entry
add_pricing_entry("ollama/qwen3.8:27b", "2026-09-01",
input_price=0.20, output_price=0.60, cached_input_price=0.04)
add_pricing_entry("my-gpt-6-sol-deployment", "2026-09-01",
input_price=4.00, output_price=15.00, cached_input_price=0.40,
minimum_tokens=272000) # long-context tier
Limitations
- Prices are list prices; negotiated discounts, credits and free tiers are not modelled.
- Non-token billing (per image, per second, per character, provisioned throughput, storage) is out of scope.
- Regional prices cover the configured regions (Bedrock: us-east-1, us-west-2, eu-central-1; Azure regional: eastus2); others raise
PricingUnavailableError. - DeepSeek's off-peak calendar does not model Chinese public holidays.
- Cross-provider comparison is exact for first-party models and best-effort for open-weight models.
Troubleshooting
UnknownModelError after a new model launch
- Check
openai-cost-calculator pricing models PROVIDER; the weekly sync adds new official models automatically. - Upgrade the package to pick up the latest catalog.
- Meanwhile, pass a custom catalog (see the cookbook) or open an issue.
The cost looks too high
Check cost.min_input_tokens (a long-context tier may apply), cost.conditions (region or tier)
and cost.items. Remember that input_tokens excludes cached tokens.
PricingUnavailableError
The model exists but not under the requested conditions; the message lists the available tiers, regions and dates.
Streaming usage is missing
Request final usage with stream_options={"include_usage": True}.
License & contributions
MIT License © 2025 Orkun Kınay & Murat Barkın Kınay. Adding a provider is documented in docs/ADDING_A_PROVIDER.md.