import type { Metadata } from 'next'; import Link from 'next/link'; import { DataTable, Td, Th } from '@/components/ui/data-table'; import { Container, Note, PageHeader, Section } from '@/components/ui/section'; import { Unavailable } from '@/components/ui/unavailable'; import { api, safe } from '@/lib/api'; import { PUBLIC_API_BASE, routes } from '@/lib/site'; import type { EntityDetail, Stats } from '@/lib/types'; export const metadata: Metadata = { title: 'Developers — public API', description: 'The AI Atlas public API: JSON over HTTPS, no key required for public routes, OpenAPI docs, live example responses and curl recipes.', alternates: { canonical: '/developers' } }; export const revalidate = 3600; const ENDPOINTS: { path: string; returns: string }[] = [ { path: 'GET /health', returns: 'Service status (db, redis, llm) and version' }, { path: 'GET /stats', returns: 'Live counters: entities per type, sources, documents, claims, events, prices, archive size — always computed from the database' }, { path: 'GET /stats/history?days=90', returns: 'Daily counts' }, { path: 'GET /search?q=&type=&limit=&offset=', returns: 'Natural-language or keyword search → { query: compiled filters, items: ranked entities (+rank), total }' }, { path: 'GET /search/suggest?q=', returns: '≤ 8 prefix suggestions { id, entity_type, slug, name, organization_name }' }, { path: 'GET /entities/{slug}', returns: 'Full entity detail: attributes, provenance per attribute, aliases, identifiers, relations (grouped), sources, timeline and type-specific blocks (prices, price_history, results, lineage, hardware_fit, models, papers, repositories)' }, { path: 'GET /models/{slug} · /companies/{slug} · /papers/{slug} · /providers/{slug} · /benchmarks/{slug} · /hardware/{slug} · /frameworks/{slug} · /datasets/{slug} · /tools/{slug}', returns: 'Same detail, type-checked (404 if the slug is another type)' }, { path: 'GET /entities/{slug}/timeline?limit=&before=', returns: '{ items: ChangeEvent[], next_before } — the entity’s own events (and, for companies, those of the models it develops)' }, { path: 'GET /entities/{slug}/history?property=', returns: '{ items: Claim[] } — every claim ever recorded, newest first, with status current | superseded | conflicting | retracted, valid_from → valid_to, source and tier' }, { path: 'GET /entities/{slug}/asof?date=YYYY-MM-DD', returns: '{ existed, first_seen_at, date, attributes, claims } — the entity as it was known on that date' }, { path: 'GET /entities/{slug}/graph?depth=1|2&limit=80', returns: '{ root, nodes: { id, slug, name, entity_type, organization_name }[], edges: { source, target, predicate }[] } — capped neighbourhood' }, { path: 'GET /entities/{slug}/sources · /related?limit=', returns: 'Documents describing the entity (deduped by URL) · related entities (same org / family / shared relations)' }, { path: 'GET /models?q=&org=&family=&openness=&modality=&status=&min_params=&max_params=&min_context=&year_from=&year_to=&license=&sort=&order=&facets=1', returns: 'Paged models (+facets: organizations, openness, modalities, families, years, licenses, status)' }, { path: 'GET /companies?q=&country=&kind=&sort=', returns: 'Paged organizations with model_count and paper_count (+facets)' }, { path: 'GET /papers?q=&category=&org=&since=&until=&sort=', returns: 'Paged papers' }, { path: 'GET /providers · GET /prices?model=&provider=&sort=¤t=1 · GET /prices/history?model=|provider=', returns: 'Providers with min prices · current price rows (USD / 1M tokens) · every price row incl. closed ones, oldest first' }, { path: 'GET /prices/index?days=180', returns: '{ days, series: { day, median_input, median_output, min_input, max_input, models, offers }[], movers: ChangeEvent[] (PRICE_CHANGED), note }' }, { path: 'GET /benchmarks · GET /benchmarks/{slug}/results?config=&history=1&limit=&offset= · GET /benchmarks/{slug}/history?model=', returns: 'Benchmarks with result_count, model_count and current top · leaderboard sorted by score (respecting higher_is_better), current rows only unless history=1, filterable by config key=value · one model’s results over time' }, { path: 'GET /hardware?kind=&manufacturer=&min_memory=&sort= · GET /hardware/fit?memory_gb=&quant=4bit|8bit|fp16&context=8192&openness=&limit=', returns: 'Hardware listing · ESTIMATED fit: { inputs, estimated: true, assumptions[], counts, items: { model, parameter_count, estimated_memory_gb, headroom_gb, fits, quantization, note }[] }' }, { path: 'GET /explore/types · GET /explore/{type}?q=&org=&sort=', returns: 'Entity types with live counts; generic paged listing for any type' }, { path: 'GET /changes?category=&type=&entity_type=&importance_min=&since=&until=&q=&limit=&before= · GET /changes/daily?date= · GET /changes/categories?days=', returns: 'Change-event feed (cursor paged, next_before) · daily digest with sections · category counts' }, { path: 'GET /timeline?entity=&year=&category=&limit=', returns: '{ items: { month, count, events[] }[], total } — grouped by month' }, { path: 'GET /compare?ids=a,b[,…6]', returns: '{ entity_type, dimensions: { key, label, unit, kind: number|text|list|bool|date, source }[], items: { entity, values, provenance, prices?, results? }[] } — 2–6 entities of one type; models add best prices and the benchmarks shared by all' }, { path: 'GET /diff?a=YYYY-MM-DD&b=YYYY-MM-DD&scope=all|models|org:|family:', returns: '{ a, b, scope, new_entities[], gone_entities[], property_changes[], price_changes[], benchmark_changes[], counts }' }, { path: 'GET /sources · GET /methodology · GET /trending?days=&limit=&type= · GET /sitemap?type=&limit=&offset=', returns: 'Transparency page data, vocabularies and tiers, most viewed entities, sitemap feed' }, ]; /** Compact, honest projection of /stats for the example block (the real payload has more counters). */ function statsExample(s: Stats) { return { entities: s.entities, entities_total: s.entities_total, sources: s.sources, connectors: s.connectors, claims_current: s.claims_current, change_events_24h: s.change_events_24h, benchmark_results: s.benchmark_results, prices_current: s.prices_current, last_event_at: s.last_event_at, computed_at: s.computed_at, '…': `${Object.keys(s).length} keys in total`, }; } function modelExample(d: EntityDetail) { const attrKeys = Object.keys(d.attributes ?? {}).slice(0, 8); const attributes: Record = {}; for (const k of attrKeys) attributes[k] = d.attributes[k]; const provKey = attrKeys.find((k) => d.provenance?.[k]) ?? Object.keys(d.provenance ?? {})[0]; const rest = Object.keys(d.attributes ?? {}).length - attrKeys.length; return { id: d.id, entity_type: d.entity_type, slug: d.slug, name: d.name, organization: d.organization, status: d.status, attributes: rest > 0 ? { ...attributes, '…': `${rest} more` } : attributes, provenance: provKey ? { [provKey]: d.provenance[provKey], '…': `one entry per attribute` } : {}, quality: d.quality, counts: d.counts, relations: `${d.relations?.length ?? 0} groups`, prices: `${d.prices?.length ?? 0} current rows`, results: `${d.results?.length ?? 0} benchmark results`, timeline: `${d.timeline?.length ?? 0} events`, updated_at: d.updated_at, }; } function Code({ children }: { children: string }) { return (
      {children}
    
); } export default async function DevelopersPage() { const [health, stats, top] = await Promise.all([safe(api.health()), safe(api.stats()), safe(api.models({ limit: 2, sort: 'quality' }))]); const slugA = top?.items[0]?.slug; const slugB = top?.items[1]?.slug; const model = slugA ? await safe(api.entityOfType('models', slugA)) : null; const exA = slugA ?? ''; const exB = slugB ?? ''; const today = new Date().toISOString().slice(0, 10); const weekAgo = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10); return ( API {health.status} · v{health.version}

:

API status unavailable

} />
{PUBLIC_API_BASE}} hairline={false}>

