Preview build — figures marked “✓” or in green are sourced (benchmarks by Artificial Analysis, MTEB and Open ASR); treat everything else (value scores, “Est.” figures, unmarked prices and changes) as illustrative. Methodology

API

One request, the whole price book.

Every price, benchmark and provenance column the site renders is available as JSON and CSV, from the same serializer that builds the pages — so the API cannot disagree with what you see in the tables. No key, no signup, no tracking. 10 requests per day per IP, for reasons set out below.

Rows
4325
Providers
224
Markets
6
Snapshot
2026-08-03

Endpoints

GET/api/v1/modelsJSON · 93 KB gzipped

The full catalogue in one object: a documented envelope plus one row per priced offering.

GET/api/v1/models.csvCSV · 67 KB gzipped

The same rows, flat. A leading # comment line carries the snapshot id and the required attribution — pass comment='#' to your reader.

GET only, over HTTPS. CORS is open (access-control-allow-origin: *), so you can call this straight from a browser. Responses are gzipped, which is the figure that matters: the JSON is 3.0 MB of text but 93 KB over the wire. There is no pagination, no per-model endpoint and no query filtering — splitting a payload that size would only cost you requests.

Quick start

Fetch it:

curl -s https://modelpricebook.com/api/v1/models
curl -sL https://modelpricebook.com/api/v1/models.csv -o model-price-book.csv

Load it into pandas and rank the cheapest sourced language models:

import pandas as pd

df = pd.read_csv("https://modelpricebook.com/api/v1/models.csv", comment="#")

# Only quote what is sourced — the rest is illustrative.
sourced = df[df.price_sourced]

# Cheapest sourced LLM by blended token price.
llm = sourced[sourced.category == "llm"].copy()
llm["blended"] = llm.price_input_per_1m + llm.price_output_per_1m
print(llm.nsmallest(5, "blended")[["name", "provider", "blended"]])

Or from JavaScript:

const res = await fetch("https://modelpricebook.com/api/v1/models");
const book = await res.json();

// Cache on snapshot_id: unchanged id means byte-identical data.
console.log(book.snapshot_id, book.model_count);

const cheapestVideo = book.models
  .filter((m) => m.category === "video" && m.price_sourced)
  .sort((a, b) => a.price_per_second - b.price_per_second)[0];

What you get

The dataset answers one question across six markets: what does this model cost, who is actually selling it, how good is it, and how do we know. Today that is 4325 priced offerings from 224 providers, 882 of 1133 tracked models carrying a sourced price and 425 carrying a sourced benchmark score.

Language Modelsper 1M tokens705 models · 171 benchmarked
Video Generationper second114 models · 77 benchmarked
Text to Speechper 1K chars6 models · 5 benchmarked
Image Generationper image177 models · 145 benchmarked
Embeddingsper 1M tokens85 models · 25 benchmarked
Speech to Textper audio minute46 models · 2 benchmarked

The envelope

JSON responses wrap the rows in the terms you need to use them safely:

datasetstring

Constant identifier for the dataset.

snapshot_idstring

The build this payload came from. Key your cache on it: an unchanged snapshot_id means byte-identical data.

generated_atstring

ISO 8601 timestamp of the data run that produced the snapshot.

licensestring

Licence of the compilation, plus the benchmark attribution you must retain.

citationstring

The citation line to reproduce.

attributionobject

Benchmark sources and price feeds, spelled out with URLs so credit can be passed on mechanically.

notesstring[]

What the prices exclude — taxes, batch discounts, volume and context-length tiers — and which columns are estimates. Read this before comparing.

model_countnumber

Number of rows in `models`.

modelsobject[]

The rows, documented below.

The columns

Every row carries 26 columns. The CSV has the same names in the same order.

Identity

Which model, sold by whom, in which market.

idstring

Stable row key. One model sold by two providers is two rows with two ids — that duplication is the cross-provider comparison.

namestring

Display name as the provider markets it.

providerstring

Who you actually buy from — the first party, a cloud, a host or a gateway. Cheapest means little if the seller routes your request elsewhere on different terms.

category"llm" | "video" | "tts" | "image" | "embeddings" | "stt"

Which of the six markets the row belongs to.

unitstring

The billing unit the populated price column is measured in, e.g. "per 1M tokens". Read it before comparing two rows from different categories.

Price

Exactly one price shape is populated per row, matching the category's billing unit. Every other price column is null — that is the schema working, not missing data.

price_input_per_1mnumber | nullUSD / 1M input tokens

Input leg for language models; embeddings are billed on this leg too, since they meter exactly like an LLM's input.

price_output_per_1mnumber | nullUSD / 1M output tokens

Output leg for language models.

price_cached_input_per_1mnumber | nullUSD / 1M cached input tokens

Cache-read rate where the provider publishes one. Null where feeds disagree on the leg — a contested cache rate is never exported as fact.

price_per_secondnumber | nullUSD / second of output

Video generation.

price_per_imagenumber | nullUSD / generated image

Image generation.

price_per_1k_charsnumber | nullUSD / 1K input characters

Speech synthesis.

price_per_audio_minutenumber | nullUSD / minute of audio

Transcription.

Price provenance

Where the number came from and when. These columns are the reason the dataset exists: a price you cannot trace is a rumour.

price_sourcedboolean

True where the price came from real feeds or a provider's own page. False marks an illustrative sample — filter on this column before you quote anything.

price_sourcesstring

