SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
11.3 KB · 144 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { BreadcrumbLd } from '@/components/meta/breadcrumb-ld';4import { DataTable, Td, Th } from '@/components/ui/data-table';5import { Container, Note, PageHeader, Section } from '@/components/ui/section';6import { Unavailable } from '@/components/ui/unavailable';7import { api, safe } from '@/lib/api';8import { PUBLIC_API_BASE, routes, SITE_NAME } from '@/lib/site';9import type { Stats } from '@/lib/types';10import { RequestBuilder } from './request-builder';11import { ROUTES } from './routes';1213export const metadata: Metadata = { title: 'Developers — public API 1.1, request builder, provenance and history recipes', description: 'The AI Atlas public API: JSON over HTTPS, no key for public routes, OpenAPI docs, an interactive request builder with curl / Python / JavaScript snippets, and recipes for pagination, provenance and historical queries.', alternates: { canonical: '/developers' } };14export const revalidate = 3600;1516/** Compact, honest projection of /stats for the example block (the real payload has more counters). */17function statsExample(s: Stats) {18  return { entities: s.entities, entities_total: s.entities_total, organizations_total: s.organizations_total, change_events_live_24h: s.change_events_live_24h, claims_current: s.claims_current, prices_current: s.prices_current, last_event_at: s.last_event_at, definitions: s.definitions ? `${Object.keys(s.definitions).length} counter definitions` : undefined, computed_at: s.computed_at, '…': `${Object.keys(s).length} keys in total` };19}20function Code({ children }: { children: string }) {21  return (22    <pre className="scrollbar-thin mt-3 overflow-x-auto border border-rule bg-surface p-4 text-[12.5px] leading-relaxed text-ink">23      <code>{children}</code>24    </pre>25  );26}2728export default async function DevelopersPage() {29  const [health, stats, top] = await Promise.all([safe(api.health()), safe(api.stats()), safe(api.models({ limit: 1, sort: 'quality' }))]);30  const exA = top?.items[0]?.slug ?? '<model-slug>';31  const today = new Date().toISOString().slice(0, 10);32  const weekAgo = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);3334  return (35    <Container wide>36      <BreadcrumbLd items={[{ name: SITE_NAME, href: '/' }, { name: 'Developers', href: '/developers' }]} />37      <PageHeader eyebrow="Developers" title="Public API 1.1" lede="Everything on this site comes from the same JSON API. Public routes need no key; responses carry a weak ETag and are cached for 1–10 minutes; search is rate-limited per IP. 1.1 is additive over v1 — no route, field or type was removed." 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}{(health as { api_version?: string }).api_version ? ` · contract ${(health as { api_version?: string }).api_version}` : ''}</p> : <p className="text-sm text-ink-3">API status unavailable</p>} />3839      <Section eyebrow="Base URL" title={<span className="mono text-lg md:text-xl">{PUBLIC_API_BASE}</span>} hairline={false}>40        <p className="text-sm text-ink-2">41          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 / 409 / 429 / 501 / 503 (422 with <span className="mono">errors</span> for parameters rejected by validation). Every response carries <span className="mono">x-api-version: 1.1</span>.42        </p>43      </Section>4445      <Section id="builder" eyebrow="Request builder" title="Build a request, try it, copy the snippet" lede="Routes and parameters come from the 1.1 contract; “Try” calls the API from your browser through this site's same-origin proxy.">46        <RequestBuilder />47      </Section>4849      <Section id="recipes" eyebrow="Recipes" title="Pagination, provenance, history">50        <div className="grid gap-8 lg:grid-cols-2">51          <div className="min-w-0">52            <p className="text-sm font-medium text-ink">Cursor pagination on feeds</p>53            <Code>{`# /changes and /entities/{slug}/timeline return next_before; pass it back as before=54curl -s "${PUBLIC_API_BASE}/changes?importance_min=2&limit=50" | jq '{next_before, n: (.items|length)}'55curl -s "${PUBLIC_API_BASE}/changes?importance_min=2&limit=50&before=<next_before>"5657# Listings use limit/offset (≤ 200; /sitemap ≤ 5 000). 1.1 feeds are keyed on occurred_at and exclude backfill:58curl -s "${PUBLIC_API_BASE}/changes?include_backfill=1&date_field=observed&limit=20"   # v1 behaviour`}</Code>59            <p className="mt-6 text-sm font-medium text-ink">Field-level provenance</p>60            <Code>{`# Every displayed value has a claim behind it61curl -s "${PUBLIC_API_BASE}/entities/${exA}/provenance/context_length" | jq '{value, source: .source.name, tier, extractor, observed_at, valid_since, claim_id, conflicts: (.conflicts|length)}'6263# One claim and its lifecycle (previous, superseding, conflicting)64curl -s "${PUBLIC_API_BASE}/claims/<claim_id>" | jq '{property, claim: .claim.value, chain: (.chain | map_values(if type=="array" then length else . end))}'6566# All current claims of an entity67curl -s "${PUBLIC_API_BASE}/entities/${exA}/claims?status=current&limit=50" | jq '.items[] | {property, value, tier, source_name}'`}</Code>68          </div>69          <div className="min-w-0">70            <p className="text-sm font-medium text-ink">Historical queries</p>71            <Code>{`# The atlas as of a date (reconstructed before the observation history)72curl -s "${PUBLIC_API_BASE}/time-machine?date=2025-06-01&scope=models&limit=20" | jq '{reconstructed, first_entity_at, total: .models.total}'7374# One entity as of a date, and the full claim history of a property75curl -s "${PUBLIC_API_BASE}/entities/${exA}/asof?date=${weekAgo}" | jq '{existed, attributes}'76curl -s "${PUBLIC_API_BASE}/entities/${exA}/history?property=context_length" | jq '.items[] | {value, status, valid_from, valid_to}'7778# What changed between two dates (scope: all | models | org:<slug> | family:<slug>)79curl -s "${PUBLIC_API_BASE}/diff?a=${weekAgo}&b=${today}&scope=models" | jq '.counts'8081# Today in AI 2.0 — grouped sections, backfill excluded82curl -s "${PUBLIC_API_BASE}/changes/daily?date=${today}" | jq '{total, backfill_excluded, sections: [.today[] | {key, total}]}'`}</Code>83            <p className="mt-6 text-sm font-medium text-ink">Graph</p>84            <Code>{`# Typed neighbourhood explorer — never more than limit nodes, truncated says when the API cut85curl -s "${PUBLIC_API_BASE}/graph/explore?node=anthropic&mode=company&depth=1&limit=150" | jq '{truncated, counts, predicates}'`}</Code>86          </div>87        </div>88      </Section>8990      <Section eyebrow="Live example" title="GET /stats" lede="Fetched from the API when this page was rendered (revalidated hourly) and trimmed for display — not sample data.">91        {stats ? <Code>{JSON.stringify(statsExample(stats), null, 2)}</Code> : <Unavailable what="Live /stats example" className="mt-3" />}92      </Section>9394      <Section eyebrow="Endpoints" title="Public routes (1.1)">95        <DataTable caption="Public endpoints" compact>96          <thead>97            <tr>98              <Th>Route</Th>99              <Th>Group</Th>100              <Th>Returns</Th>101            </tr>102          </thead>103          <tbody>104            {ROUTES.map((r) => (105              <tr key={r.id}>106                <Td primary className="mono text-[12.5px] break-words">107                  {r.method} {r.path}108                  {r.params.length > 0 && <span className="block text-[11px] text-ink-3">{r.params.map((p) => p.name).join(' · ')}</span>}109                </Td>110                <Td label="Group" className="text-xs text-ink-2">{r.group}</Td>111                <Td label="Returns" wide className="text-ink-2">{r.returns}</Td>112              </tr>113            ))}114          </tbody>115        </DataTable>116        <Note className="mt-3">117          Shapes are documented in the OpenAPI schema and in <span className="mono">docs/API.md</span>. Admin routes (<span className="mono">/admin/*</span>) require the <span className="mono">x-aia-admin-token</span> header and are not public. Also: <span className="mono">/benchmarks/{'{slug}'}/frontier</span>, <span className="mono">/pareto</span>, <span className="mono">/cost/context</span>, <span className="mono">/hardware/{'{slug}'}/fit</span>, <span className="mono">/licenses/{'{key}'}</span>, <span className="mono">/compare</span>, <span className="mono">/explore/types</span>, <span className="mono">/explore/{'{type}'}</span>, <span className="mono">POST /views</span>.118        </Note>119      </Section>120121      <Section eyebrow="Conventions" title="Reading responses">122        <ul className="max-w-3xl list-disc space-y-1.5 pl-5 text-sm leading-relaxed text-ink-2">123          <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>124          <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>125          <li>Events: <span className="mono">occurred_at = coalesce(effective_at, observed_at)</span>; <span className="mono">is_backfill</span> marks imported history; <span className="mono">group_key</span> folds one release seen in several documents.</li>126          <li>Universe: <span className="mono">/models</span> lists canonical model releases; artifacts and folded variants are excluded unless <span className="mono">include=artifacts</span>. Old slugs keep resolving (<span className="mono">redirected_from</span>).</li>127          <li>Benchmarks: results live in comparability groups (metric × <span className="mono">config_key</span>); leaderboards are one row per canonical model with a <span className="mono">trust_level</span>.</li>128          <li>Anything marked <span className="mono">estimated: true</span> (hardware fit, memory-derived parameter bounds) is derived by a stated formula — the <span className="mono">assumptions</span> array is part of the response.</li>129          <li>Caching: weak <span className="mono">ETag</span> + <span className="mono">Cache-Control: public, max-age=60, stale-while-revalidate=300</span>; send <span className="mono">If-None-Match</span> for a 304.</li>130        </ul>131      </Section>132133      <Section eyebrow="Terms" title="Fair use">134        <ul className="max-w-3xl list-disc space-y-1.5 pl-5 text-sm leading-relaxed text-ink-2">135          <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>136          <li>Search is rate-limited per IP (HTTP 429 when exceeded). Higher limits and developer keys (<span className="mono">x-api-key</span>) are available on request — see <Link href={routes.about()} className="link">contact</Link>.</li>137          <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>138          <li>Agent-readable access (MCP server, bulk exports) is planned, not available yet — the routes above are the only supported surface today.</li>139        </ul>140      </Section>141    </Container>142  );143}144