API reference

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.

FieldTypeNotes
slugstringe.g. openai, anthropic
namestringDisplay name
websitestring | null
pricingUrlstring | nullThe provider's own pricing page
officialbooleanWhether this is the maker's own listing (see Introduction)
modelsnumberModel count for this provider

GET /api/v1/models

Paginated model list.

ParamTypeDefaultNotes
qstring—Free-text match against slug and display name
providerstring—Comma-separated provider slugs, e.g. openai,anthropic
confidencestring—One of verified, high, medium, low, unverified
limitnumber501–500
cursorstring—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.

ParamTypeDefaultNotes
qstring—Free-text match against slug and display name. Omit it for the head of the catalogue
limitnumber101–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.

ParamTypeDefaultNotes
daysnumber30Look-back window
limitnumber100Max rows (capped at 500)
providerstring—Only this provider's changes (its slug)
modelstring—Only this model's changes (its slug, without the provider)
qstring—Free text: searches by slug and by display name, of the model and of its provider (2026-09-17)
kindstringthe four market kindsOne 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):

LevelMeaning
verifiedConfirmed directly on the provider's own pricing page
highStrong agreement across independent sources
mediumSome agreement, or a single reasonably reliable source
lowWeak or partial evidence
unverifiedNot 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.

API reference