Semicolon-separated feed names behind the number, e.g. "models.dev; LiteLLM". Empty where the row is not sourced.

price_retrieved_atstring | nullISO 8601 timestamp

When the primary feed snapshot was retrieved.

price_provider_verified_urlstring | null

The provider's own pricing page, present where the figure was additionally read there by hand. The provider page outranks every aggregator feed.

price_notestring | null

Pricing caveat the number is only true under, e.g. "base tier ≤200K context". Where this is set, the bare price is not the whole story.

Quality benchmark

Sourced measurements only. Where no published score exists the columns are null — a gap, never an invented number.

benchmark_labelstring | null

Which scale the score is on — "Intelligence index", "Arena Elo", "MTEB mean" or "Avg WER". The units differ per category and are not comparable across them.

benchmark_scorenumber | null

The measurement. Higher is better on every scale except Avg WER, which is an error rate: lower is better.

benchmark_ci95number | null

95% confidence-interval half-width for arena Elo scores. Two models whose intervals overlap are not meaningfully ranked.

benchmark_sourcestring | null

Who measured it. Attribution is required wherever the score is republished — see the licence below.

benchmark_task_setstring | null

The exact task set a mean is taken over, e.g. "MTEB(eng, v2)". A mean is only comparable within one task set, so the set travels with the number.

benchmark_retrieved_atstring | nullISO 8601 timestamp

When the leaderboard was read.

Derived estimate

Computed by us, named so you can tell. Not a citable measurement.

value_score_estnumber | null0–100

Benchmark-per-dollar, normalised against the live catalogue's median. An estimate, carried under an _est name on purpose: it moves when the catalogue moves, so it is not a stable figure to cite. Null where a row has no score or no positive price.

Model facts

Everything else worth carrying in a row.

context_window_tokensnumber | nulltokens

Context window, language models only.

release_datestring | nullISO 8601 date

Public availability date, where one is known.

What is deliberately not in it

  • Invented numbers. Where no benchmark is published the score columns are null. Synthetic scores render on the site with an est tag and are never exported.
  • Contested figures. A price its sources disagree on is not shipped as fact — it is held under review and listed on the methodology page.
  • Price history. The API is a snapshot of current list prices. The history chart on the site is still an illustrative sample, so it is not exported — archive the daily snapshots yourself if you need a series.
  • Negotiated or effective prices. These are provider list prices in USD, excluding taxes, batch discounts, volume tiers and context-length tiers except where price_note says otherwise. What you actually pay depends on your contract.
  • Latency, throughput and rate limits. Not measured here, so not claimed here.

Rate limit

The rule

10 requests per rolling 24 hours, counted per source IP address, across both endpoints combined.

It applies to /api/v1/* and to nothing else. Pages, charts, the sitemap and the OG images are never limited, so readers and crawlers never meet it.

Why 10 is not stingy

  • A complete integration costs one request. There is no pagination, no per-model endpoint and no auth round-trip: request one returns all 4325 rows, across all 6 markets, with every provenance column attached. The limit is ten times what a correct client needs.
  • The data changes once a day. A daily run rebuilds the catalogue and stamps it with a new snapshot_id. Requests two through ten in the same day return byte-identical content. Polling faster cannot get you fresher numbers — it can only get you the same numbers again.
  • It is a fairness device, not a paywall. There is no paid tier to upsell you to, and no key to make you register for. The limit exists so one script cannot spend the bandwidth that thousands of readers share.
  • The site is one small server. This runs on a single low-cost VM in the single digits of dollars a month, with no ads, no trackers and no sponsor. Each JSON response is 93 KB on the wire, so the quota still allots every address around 928 KB a day — generous per caller, survivable in aggregate. One unbounded scraper on a loop would move more than the readers do.
  • Copying is encouraged; hammering is not. The compilation is CC BY 4.0. If you need the data constantly, take it once and serve it from your own infrastructure — that is explicitly allowed, and it is better for both of us than ten thousand requests a day.

What happens when you exceed it

You get 429 Too Many Requests with a JSON body naming the limit and a Retry-After header in seconds. The window is rolling, not a midnight reset: a slot frees exactly 24 hours after it was used, so a client that backs off recovers on its own. Nothing is blocked, banned or blacklisted.

Staying under it

  • Fetch once a day and cache the result. Key your cache on snapshot_id: if it has not moved, nothing in the payload has.
  • Send If-None-Match with the ETag you were given. A 304 still spends a slot, but costs neither of us the payload.
  • Fetch server-side, not from every visitor's browser — otherwise your users share your quota and your tenth reader gets an error.
  • Do not retry a 429 immediately. Honour Retry-After; retrying in a loop just keeps the window full.

If 10 a day genuinely is not enough

Email ilyasubach@gmail.com and say what you are building. Raises are free. This matters most for shared addresses — an office, a university, a CI runner or a corporate NAT can burn ten requests between colleagues who have never met, and that is a flaw in per-IP counting rather than anything you did wrong.

Licence and attribution

The compilation is CC BY 4.0. Use it commercially, modify it, redistribute it — keep the credit. Cite as:

Model Price Book, snapshot 2026-08-03 — https://modelpricebook.com

Benchmark scores are third-party measurements used under their own attribution requirements, and that credit travels with the data: every row names its measurer in benchmark_source, and the payload's attribution object spells out the sources in full. If you republish a score, you must carry that attribution too — the methodology page explains each source's terms.

Found a wrong number? Every figure here is meant to be checked — report it and it gets fixed in public.