SPB Git

spb/ai-atlas

Public
15.8 KB · 195 lines tsx
1import type { Metadata } from 'next';2import Link from 'next/link';3import { DataTable, Td, Th } from '@/components/ui/data-table';4import { Container, Note, PageHeader, Section } from '@/components/ui/section';5import { Unavailable } from '@/components/ui/unavailable';6import { api, safe } from '@/lib/api';7import { PUBLIC_API_BASE, routes } from '@/lib/site';8import type { EntityDetail, Stats } from '@/lib/types';910export 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' } };11export const revalidate = 3600;1213const ENDPOINTS: { path: string; returns: string }[] = [14  { path: 'GET /health', returns: 'Service status (db, redis, llm) and version' },15  { path: 'GET /stats', returns: 'Live counters: entities per type, sources, documents, claims, events, prices, archive size — always computed from the database' },16  { path: 'GET /stats/history?days=90', returns: 'Daily counts' },17  { path: 'GET /search?q=&type=&limit=&offset=', returns: 'Natural-language or keyword search → { query: compiled filters, items: ranked entities (+rank), total }' },18  { path: 'GET /search/suggest?q=', returns: '≤ 8 prefix suggestions { id, entity_type, slug, name, organization_name }' },19  { 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)' },20  { 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)' },21  { 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)' },22  { 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' },23  { 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' },24  { 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' },25  { path: 'GET /entities/{slug}/sources · /related?limit=', returns: 'Documents describing the entity (deduped by URL) · related entities (same org / family / shared relations)' },26  { 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)' },27  { path: 'GET /companies?q=&country=&kind=&sort=', returns: 'Paged organizations with model_count and paper_count (+facets)' },28  { path: 'GET /papers?q=&category=&org=&since=&until=&sort=', returns: 'Paged papers' },29  { path: 'GET /providers · GET /prices?model=&provider=&sort=&current=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' },30  { 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 }' },31  { 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' },32  { 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 }[] }' },33  { path: 'GET /explore/types · GET /explore/{type}?q=&org=&sort=', returns: 'Entity types with live counts; generic paged listing for any type' },34  { 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' },35  { path: 'GET /timeline?entity=&year=&category=&limit=', returns: '{ items: { month, count, events[] }[], total } — grouped by month' },36  { 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' },37  { path: 'GET /diff?a=YYYY-MM-DD&b=YYYY-MM-DD&scope=all|models|org:<slug>|family:<name>', returns: '{ a, b, scope, new_entities[], gone_entities[], property_changes[], price_changes[], benchmark_changes[], counts }' },38  { 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' },39];4041/** Compact, honest projection of /stats for the example block (the real payload has more counters). */42function statsExample(s: Stats) {43  return {44    entities: s.entities,45    entities_total: s.entities_total,46    sources: s.sources,47    connectors: s.connectors,48    claims_current: s.claims_current,49    change_events_24h: s.change_events_24h,50    benchmark_results: s.benchmark_results,51    prices_current: s.prices_current,52    last_event_at: s.last_event_at,53    computed_at: s.computed_at,54    '…': `${Object.keys(s).length} keys in total`,55  };56}5758function modelExample(d: EntityDetail) {59  const attrKeys = Object.keys(d.attributes ?? {}).slice(0, 8);60  const attributes: Record<string, unknown> = {};61  for (const k of attrKeys) attributes[k] = d.attributes[k];62  const provKey = attrKeys.find((k) => d.provenance?.[k]) ?? Object.keys(d.provenance ?? {})[0];63  const rest = Object.keys(d.attributes ?? {}).length - attrKeys.length;64  return {65    id: d.id,66    entity_type: d.entity_type,67    slug: d.slug,68    name: d.name,69    organization: d.organization,70    status: d.status,71    attributes: rest > 0 ? { ...attributes, '…': `${rest} more` } : attributes,72    provenance: provKey ? { [provKey]: d.provenance[provKey], '…': `one entry per attribute` } : {},73    quality: d.quality,74    counts: d.counts,75    relations: `${d.relations?.length ?? 0} groups`,76    prices: `${d.prices?.length ?? 0} current rows`,77    results: `${d.results?.length ?? 0} benchmark results`,78    timeline: `${d.timeline?.length ?? 0} events`,79    updated_at: d.updated_at,80  };81}8283function Code({ children }: { children: string }) {84  return (85    <pre className="scrollbar-thin mt-3 overflow-x-auto border border-rule bg-surface p-4 text-[12.5px] leading-relaxed text-ink">86      <code>{children}</code>87    </pre>88  );89}9091export default async function DevelopersPage() {92  const [health, stats, top] = await Promise.all([safe(api.health()), safe(api.stats()), safe(api.models({ limit: 2, sort: 'quality' }))]);93  const slugA = top?.items[0]?.slug;94  const slugB = top?.items[1]?.slug;95  const model = slugA ? await safe(api.entityOfType('models', slugA)) : null;96  const exA = slugA ?? '<model-slug>';97  const exB = slugB ?? '<other-slug>';98  const today = new Date().toISOString().slice(0, 10);99  const weekAgo = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);100101  return (102    <Container>103      <PageHeader eyebrow="Developers" title="Public API" lede="Everything on this site comes from the same JSON API. Public routes need no key; responses are cached for 1–10 minutes and search is rate-limited per IP." aside={health ? <p className="text-sm text-ink-3">API <span className={health.status === 'ok' ? 'text-positive' : 'text-warning'}>{health.status}</span> · v{health.version}</p> : <p className="text-sm text-ink-3">API status unavailable</p>} />104105      <Section eyebrow="Base URL" title={<span className="mono text-lg md:text-xl">{PUBLIC_API_BASE}</span>} hairline={false}>106        <p className="text-sm text-ink-2">107          Interactive OpenAPI documentation: <a href="/api/v1/docs" className="link mono">/api/v1/docs</a>. JSON, UTF-8, ISO-8601 UTC timestamps. Errors are <span className="mono">{'{ "detail": "…" }'}</span> with 400 / 404 / 429 / 503 (422 for parameters rejected by validation).108        </p>109        <Code>{`# Live counters110curl -s ${PUBLIC_API_BASE}/stats | jq '.entities'111112# Natural-language search → compiled filters + ranked results113curl -s "${PUBLIC_API_BASE}/search?q=open+models+over+100B+released+in+2026" | jq '.query, .items[0:3]'114115# A model with provenance, prices, results, lineage116curl -s ${PUBLIC_API_BASE}/models/${exA} | jq '{name, attributes, provenance: (.provenance | keys)}'117118# Compare two models on every shared dimension (+ shared benchmarks, best prices)119curl -s "${PUBLIC_API_BASE}/compare?ids=${exA},${exB}" | jq '.dimensions[].key, .items[].values'120121# What changed between two dates (scope: all | models | org:<slug> | family:<name>)122curl -s "${PUBLIC_API_BASE}/diff?a=${weekAgo}&b=${today}&scope=models" | jq '.counts, .new_entities[0:3]'123124# Claim history and the state of a record as of a date125curl -s "${PUBLIC_API_BASE}/entities/${exA}/history?property=context_length" | jq '.items[] | {value, status, valid_from, valid_to, source_url}'126curl -s "${PUBLIC_API_BASE}/entities/${exA}/asof?date=${weekAgo}" | jq '{existed, attributes}'127128# Neighbourhood graph, price index, leaderboard by config, estimated hardware fit129curl -s "${PUBLIC_API_BASE}/entities/${exA}/graph?depth=1&limit=80" | jq '{nodes: (.nodes|length), edges: (.edges|length)}'130curl -s "${PUBLIC_API_BASE}/prices/index?days=90" | jq '.series[-1], (.movers|length)'131curl -s "${PUBLIC_API_BASE}/benchmarks/<benchmark-slug>/results?config=variant=v2.1&limit=20" | jq '.items[] | {model: .model.name, score, config}'132curl -s "${PUBLIC_API_BASE}/hardware/fit?memory_gb=64&quant=4bit&context=32768&limit=20" | jq '.assumptions, .items[0]'133134# What changed today135curl -s "${PUBLIC_API_BASE}/changes?importance_min=2&limit=20" | jq '.items[] | {event_type, summary, source_url}'`}</Code>136      </Section>137138      <Section eyebrow="Live examples" title="Example responses" lede="Fetched from the API when this page was rendered (revalidated hourly) and trimmed for display — not sample data.">139        <div className="grid gap-8 lg:grid-cols-2">140          <div className="min-w-0">141            <p className="mono text-sm text-ink">GET /stats</p>142            {stats ? <Code>{JSON.stringify(statsExample(stats), null, 2)}</Code> : <Unavailable what="Live /stats example" className="mt-3" />}143          </div>144          <div className="min-w-0">145            <p className="mono text-sm text-ink">GET /models/{exA}</p>146            {model ? <Code>{JSON.stringify(modelExample(model), null, 2)}</Code> : <Unavailable what="Live model example" className="mt-3" />}147            {model && (148              <Note className="mt-2">149                Trimmed: the full record has every attribute, a provenance entry per attribute, grouped relations, sources and the latest 30 events — <Link href={routes.entity(model)} className="link">see it rendered</Link> or <a href={`/api/v1/models/${encodeURIComponent(model.slug)}`} className="link">fetch the JSON</a>.150              </Note>151            )}152          </div>153        </div>154      </Section>155156      <Section eyebrow="Endpoints" title="Public routes">157        <DataTable caption="Public endpoints" compact>158          <thead><tr><Th>Route</Th><Th>Returns</Th></tr></thead>159          <tbody>160            {ENDPOINTS.map((e) => (161              <tr key={e.path}>162                <Td primary className="mono text-[12.5px] break-words">{e.path}</Td>163                <Td label="Returns" wide className="text-ink-2">{e.returns}</Td>164              </tr>165            ))}166          </tbody>167        </DataTable>168        <Note className="mt-3">Shapes are documented in the OpenAPI schema. Admin routes (<span className="mono">/admin/*</span>) require the <span className="mono">x-aia-admin-token</span> header and are not public.</Note>169      </Section>170171      <Section eyebrow="Conventions" title="Reading responses">172        <ul className="max-w-3xl list-disc space-y-1.5 pl-5 text-sm leading-relaxed text-ink-2">173          <li>Numeric aggregates may arrive as <span className="mono">string</span> (Postgres decimals): treat every numeric field as <span className="mono">number | string | null</span> and coerce.</li>174          <li><span className="mono">null</span> 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.</li>175          <li>Timestamps are ISO-8601 UTC. Dates without time (<span className="mono">release_date</span>, <span className="mono">knowledge_cutoff</span>) may be <span className="mono">YYYY</span>, <span className="mono">YYYY-MM</span> or <span className="mono">YYYY-MM-DD</span>, exactly as published.</li>176          <li>Units: tokens, USD per 1M tokens (<span className="mono">*_per_mtok</span>), GB, GB/s, W. Openness vocabulary: <span className="mono">open-weights | open-source | proprietary | restricted</span>.</li>177          <li><span className="mono">provenance</span> is an object keyed by attribute: <span className="mono">{'{ source_id, url, observed_at, tier (1 official → 4 unverified), confidence, extractor: deterministic | llm, unit? }'}</span>.</li>178          <li>Cursor feeds (<span className="mono">/changes</span>, <span className="mono">/entities/{'{slug}'}/timeline</span>) return <span className="mono">next_before</span>; pass it as <span className="mono">before=</span> for the next page (<span className="mono">null</span> at the end). Listings otherwise use <span className="mono">limit</span>/<span className="mono">offset</span>, capped at 200 (sitemap: 5 000).</li>179          <li>Parameters rejected by validation (bad type or bounds) return <span className="mono">422</span> with <span className="mono">{'{ detail, errors }'}</span>; semantic errors (unknown sort, bad date, wrong scope, ids count) return <span className="mono">400</span>.</li>180          <li>Anything marked <span className="mono">estimated</span> (hardware fit) is derived from published specs by a stated formula — the <span className="mono">assumptions</span> array is part of the response.</li>181        </ul>182      </Section>183184      <Section eyebrow="Terms" title="Fair use">185        <ul className="max-w-3xl list-disc space-y-1.5 pl-5 text-sm leading-relaxed text-ink-2">186          <li>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.</li>187          <li>Search is rate-limited per IP (HTTP 429 when exceeded). Higher limits and developer keys (<span className="mono">x-api-key</span>) will be available on request — see <Link href={routes.about()} className="link">contact</Link>.</li>188          <li>Attribution: “Data: AI Atlas (www.ai-atlas.co)” with a link. Every record carries its own upstream sources; please keep them when you redistribute.</li>189          <li>Never treat a missing field as zero. <span className="mono">null</span> means the sources did not state it.</li>190        </ul>191      </Section>192    </Container>193  );194}195
Files · c509d7e03d5aed8bc61fb24bc1c8bb3de1f4b9e2
Menu

Theme