Overview
The public API lives under /api/v1. It is read-only, requires no API key or
authentication, and is rate-limited per client IP (300 requests/minute by
default). CORS is wide open (Access-Control-Allow-Origin: *) — this is a
public dataset with no cookies or credentials to protect.
curl https://aiapipricing.slxd.app/api/v1/models
Response envelope
Every successful response is:
{
"data": /* the endpoint's payload */,
"meta": {
"datasetVersion": "2026-08-19T03:00:00.000Z",
"generatedAt": "2026-08-19T03:00:00.000Z"
/* endpoint-specific fields, e.g. count, nextCursor */
}
}
meta.datasetVersion is the timestamp of the ingestion run that produced the
data — the same value as the x-dataset-version response header. Two
responses with the same datasetVersion were computed from the same run and
are safe to compare directly.
Caching
Responses carry a weak ETag covering both the dataset version and the exact
query string, plus Cache-Control: public, max-age=<n>, s-maxage=3600, stale-while-revalidate=86400. Send If-None-Match on repeat requests — a
match returns 304 Not Modified with no body, which is far cheaper than
re-fetching. max-age varies per endpoint (see below); a single model or the
source registry, which change less often, cache longer than the model list.
Errors
Errors are RFC 9457 problem details
(Content-Type: application/problem+json):
{ "type": "about:blank", "title": "Model not found", "status": 404, "detail": "openai/made-up-model" }
A 429 additionally carries a Retry-After header.
Endpoints
GET /api/v1/meta
Dataset summary: counts and the last ingestion run. Useful as a health check
or to read the current datasetVersion without fetching anything else.
max-age=60.
{
"data": {
"datasetVersion": "2026-08-19T03:00:00.000Z",
"generatedAt": "2026-08-19T03:00:00.000Z",
"models": 842,
"providers": 37,
"prices": 3110,
"lastRun": { "id": "128", "status": "ok", "finishedAt": "2026-08-19T03:00:00.000Z" },
"rates": { "contractVersion": 2, "officialSources": ["official_scrape"] }
}
}
GET /api/v1/providers
Every provider in the catalogue, with its model count.
These are the houses that make the models. A reseller — Vertex, Bedrock, Azure, OpenRouter, Alibaba's catalogue, a subscription plan — is a sales channel, not a provider, and is not listed here: the same model is one model, sold at several prices.
| Field | Type | Notes |
|---|---|---|
slug | string | e.g. openai, anthropic |
name | string | Display name |
website | string | null | |
pricingUrl | string | null | The provider's own pricing page |
official | boolean | Whether this is the maker's own listing (see Introduction) |
models | number | Model count for this provider |
GET /api/v1/models
Paginated model list.
| Param | Type | Default | Notes |
|---|---|---|---|
q | string | — | Free-text match against slug and display name |
provider | string | — | Comma-separated provider slugs, e.g. openai,anthropic |
confidence | string | — | One of verified, high, medium, low, unverified |
limit | number | 50 | 1–500 |
cursor | string | — | Opaque cursor from a previous response's meta.nextCursor |
Pagination is keyset-based (never offset), so it stays fast past thousands of
rows. meta.nextCursor is null on the last page.
curl "https://aiapipricing.slxd.app/api/v1/models?provider=anthropic&limit=20"
Each model in data has this shape (same shape returned by GET /api/v1/models/{provider}/{model} and GET /api/v1/compare):
{
"id": "anthropic/claude-sonnet-4-5",
"provider": "anthropic",
"slug": "claude-sonnet-4-5",
"name": "Claude Sonnet 4.5",
"family": "claude-4",
"contextWindow": 200000,
"maxOutputTokens": 64000,
"openWeights": false,
"firstSeenAt": "2026-02-11T00:00:00.000Z",
"lifecycle": { "state": "active", "releasedOn": "2026-02-11", "deprecatedOn": null, "retiresOn": null },
"prices": [
{
"label": "Input",
"direction": "input",
"modality": "text",
"tier": "standard",
"cache": "none",
"contextMin": 0,
"contextMax": null,
"qualifiers": {},
"amount": 3,
"displayUnit": "per 1M tokens",
"denomination": "currency",
"currency": "USD",
"confidence": "verified",
"confidenceScore": 0.95,
"sources": ["anthropic-pricing-page"],
"dissenting": [],
"disputed": false,
"fallbackAmount": null,
"effectiveFrom": "2026-02-11T00:00:00.000Z",
"staleSince": null
}
]
}
amount is always in display units (e.g. dollars per million tokens),
never raw base units — a caller comparing 3 against 0.000003 has already
lost.
Two price dimensions can share a label — web search is priced per context
size, and all three are called "Web search" — so the ones that would collide
in the same model are disambiguated with their qualifiers: Web search (context size: high). The raw qualifiers object is still there for
structured use.
GET /api/v1/models/search
Typeahead suggestions: the same filter and order as GET /api/v1/models, but
only what a picker needs — the id and how it reads. Use it when you are
completing a model name; use /api/v1/models when you want the prices.
| Param | Type | Default | Notes |
|---|---|---|---|
q | string | — | Free-text match against slug and display name. Omit it for the head of the catalogue |
limit | number | 10 | 1–50 |
{
"data": [
{ "id": "anthropic/claude-sonnet-4-5", "label": "Claude Sonnet 4.5 · Anthropic" }
],
"meta": { "count": 1 }
}
The label carries the provider because the same model is listed by several gateways, and the bare name repeats.
GET /api/v1/models/{provider}/{model}
A single model by provider slug and model slug, e.g.
/api/v1/models/anthropic/claude-sonnet-4-5. 404 (problem detail) when
unknown. max-age=3600.
By default the prices are the maker's own. Add ?channel= to get what a reseller charges for the same model instead — one of direct, vertex, bedrock, azure, alibaba, openrouter, coding_plan, token_plan, other. A channel that does not sell this model returns the model with an empty prices array; an unknown channel is a 400.
curl "https://aiapipricing.slxd.app/api/v1/models/anthropic/claude-opus-5?channel=bedrock"
GET /api/v1/models/{provider}/{model}/history
That model's price history — one point per change, not one per day,
since prices are step functions. Add from / to (ISO dates, inclusive) to
narrow the range. max-age=3600.
One series per price dimension, not per label: two dimensions can share a
name (web search is priced per context size, and all three are called "Web
search"), so series that would collide are disambiguated with their qualifiers
— Web search (context size: high). Within a series, consecutive points with
the same amount are folded into the first: repeating an amount is not a
change.
Each point is [date, amount], the date as YYYY-MM-DD. When a price changed
twice on the same day, both points are returned with the same date, in order:
folding them would hide a price change that really happened.
{
"data": [
{ "label": "Input", "displayUnit": "per 1M tokens", "points": [["2026-02-11", 3], ["2026-06-01", 2.5]] }
],
"meta": { "model": "anthropic/claude-sonnet-4-5", "granularity": "change" }
}
GET /api/v1/compare?models=
Side-by-side prices for 2–8 models, comma-separated as provider/slug:
curl "https://aiapipricing.slxd.app/api/v1/compare?models=openai/gpt-5,anthropic/claude-opus-5"
data is an array of the same model shape as /api/v1/models, in the order
found (unknown ids are silently dropped — check meta.found against
meta.requested). 400 if fewer than two ids are given. Amounts billed in
credits are never converted to money, so a credit-billed model isn't
comparable to a token-billed one by raw amount.
GET /api/v1/changes
Recent market moves, newest first: increases, decreases, removals and unit
changes. The JSON equivalent of /changes.xml, with the
same filters.
By default it does not include catalogue additions (price_added,
coverage_added, reference_changed): those are "New in the catalogue", not
a price change, and only show up here if requested explicitly via kind.
| Param | Type | Default | Notes |
|---|---|---|---|
days | number | 30 | Look-back window |
limit | number | 100 | Max rows (capped at 500) |
provider | string | — | Only this provider's changes (its slug) |
model | string | — | Only this model's changes (its slug, without the provider) |
q | string | — | Free text: searches by slug and by display name, of the model and of its provider (2026-09-17) |
kind | string | the four market kinds | One or several, comma-separated: price_increase, price_decrease, price_removed, unit_change, price_added, coverage_added, reference_changed. An unknown value is 400. |
{
"data": [
{
"kind": "price_increase",
"model": "openai/gpt-5",
"modelName": "GPT-5",
"providerName": "OpenAI",
"label": "Output",
"oldAmount": 10,
"newAmount": 12,
"deltaPct": 20,
"occurredAt": "2026-08-01T00:00:00.000Z"
}
]
}
GET /api/v1/sources
Every configured data source and its health — weight, licence, whether it's
authoritative, when it last succeeded, and its last error if any. This is how
to judge how much to trust a price beyond the per-price confidence field.
max-age=900.
{
"data": [
{
"source": "anthropic-pricing-page",
"name": "Anthropic pricing page",
"license": null,
"homepage": "https://www.anthropic.com/pricing",
"weight": 10,
"authoritative": true,
"lastSuccessAt": "2026-08-19T03:00:00.000Z",
"lastError": null,
"rowsLastRun": 24
}
]
}
GET /api/v1/rates
The cost rate of every billable model, narrowed to what a biller needs:
text tokens, standard tier, no cache, no context bracket — one number per
direction. This is the endpoint the SLXD suite consumes to charge AI credits;
the margin is the platform's (cost × 2 with a floor of 1 credit per operation), only
the fact lives here. max-age=3600.
Optional ?provider=anthropic,openai filter (up to 50). Unpaginated on
purpose: the consumer wants it whole and the ETag keeps it from being resent
while the dataset is unchanged. A model missing either direction is left out —
absent beats half-priced.
{
"data": [
{
"id": "anthropic/claude-opus-5",
"provider": "anthropic",
"slug": "claude-opus-5",
"currency": "USD",
"inputPerMTok": 15,
"outputPerMTok": 75,
"cachedInputPerMTok": 1.5,
"confidence": "verified",
"confidenceScore": 95,
"agreeingSources": ["models_dev", "official_scrape"],
"sourceCount": 2,
"official": true,
"disputed": false,
"staleSince": null,
"aliases": ["claude-opus-5-20260101"]
}
]
}
Contract version 2 (2026-09-17). Every rate now also states where it comes
from, so whoever bills with it can decide by policy: confidenceScore
(0-100), agreeingSources (the sources backing both directions, not the
union of the two), sourceCount (how many spoke about the weaker leg),
official (the provider's own pricing page is among them) and disputed.
These are new fields: nothing was renamed, and a version 1 client still
reads what it always did. GET /api/v1/meta returns the contract version and
which sources count as official (rates.contractVersion,
rates.officialSources), so nobody has to hardcode that table.
The endpoint does not filter by confidence: it publishes the whole fact and
the policy belongs to the consumer. The SLXD suite, since 2026-09-17, only
charges credits against verified or official rates and falls back to its
static catalogue for the rest.
Confidence levels
Every price carries a confidence and a confidenceScore, plus which
sources agree (sources) and dissent (dissenting):
| Level | Meaning |
|---|---|
verified | Confirmed directly on the provider's own pricing page |
high | Strong agreement across independent sources |
medium | Some agreement, or a single reasonably reliable source |
low | Weak or partial evidence |
unverified | Not yet corroborated — treat with caution |
disputed: true means at least one source actively disagrees with the
published amount; fallbackAmount (when present) is what the catalogue
would show if the primary source failed.
See also
The same catalogue is queryable by an AI assistant through the MCP connector, which mirrors these endpoints one for one so a model never quotes a different price than this API.