Pricing API
When the ChatGPT API launched, it was a simple price per token. Over time, LLM providers made pricing complicated: different prices for input and output tokens, batch discounts, prompt caching, reasoning tokens, tool fees, time-of-day pricing and more. A provider response tells you the usage. It does not tell you the cost. You have to do the math yourself. The same request can cost 2x more during peak hours or 10x less if you hit the cache.
The Metergraph Pricing API does the math. Send the original provider response and get the accurate cost, or look up a model’s current price. It uses the same catalog and resolver that price every call Metergraph ingests. For the same model, usage and price, it returns the figure the dashboard shows.
The API is free. You do not need an account.
Get started
Section titled “Get started”Check the supported models and pricing first, or go straight to a lookup when you already know the model and provider.
-
List the supported models and pricing.
curl https://app.metergraph.dev/v1/pricing/models -
Look up a price. Name the model and the provider or channel that bills it.
curl https://app.metergraph.dev/v1/pricing/lookup \-H 'Content-Type: application/json' \-d '{"model":"gpt-5","context":{"provider":"openai","region":"global"}}' -
Calculate a request’s cost. Send the original provider response as you received it. The API recognizes the provider’s format.
curl https://app.metergraph.dev/v1/pricing/calculate \-H 'Content-Type: application/json' \-d '{"trace": {"type": "message","model": "claude-sonnet-5","usage": {"input_tokens": 1000, "cache_read_input_tokens": 800, "output_tokens": 200}},"context": {"region": "global"}}'
The result includes the resolved price, the total cost, the component breakdown, the price’s source and effective date, and any missing dimensions that would make the answer more precise.
What the API reads
Section titled “What the API reads”Metergraph reads only the fields needed to calculate the price: the model, the provider, the usage counts, the region, the service tier and the timestamp. Prompt and response content is ignored and never stored. Every other field is discarded before the request is resolved. Request and response bodies are not logged. Send the provider response as you received it. You do not need to strip content first.
Coverage
Section titled “Coverage”The catalog currently covers more than 600 priced model, channel and region combinations. This includes direct providers, cloud platforms such as Bedrock and Vertex, and gateways such as Vercel AI Gateway and OpenRouter. The models endpoint is the live list.
Endpoints
Section titled “Endpoints”| Endpoint | Purpose |
|---|---|
GET /v1/pricing/models |
Supported models, their aliases, and the channels, regions, service tiers and pricing dimensions each one has a current price for. |
POST /v1/pricing/lookup |
The price in effect for a model, without usage. |
POST /v1/pricing/calculate |
The cost of one request, from a provider response or trace. |
The POST endpoints change nothing. They use POST only to carry a body, so a retry is always safe.
In the models listing, each price’s dimensions name the rates it has:
input, output, cache_read, cache_write_5m, cache_write_1h,
batch_input, batch_output and request_fee. Its service_tiers include
batch only when it has both batch rates.
Lookup
Section titled “Lookup”{ "model": "gpt-5", "context": { "provider": "openai", "channel": "openai-api", "region": "global", "service_tier": "standard", "at": "2026-10-19T12:00:00Z" }}Only model is required. Every context field is optional.
| Field | Meaning |
|---|---|
provider |
The company you called, such as openai or anthropic. It selects that provider’s direct channel. |
channel |
The channel that bills the call, such as aws-bedrock or vercel-ai-gateway. It takes precedence over provider, because a gateway and a direct provider can price the same model differently. |
region |
The billing region. Leave it out if you do not know it. The result tells you whether it mattered. |
service_tier |
standard or batch. A tier the catalog does not price gets the standard rate and is flagged. |
at |
An ISO 8601 timestamp. The price in effect at that time is used. Defaults to the time of the request. |
Calculate
Section titled “Calculate”{ "trace": { "...": "the provider response, unchanged" }, "context": { "region": "global" }}format is optional for a provider response: the API recognizes it from the
fields each provider always returns. Name it when the response is too sparse
to recognize, such as a body you rebuilt with only its usage. An OpenTelemetry
span or a Langfuse observation always needs its format.
| Format | Send |
|---|---|
openai.chat_completions |
An OpenAI Chat Completions response, or a gateway response in the same shape. |
openai.responses |
An OpenAI Responses API response. |
anthropic.messages |
An Anthropic Messages API response. |
gemini.generate_content |
A Google Gemini generateContent response. |
bedrock.converse |
An Amazon Bedrock Converse response. It does not name the model, so send model with it. |
otel.gen_ai |
One OpenTelemetry GenAI span, as OTLP/JSON or with attributes as a flat object. |
langfuse |
One Langfuse generation observation. |
model next to trace overrides the model the trace names.
Cached tokens are billed once, whichever way the response counts them. OpenAI and Gemini count cached tokens inside the input total, and gateways such as OpenRouter report cache writes beside cache reads. Each is billed at its own rate. Gemini thinking tokens are billed as output.
context takes the same fields as a lookup. A value in context overrides the
same value read from the trace. If context has no at, the trace’s own
timestamp is used. If the trace has none, the time of the request is used.
Each provider response format implies its provider. A gateway that relays
another company’s model, such as LiteLLM or OpenRouter serving Claude, still
returns a Chat Completions or Responses body in OpenAI’s shape. Set provider
or channel in context to the one that billed the call. For an OTel span or
a Langfuse observation the provider is read from the trace. Set it in
context if the trace does not say.
Several records in one request
Section titled “Several records in one request”Both POST endpoints also accept a records array. Each record has the shape of
a single request, and the response has one result per record, in order.
{"records": [ {"model": "gpt-5", "context": {"provider": "openai"}}, {"model": "claude-sonnet-5", "context": {"provider": "anthropic"}}]}Results
Section titled “Results”Every response carries a results array.
{ "results": [ { "status": "priced", "model_id": "anthropic/claude-sonnet-5", "price": { "channel": "anthropic-api", "region": "global", "currency": "USD", "input_per_mtok": "2.0", "output_per_mtok": "10.0", "cache_read_per_mtok": "0.2", "effective_from": "2026-06-30", "source_url": "https://platform.claude.com/docs/en/about-claude/pricing" }, "effective_at": "2026-10-20T12:00:00Z", "currency": "USD", "cost": "0.00416000", "known_subtotal": "0.00416000", "estimated_total": null, "token_basis": "exact", "catalog_hash": "<catalog SHA-256>", "retrieval": [], "components": { "input": "0.00200000", "output": "0.00200000", "cache_read": "0.00016000", "cache_write": "0.00000000", "request": "0.00000000", "search": "0" }, "missing_dimensions": [] } ]}The price above is abridged. The full object also lists the cache write, batch and per-request rates, the pricing rules, and the end of the price’s effective window.
| Field | Meaning |
|---|---|
status |
priced, partial or unpriced. |
model_id |
The catalog’s id for the model, not the alias you sent. |
price |
The applied price, with its channel, region, rates per million tokens, effective window and source. |
effective_at |
The time the price was resolved for. |
currency |
The ISO 4217 currency of every amount in the result. |
cost |
Calculate only. The complete total, or null when any part is unresolved. |
components |
Token, request and search amounts. Fully priced components sum to cost. |
known_subtotal |
Sum of exactly priced components; excludes token estimates. |
estimated_total |
Fallback estimate when tokens are partial and all retrieval costs are known; otherwise null. |
token_basis |
exact, fallback_estimate or unknown. |
catalog_hash |
SHA-256 identifying the catalog snapshot used. |
retrieval |
Counted retrieval operations, costs and supporting price sources. |
missing_dimensions |
What the answer depends on that the request did not say. |
Amounts are decimal strings, so no precision is lost in transit.
Partial and unpriced results
Section titled “Partial and unpriced results”partial means a price applied, but the figure depends on something the
request left out. unpriced means no price applied. In both cases
missing_dimensions names what the request would need to say:
| Dimension | Meaning |
|---|---|
region |
A global price was used, but regional billing differs. Send region for an exact figure. When only regional prices exist, the result is unpriced. |
channel |
No provider or channel identified a price, so the result is unpriced. Send provider or channel. |
input_tokens, output_tokens |
A usage count was missing. cost is null; any token fallback appears in estimated_total, not known_subtotal. |
service_tier |
The catalog does not price the requested tier, so the standard rate was used. |
model |
The trace did not name a model. |
An unknown model is not an error. It returns unpriced, so one record never
fails the rest of a batch.
Limits
Section titled “Limits”Metergraph may rate limit high-volume use. Every successful or rate-limited
response carries RateLimit-Limit, RateLimit-Remaining and
RateLimit-Reset headers. A rate-limited request returns 429 with
Retry-After.
A request body may be at most 8 MiB, and a request may carry at most 100 records.
Errors
Section titled “Errors”Every error has the same shape:
{"error": {"code": "invalid_request", "message": "body must be valid JSON", "request_id": "3f9a0c1d2b4e5f60"}}| Code | Status | Meaning |
|---|---|---|
invalid_request |
400 | The body is not valid JSON or a field has the wrong type. |
unsupported_trace_format |
400 | format is not one of the supported formats, or is missing for a trace the API does not recognize. |
too_many_records |
400 | The request carries more records than allowed. |
payload_too_large |
413 | The body is larger than allowed. |
rate_limited |
429 | The rate limit was reached. Retry after Retry-After seconds. |
pricing_unavailable |
503 | Pricing could not be served. Retry shortly. |
The codes are stable. The message text may change, so match on code.
Complete call pricing
Section titled “Complete call pricing”POST /v1/pricing/calculate includes Anthropic
web-search and web-fetch fees on anthropic-api. Observed retrieval on an
unsupported channel remains unresolved.
The response returns model_id once, at the result level. It adds components.search,
retrieval details with price IDs and sources, and catalog_hash.
costis set only for a fully priced call.known_subtotalcontains exact components.estimated_totalseparately includes a token fallback when all retrieval fees are known.- Missing required charges produce
partial; an unknown model staysunpriced.
Retrieval counts must be nonnegative integers. Langfuse accepts
usageDetails.web_search_requests and usageDetails.web_fetch_requests.
OpenTelemetry accepts the Metergraph extension attributes
gen_ai.usage.web_search_requests and gen_ai.usage.web_fetch_requests.