Interactive OpenAPI documentation: /api/v1/docs. JSON, UTF-8, ISO-8601 UTC timestamps. Errors are {'{ "detail": "…" }'} with 400 / 404 / 429 / 503 (422 for parameters rejected by validation).

{`# Live counters curl -s ${PUBLIC_API_BASE}/stats | jq '.entities' # Natural-language search → compiled filters + ranked results curl -s "${PUBLIC_API_BASE}/search?q=open+models+over+100B+released+in+2026" | jq '.query, .items[0:3]' # A model with provenance, prices, results, lineage curl -s ${PUBLIC_API_BASE}/models/${exA} | jq '{name, attributes, provenance: (.provenance | keys)}' # Compare two models on every shared dimension (+ shared benchmarks, best prices) curl -s "${PUBLIC_API_BASE}/compare?ids=${exA},${exB}" | jq '.dimensions[].key, .items[].values' # What changed between two dates (scope: all | models | org: | family:) curl -s "${PUBLIC_API_BASE}/diff?a=${weekAgo}&b=${today}&scope=models" | jq '.counts, .new_entities[0:3]' # Claim history and the state of a record as of a date curl -s "${PUBLIC_API_BASE}/entities/${exA}/history?property=context_length" | jq '.items[] | {value, status, valid_from, valid_to, source_url}' curl -s "${PUBLIC_API_BASE}/entities/${exA}/asof?date=${weekAgo}" | jq '{existed, attributes}' # Neighbourhood graph, price index, leaderboard by config, estimated hardware fit curl -s "${PUBLIC_API_BASE}/entities/${exA}/graph?depth=1&limit=80" | jq '{nodes: (.nodes|length), edges: (.edges|length)}' curl -s "${PUBLIC_API_BASE}/prices/index?days=90" | jq '.series[-1], (.movers|length)' curl -s "${PUBLIC_API_BASE}/benchmarks//results?config=variant=v2.1&limit=20" | jq '.items[] | {model: .model.name, score, config}' curl -s "${PUBLIC_API_BASE}/hardware/fit?memory_gb=64&quant=4bit&context=32768&limit=20" | jq '.assumptions, .items[0]' # What changed today curl -s "${PUBLIC_API_BASE}/changes?importance_min=2&limit=20" | jq '.items[] | {event_type, summary, source_url}'`}

