openai_cost_calculator
GitHub

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')
If you have an OpenAI-style total that includes cached tokens, convert it with 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 idAliasesPricing source
openaiOpenAI pricing docs (Markdown rendition)
anthropicclaudeClaude API pricing docs (Markdown rendition)
geminigoogle-gemini, google-ai-studioGemini API pricing docs (Markdown rendition)
vertexvertex-ai, google-vertexVertex AI generative AI pricing page
azureazure-openai, azure-aiAzure Retail Prices API
bedrockaws-bedrock, amazon-bedrockAWS Price List API
openrouterOpenRouter models API
deepseekDeepSeek API docs
togethertogether-aiTogether serverless model catalog
groqGroqCloud models page
fireworksfireworks-aiFireworks serverless pricing docs
deepinfraDeepInfra model list API
mistralmistral-aiMistral 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

FieldMeaning
totalExact Decimal total in USD (rounded(8) for display)
itemsLine items: dimension, quantity, unit price, cost
provider, resolved_model, canonical_modelWho billed it and which offering was priced
conditionsService tier, region and period in effect
min_input_tokensLong-context tier applied (0 for the base tier)
assumptionsDefaults that affected the price
source_url, verified_atProvenance 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.

ErrorMeaning
UnknownProviderErrorLists the supported providers
UnknownModelErrorIncludes "did you mean" suggestions
AmbiguousModelErrorAn identifier matches several offerings; lists them
PricingUnavailableErrorThe model exists, but not for the requested tier, region or date
MissingRateErrorUsage contains a dimension the model has no price for
UsageErrorNegative, 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, ...?
Missing rates are never silently replaced, with two deliberate exceptions: cache reads and writes fall back to the input rate, and reasoning tokens to the output rate.

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.

FunctionDescription
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.
The provider-agnostic API never uses the network; it reads the catalog bundled with the package.

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)
APIWhat it tracks
CostTrackerStateful 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_costTotal Decimal cost for calls made inside that turn.
turn.cost_by_modelPer-model Decimal totals for the turn.
tracker.session_totalRunning Decimal total across the session.
For streaming calls, pass 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.

Send 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

  1. Check openai-cost-calculator pricing models PROVIDER; the weekly sync adds new official models automatically.
  2. Upgrade the package to pick up the latest catalog.
  3. 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.