import type { Metadata } from 'next';
import Link from 'next/link';
import { Fragment } from 'react';
import { ChangeRow } from '@/components/changes/change-row';
import { BTN_GHOST, CTRL, DistBars, Field, Methodology, SortTh } from '@/components/intelligence/bits';
import { ExpandableOfferRow } from '@/components/intelligence/expand-price-row';
import { PriceIndexChart } from '@/components/intelligence/price-index-chart';
import { DataStrip, type StripItem, TerminalLayout } from '@/components/layout/terminal';
import { PriceMovers } from '@/components/prices/movers';
import { Chip } from '@/components/ui/badges';
import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';
import { EntityLink } from '@/components/ui/entity';
import { Pagination, withParams } from '@/components/ui/pagination';
import { SourceCell } from '@/components/ui/provenance';
import { Note, PageHeader, Section } from '@/components/ui/section';
import { EmptyState, Unavailable } from '@/components/ui/unavailable';
import { api, ApiError, intel, safe } from '@/lib/api';
import { fmtAgo, fmtDate, fmtInt, fmtTokens, fmtUsdPerM, num } from '@/lib/format';
import { routes, SITE_NAME, SITE_URL } from '@/lib/site';
import type { CheapestFrontier, Page, Price } from '@/lib/types';
export const revalidate = 300;
type SP = Record;
const LIMIT = 100;
const DAYS = [30, 90, 180, 365];
const SORTS = ['input', 'output', 'cheapest_frontier', 'model', 'provider', 'observed'] as const;
type Sort = (typeof SORTS)[number];
const KEYS = ['days', 'scale', 'sort', 'provider', 'org', 'family', 'modality', 'model', 'offset'] as const;
const SORT_LABELS: Record = { input: 'Cheapest input', output: 'Cheapest output', cheapest_frontier: 'Cheapest frontier output', model: 'Model', provider: 'Provider', observed: 'Recently observed' };
function pick(sp: SP) {
const cur: Record = {};
for (const k of KEYS) if (sp[k]) cur[k] = sp[k];
const days = DAYS.includes(Number(cur.days)) ? Number(cur.days) : 180;
const sort: Sort = (SORTS as readonly string[]).includes(cur.sort ?? '') ? (cur.sort as Sort) : 'input';
return { cur, days, sort, offset: Math.max(0, Number(cur.offset) || 0), scale: cur.scale === 'log' ? ('log' as const) : ('linear' as const) };
}
const TITLE = 'AI Price Index — USD per 1M tokens, every provider, every day';
export async function generateMetadata({ searchParams }: { searchParams: Promise }): Promise {
const { cur, days } = pick(await searchParams);
const filtered = cur.provider || cur.model || cur.org || cur.family || cur.modality;
const title = filtered ? `AI prices — ${[cur.provider, cur.org, cur.family, cur.modality, cur.model].filter(Boolean).join(' · ')}` : TITLE;
const description = `Daily medians of published input, output, frontier, open-weight and embedding prices per 1M tokens across every provider offer AI Atlas tracks (last ${days} days), the distribution of current offers, the cheapest frontier model, price movers, new listings and delistings, and every current offer with its source and history.`;
return {
title,
description,
alternates: { canonical: routes.prices() },
openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${routes.prices()}`, type: 'website', siteName: SITE_NAME },
twitter: { card: 'summary_large_image', title, description },
robots: filtered || cur.offset ? { index: false, follow: true } : undefined,
};
}
/** `/prices` 404s when a slug filter is unknown — keep that distinction instead of a generic "unavailable". */
async function loadPrices(query: Record): Promise<{ page: (Page & { methodology?: string }) | null; notFound: boolean; detail?: string | null }> {
try {
return { page: await intel.prices(query), notFound: false };
} catch (e) {
return { page: null, notFound: e instanceof ApiError && e.notFound, detail: e instanceof ApiError ? e.detail : null };
}
}
function CheapestStrip({ c, label }: { c: CheapestFrontier | null | undefined; label: string }) {
return (
{label}
{c ? (
<>
{c.model.organization && {c.model.organization.name} }
{fmtUsdPerM(c.output)} output / 1M
input {fmtUsdPerM(c.input)} · context {fmtTokens(c.context_length)} · via
>
) : (
No frontier model with a current priced offer in this response.
)}
);
}
export default async function PricesPage({ searchParams }: { searchParams: Promise }) {
const sp = await searchParams;
const { cur, days, sort, offset, scale } = pick(sp);
const [index, providers, facets, priced, listed, delisted] = await Promise.all([
safe(intel.priceIndex(days)),
safe(intel.providers()),
safe(api.models({ limit: 1, facets: 1 })),
loadPrices({ sort, provider: cur.provider, model: cur.model, org: cur.org, family: cur.family, modality: cur.modality, limit: LIMIT, offset }),
safe(api.changes({ type: 'PROVIDER_LISTED', limit: 8 })),
safe(api.changes({ type: 'PROVIDER_DELISTED', limit: 8 })),
]);
const href = (patch: Record) => withParams('/prices', cur, patch);
const providerHref = (name: string) => {
const p = (providers?.items ?? []).find((x) => x.name === name);
return p ? href({ provider: p.slug, offset: undefined }) : undefined;
};
// ---- index
const series = index?.series ?? [];
const populated = series.filter((p) => num(p.median_input) !== null || num(p.median_output) !== null);
const latest = populated.at(-1) ?? null;
const first = populated[0] ?? null;
const delta = (field: 'median_input' | 'median_output' | 'median_frontier_output' | 'median_open_output' | 'median_embedding_input') => {
const a = num(first?.[field]);
const b = num(latest?.[field]);
if (a === null || b === null || a === 0 || first === latest) return undefined;
const pct = ((b - a) / a) * 100;
if (Math.abs(pct) < 0.05) return undefined;
return { value: `${pct > 0 ? '+' : ''}${pct.toFixed(1)}%`, tone: pct < 0 ? ('positive' as const) : pct > 0 ? ('negative' as const) : ('neutral' as const) };
};
const money = (v: unknown) => {fmtUsdPerM(v)} ;
const strip: StripItem[] | null = latest
? [
{ label: 'Median input', value: money(latest.median_input), definition: 'Median of live input prices (USD / 1M tokens) across every provider offer valid at the end of the day; zero or missing prices excluded.', delta: delta('median_input'), hint: `n=${fmtInt(latest.sample?.offers ?? latest.offers)}` },
{ label: 'Median output', value: money(latest.median_output), definition: 'Median of live output prices across every provider offer valid at the end of the day.', delta: delta('median_output') },
{ label: 'Median frontier output', value: money(latest.median_frontier_output), definition: index?.frontier?.methodology ?? 'Frontier models = recent releases by active organizations or top-10 on a benchmark; no composite score.', delta: delta('median_frontier_output'), hint: `n=${fmtInt(latest.sample?.frontier_offers)}` },
{ label: 'Median open output', value: money(latest.median_open_output), definition: 'Median output price over models with openness open-weights / open-source.', delta: delta('median_open_output'), hint: `n=${fmtInt(latest.sample?.open_models)}` },
{ label: 'Median embedding input', value: money(latest.median_embedding_input), definition: 'Median input price over models whose modalities include embedding.', delta: delta('median_embedding_input'), hint: `n=${fmtInt(latest.sample?.embedding_models)}` },
{ label: 'Models priced', value: fmtInt(latest.models), definition: 'Canonical models with at least one live offer on the latest day.', hint: fmtDate(latest.day) },
{ label: 'Cheapest input', value: fmtUsdPerM(latest.min_input), definition: 'Lowest positive live input price on the latest day.', hint: num(latest.max_input) !== null ? `dearest ${fmtUsdPerM(latest.max_input)}` : undefined },
]
: null;
// ---- offers
const page = priced.page;
const rows = page?.items ?? [];
const minByModel = new Map();
const providersByModel = new Map>();
for (const r of rows) {
const v = num(r.input_per_mtok);
if (v !== null) {
const m = minByModel.get(r.model.slug);
if (m === undefined || v < m) minByModel.set(r.model.slug, v);
}
const s = providersByModel.get(r.model.slug) ?? new Set();
s.add(r.provider.slug);
providersByModel.set(r.model.slug, s);
}
const providerOptions = (providers?.items ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
const providerName = providerOptions.find((o) => o.slug === cur.provider)?.name;
const orgOptions = ((facets?.facets as { organizations?: { slug: string; name: string; count?: unknown }[] } | undefined)?.organizations ?? []).slice(0, 60);
const familyOptions = ((facets?.facets as { families?: { value: string; label?: string; count?: unknown }[] } | undefined)?.families ?? []).slice(0, 60);
const modalityOptions = ((facets?.facets as { modalities?: { value: string; count?: unknown }[] } | undefined)?.modalities ?? []);
const filterCount = ['provider', 'org', 'family', 'modality', 'model'].filter((k) => cur[k]).length + (days !== 180 ? 1 : 0);
const newListings = index?.new_listings_30d;
const delistings = index?.delistings_30d;
const changes30 = index?.price_changes_30d;
const asCount = (v: unknown) => (Array.isArray(v) ? v.length : num(v));
const filters = (
);
const inspector = (
Index method
{index?.methodology ?? index?.note ?? 'Unavailable.'}
{latest?.sample && (
Sample · {fmtDate(latest.day)}
offers
{fmtInt(latest.sample.offers)}
models
{fmtInt(latest.sample.models)}
frontier
{fmtInt(latest.sample.frontier_models)} models · {fmtInt(latest.sample.frontier_offers)} offers
open
{fmtInt(latest.sample.open_models)} models
embedding
{fmtInt(latest.sample.embedding_models)} models
)}
{index?.frontier?.composition && (
Frontier composition
{Object.entries(index.frontier.composition).map(([k, v]) => (
{k.replace(/_/g, ' ')}
{typeof v === 'string' && /^\d{4}-/.test(v) ? fmtDate(v) : fmtInt(v)}
))}
)}
30-day counters
new listings
{fmtInt(asCount(newListings))}
delistings
{fmtInt(asCount(delistings))}
price changes
{fmtInt(asCount(changes30))}
GET /prices/index · methodology
);
return (
{fmtInt(latest.offers)} offers · {fmtInt(latest.models)} models · {fmtDate(latest.day)}
: undefined} className="pt-4 md:pt-6" />
{/* ------------------------------------------------------------------------------------------------ index */}
Daily medians · last {days} days>} hairline={false} className="pt-0">
{!index ? (
) : !latest ? (
Prices appear once a provider pricing page has been crawled. Try a longer window.
) : (
<>
{strip && }
{fmtInt(populated.length)} populated day{populated.length === 1 ? '' : 's'} in the window · sample sizes per day in the tooltip · a null median means no offer in that universe on that day.
>
)}
{/* ------------------------------------------------------------------------------------- distribution + frontier */}
{index && (
Current offers by output price
)}
{/* ------------------------------------------------------------------------------------------------ movers */}
{/* ---------------------------------------------------------------------------------- listings / delistings */}
New listings
{fmtInt(asCount(newListings))} in 30 d
PROVIDER_LISTED events that occurred in the last 30 days (a model × provider offer first opened).
{Array.isArray(newListings) && newListings.length ? (
{newListings.slice(0, 8).map((e) => )}
) : listed?.items.length ? (
{listed.items.map((e) => )}
) : (
No listing event in the window.
)}
All listing events →
Delistings
{fmtInt(asCount(delistings))} in 30 d
PROVIDER_DELISTED events that occurred in the last 30 days (an offer closed).
{Array.isArray(delistings) && delistings.length ? (
{delistings.slice(0, 8).map((e) => )}
) : delisted?.items.length ? (
{delisted.items.map((e) => )}
) : (
No delisting recorded in the window — offers that disappear from a pricing page are closed (valid_to) and emit PROVIDER_DELISTED.
)}
{/* ------------------------------------------------------------------------------------------------ offers */}
{priced.notFound ? (
Slugs are the last part of an entity URL (/models/<slug> ). Search instead →
) : !page ? (
) : (
<>
{page.methodology && sort === 'cheapest_frontier' && }
Model
Provider
Input / 1M
Cached in
Output / 1M
Batch in / out
Context
Observed
Source
{rows.length === 0 && {sort === 'cheapest_frontier' ? 'No frontier model has a current priced offer in the API response (see the methodology above).' : 'No current offers match these filters.'} }
{rows.map((p) => {
const v = num(p.input_per_mtok);
const cheapest = v !== null && minByModel.get(p.model.slug) === v && (providersByModel.get(p.model.slug)?.size ?? 0) > 1;
return (
{p.model.organization && {p.model.organization.name} }
{p.provider_model_id && p.provider_model_id !== p.model.slug && {p.provider_model_id} }
{p.provider.name}
↗
{fmtUsdPerM(p.input_per_mtok)}
{cheapest && cheapest }
{fmtUsdPerM(p.cached_input_per_mtok)}
{fmtUsdPerM(p.output_per_mtok)}
{num(p.batch_input_per_mtok) === null && num(p.batch_output_per_mtok) === null ? — : `${fmtUsdPerM(p.batch_input_per_mtok)} / ${fmtUsdPerM(p.batch_output_per_mtok)}`}
{num(p.context_length) === null ? — : fmtTokens(p.context_length)}
{fmtAgo(p.observed_at)}
);
})}
href({ offset: o || undefined })} className="mt-4" />
“Cheapest” marks the lowest input price for a model among the rows on this page (models served by several providers). USD per 1M tokens as published; providers overview · cost calculator · methodology.
>
)}
);
}