GET /stats

{stats ? {JSON.stringify(statsExample(stats), null, 2)} : }

GET /models/{exA}

{model ? {JSON.stringify(modelExample(model), null, 2)} : } {model && ( Trimmed: the full record has every attribute, a provenance entry per attribute, grouped relations, sources and the latest 30 events — see it rendered or fetch the JSON. )}
RouteReturns {ENDPOINTS.map((e) => ( {e.path} {e.returns} ))} Shapes are documented in the OpenAPI schema. Admin routes (/admin/*) require the x-aia-admin-token header and are not public.
  • Numeric aggregates may arrive as string (Postgres decimals): treat every numeric field as number | string | null and coerce.
  • null means the sources did not state it. Never read a missing field as zero, and never average two conflicting claims — the API returns both, flagged.
  • Timestamps are ISO-8601 UTC. Dates without time (release_date, knowledge_cutoff) may be YYYY, YYYY-MM or YYYY-MM-DD, exactly as published.
  • Units: tokens, USD per 1M tokens (*_per_mtok), GB, GB/s, W. Openness vocabulary: open-weights | open-source | proprietary | restricted.
  • provenance is an object keyed by attribute: {'{ source_id, url, observed_at, tier (1 official → 4 unverified), confidence, extractor: deterministic | llm, unit? }'}.
  • Cursor feeds (/changes, /entities/{'{slug}'}/timeline) return next_before; pass it as before= for the next page (null at the end). Listings otherwise use limit/offset, capped at 200 (sitemap: 5 000).
  • Parameters rejected by validation (bad type or bounds) return 422 with {'{ detail, errors }'}; semantic errors (unknown sort, bad date, wrong scope, ids count) return 400.
  • Anything marked estimated (hardware fit) is derived from published specs by a stated formula — the assumptions array is part of the response.
  • Public routes are free to use without a key. Please cache responses and identify your client with a User-Agent that includes a contact address.
  • Search is rate-limited per IP (HTTP 429 when exceeded). Higher limits and developer keys (x-api-key) will be available on request — see contact.
  • Attribution: “Data: AI Atlas (www.ai-atlas.co)” with a link. Every record carries its own upstream sources; please keep them when you redistribute.
  • Never treat a missing field as zero. null means the sources did not state it.
); }