import type { Metadata } from 'next'; import Link from 'next/link'; import { t } from '@/i18n'; import { API_URL } from '@/lib/api'; import { API_VERSION, SITE_URL, routes } from '@/lib/site'; import { Section } from '@/components/data/section'; import { CodeBlock } from '@/components/explore/copy-button'; import { PageHeader } from '@/components/explore/page-header'; import { FooterCredits } from '@/components/layout/site-footer'; import { EndpointExplorer, type ExplorerEndpoint } from '@/components/platform/endpoint-explorer'; export const revalidate = 3600; export const metadata: Metadata = { title: t('apiPage.title'), description: t('apiPage.sub'), alternates: { canonical: routes.api() }, }; const BASE = `${SITE_URL}/api/v1`; type Row = [string, string, string]; // endpoint, description, group key const ENDPOINTS: Row[] = [ ['GET /health', t('apiPage.ep.health'), 'reference'], ['GET /countries?region=&income=&q=&sort=', t('apiPage.ep.countries'), 'countries'], ['GET /countries/{id}', t('apiPage.ep.country'), 'countries'], ['GET /countries/{id}/topics/{topic}', t('apiPage.ep.countryTopic'), 'countries'], ['GET /countries/{id}/series/{indicator}?from=&to=', t('apiPage.ep.countrySeries'), 'countries'], ['GET /countries/{id}/changes', t('apiPage.ep.countryChanges'), 'countries'], ['GET /countries/{id}/events', t('apiPage.ep.countryEvents'), 'countries'], ['GET /countries/{id}/similar?mode=', t('apiPage.ep.countrySimilar'), 'countries'], ['GET /countries/{id}/insights', t('apiPage.ep.countryInsights'), 'countries'], ['GET /countries/{id}/dna?reference=', t('apiPage.ep.countryDna'), 'countries'], ['GET /countries/{id}/story', t('apiPage.ep.story'), 'analytics'], ['GET /countries/{id}/quality', t('apiPage.ep.countryQuality'), 'analytics'], ['GET /countries/{id}/download.csv|json', t('apiPage.ep.countryDownload'), 'countries'], ['GET /indicators?topic=&q=&source=', t('apiPage.ep.indicators'), 'indicators'], ['GET /indicators/{slug}', t('apiPage.ep.indicator'), 'indicators'], ['GET /indicators/{slug}/map?year=', t('apiPage.ep.indicatorMap'), 'indicators'], ['GET /indicators/{slug}/trend?group=', t('apiPage.ep.indicatorTrend'), 'indicators'], ['GET /indicators/{slug}/frames?from=&to=', t('apiPage.ep.frames'), 'analytics'], ['GET /indicators/{slug}/distribution?year=&highlight=', t('apiPage.ep.distribution'), 'analytics'], ['GET /indicators/{slug}/related?limit=', t('apiPage.ep.related'), 'analytics'], ['GET /indicators/{slug}/quality', t('apiPage.ep.indicatorQuality'), 'analytics'], ['GET /indicators/{slug}/download.csv|json', t('apiPage.ep.indicatorDownload'), 'indicators'], ['GET /series?country=&indicator=', t('apiPage.ep.series'), 'indicators'], ['GET /rankings?topic=', t('apiPage.ep.rankings'), 'rankings'], ['GET /rankings/{indicator}?year=&group=&sort=', t('apiPage.ep.ranking'), 'rankings'], ['GET /rankings/{indicator}/history?countries=', t('apiPage.ep.rankingHistory'), 'rankings'], ['GET /rankings/{indicator}/race?from=&to=&top=', t('apiPage.ep.race'), 'analytics'], ['GET /compare?countries=&indicators=&mode=', t('apiPage.ep.compare'), 'rankings'], ['GET /compare/snapshot?countries=&topic=', t('apiPage.ep.compareSnapshot'), 'rankings'], ['GET /compare/download.csv|json?countries=&indicators=', t('apiPage.ep.download'), 'rankings'], ['GET /regions?kind=', t('apiPage.ep.regions'), 'rankings'], ['GET /regions/{slug}?indicator=', t('apiPage.ep.region'), 'rankings'], ['GET /regions/compare?a=&b=', t('apiPage.ep.regionsCompare'), 'analytics'], ['GET /pulse', t('apiPage.ep.pulse'), 'analytics'], ['GET /movers?window=&category=&kind=', t('apiPage.ep.movers'), 'analytics'], ['GET /extremes?window=&topic=', t('apiPage.ep.extremes'), 'analytics'], ['GET /scatter?x=&y=&size=&year=', t('apiPage.ep.scatter'), 'analytics'], ['GET /trajectory?x=&y=&size=&from=&to=', t('apiPage.ep.trajectory'), 'analytics'], ['GET /finder?f=slug:op:value&mode=', t('apiPage.ep.finder'), 'analytics'], ['GET /peers?y=&x=&year=', t('apiPage.ep.peers'), 'analytics'], ['GET /search?q=&type=', t('apiPage.ep.search'), 'reference'], ['GET /home', t('apiPage.ep.home'), 'reference'], ['GET /changes?kind=&indicator=&country=', t('apiPage.ep.changes'), 'reference'], ['GET /updates', t('apiPage.ep.updates'), 'analytics'], ['GET /sources', t('apiPage.ep.sources'), 'reference'], ['GET /sources/{id}', t('apiPage.ep.source'), 'reference'], ['GET /methodology', t('apiPage.ep.methodology'), 'reference'], ]; const GROUP_ORDER = ['countries', 'indicators', 'rankings', 'analytics', 'reference'] as const; const EXPLORER: ExplorerEndpoint[] = [ { id: 'country', group: 'Countries', template: '/countries/{id}', summary: t('apiPage.ep.country'), params: [{ name: 'id', in: 'path', description: 'ISO3 code or slug.', example: 'canada' }] }, { id: 'series', group: 'Countries', template: '/countries/{id}/series/{indicator}', summary: t('apiPage.ep.countrySeries'), params: [{ name: 'id', in: 'path', description: 'ISO3 code or slug.', example: 'CAN' }, { name: 'indicator', in: 'path', description: 'Indicator slug.', example: 'gdp-per-capita' }, { name: 'from', in: 'query', description: 'First year.', example: '2000' }, { name: 'to', in: 'query', description: 'Last year.' }, { name: 'include_alt', in: 'query', description: 'Also return values from lower-priority sources.', options: ['false', 'true'] }] }, { id: 'similar', group: 'Countries', template: '/countries/{id}/similar', summary: t('apiPage.ep.countrySimilar'), params: [{ name: 'id', in: 'path', description: 'ISO3 code or slug.', example: 'canada' }, { name: 'mode', in: 'query', description: 'Similarity mode.', options: ['overall', 'economic', 'demographic', 'energy', 'social'], example: 'overall' }, { name: 'limit', in: 'query', description: 'Peers returned (≤ 50).', example: '8' }] }, { id: 'story', group: 'Countries', template: '/countries/{id}/story', summary: t('apiPage.ep.story'), params: [{ name: 'id', in: 'path', description: 'ISO3 code or slug.', example: 'canada' }] }, { id: 'quality', group: 'Countries', template: '/countries/{id}/quality', summary: t('apiPage.ep.countryQuality'), params: [{ name: 'id', in: 'path', description: 'ISO3 code or slug.', example: 'canada' }] }, { id: 'indicator', group: 'Indicators', template: '/indicators/{slug}', summary: t('apiPage.ep.indicator'), params: [{ name: 'slug', in: 'path', description: 'Indicator slug.', example: 'life-expectancy' }] }, { id: 'map', group: 'Indicators', template: '/indicators/{slug}/map', summary: t('apiPage.ep.indicatorMap'), params: [{ name: 'slug', in: 'path', description: 'Indicator slug.', example: 'life-expectancy' }, { name: 'year', in: 'query', description: 'Year (default: latest year with ≥ 50 countries).', example: '2023' }, { name: 'nearest', in: 'query', description: 'Use each country’s latest value within 3 years.', options: ['false', 'true'] }] }, { id: 'trend', group: 'Indicators', template: '/indicators/{slug}/trend', summary: t('apiPage.ep.indicatorTrend'), params: [{ name: 'slug', in: 'path', description: 'Indicator slug.', example: 'gdp' }, { name: 'group', in: 'query', description: 'Group slug (world, oecd, g7, europe-central-asia…).', example: 'oecd' }] }, { id: 'frames', group: 'Indicators', template: '/indicators/{slug}/frames', summary: t('apiPage.ep.frames'), params: [{ name: 'slug', in: 'path', description: 'Indicator slug.', example: 'gdp-per-capita-ppp' }, { name: 'from', in: 'query', description: 'First year.', example: '1990' }, { name: 'to', in: 'query', description: 'Last year.' }] }, { id: 'distribution', group: 'Indicators', template: '/indicators/{slug}/distribution', summary: t('apiPage.ep.distribution'), params: [{ name: 'slug', in: 'path', description: 'Indicator slug.', example: 'life-expectancy' }, { name: 'highlight', in: 'query', description: 'Country to place on the distribution (ISO3).', example: 'CAN' }, { name: 'year', in: 'query', description: 'Year.' }] }, { id: 'related', group: 'Indicators', template: '/indicators/{slug}/related', summary: t('apiPage.ep.related'), params: [{ name: 'slug', in: 'path', description: 'Indicator slug.', example: 'life-expectancy' }, { name: 'limit', in: 'query', description: 'Rows.', example: '12' }] }, { id: 'ranking', group: 'Rankings & comparisons', template: '/rankings/{indicator}', summary: t('apiPage.ep.ranking'), params: [{ name: 'indicator', in: 'path', description: 'Indicator slug.', example: 'gdp-per-capita' }, { name: 'year', in: 'query', description: 'Ranking year (nearest available).' }, { name: 'group', in: 'query', description: 'Group slug (default world).', example: 'world' }, { name: 'sort', in: 'query', description: 'Direction.', options: ['desc', 'asc'] }, { name: 'limit', in: 'query', description: 'Rows (≤ 300).', example: '10' }] }, { id: 'race', group: 'Rankings & comparisons', template: '/rankings/{indicator}/race', summary: t('apiPage.ep.race'), params: [{ name: 'indicator', in: 'path', description: 'Indicator slug.', example: 'gdp' }, { name: 'from', in: 'query', description: 'First year.', example: '1960' }, { name: 'top', in: 'query', description: 'Top N per year.', example: '10' }] }, { id: 'compare', group: 'Rankings & comparisons', template: '/compare', summary: t('apiPage.ep.compare'), params: [{ name: 'countries', in: 'query', description: 'Comma-separated ISO3 or slugs (2–8).', example: 'CAN,USA,FRA', required: true }, { name: 'indicators', in: 'query', description: 'Comma-separated slugs (1–8).', example: 'gdp-per-capita', required: true }, { name: 'mode', in: 'query', description: 'Transformation.', options: ['absolute', 'per-capita', 'index100', 'pct'] }, { name: 'from', in: 'query', description: 'First year.', example: '1990' }] }, { id: 'regions-compare', group: 'Rankings & comparisons', template: '/regions/compare', summary: t('apiPage.ep.regionsCompare'), params: [{ name: 'a', in: 'query', description: 'Group slug.', example: 'g7', required: true }, { name: 'b', in: 'query', description: 'Group slug.', example: 'brics', required: true }] }, { id: 'pulse', group: 'Analytics (1.1)', template: '/pulse', summary: t('apiPage.ep.pulse'), params: [] }, { id: 'movers', group: 'Analytics (1.1)', template: '/movers', summary: t('apiPage.ep.movers'), params: [{ name: 'window', in: 'query', description: 'Years.', options: ['1', '5', '10'], example: '1' }, { name: 'category', in: 'query', description: 'Category.', options: ['all', 'economic', 'demographic', 'health', 'energy', 'climate', 'digital', 'housing', 'labor'] }, { name: 'kind', in: 'query', description: 'Kind filter.', options: ['all', 'improvement', 'deterioration', 'increase', 'decrease', 'record', 'reversal', 'acceleration', 'structural'] }, { name: 'limit', in: 'query', description: 'Rows.', example: '20' }] }, { id: 'extremes', group: 'Analytics (1.1)', template: '/extremes', summary: t('apiPage.ep.extremes'), params: [{ name: 'window', in: 'query', description: 'Window.', options: ['1', '5', '10', '25', 'since1990'], example: '10' }, { name: 'topic', in: 'query', description: 'Topic id filter.' }] }, { id: 'scatter', group: 'Analytics (1.1)', template: '/scatter', summary: t('apiPage.ep.scatter'), params: [{ name: 'x', in: 'query', description: 'X indicator slug.', example: 'gdp-per-capita-ppp', required: true }, { name: 'y', in: 'query', description: 'Y indicator slug.', example: 'life-expectancy', required: true }, { name: 'size', in: 'query', description: 'Bubble size indicator (or none).', example: 'population' }, { name: 'year', in: 'query', description: 'Year.' }, { name: 'group', in: 'query', description: 'Group slug.', example: 'world' }] }, { id: 'trajectory', group: 'Analytics (1.1)', template: '/trajectory', summary: t('apiPage.ep.trajectory'), params: [{ name: 'x', in: 'query', description: 'X indicator slug.', example: 'gdp-per-capita-ppp', required: true }, { name: 'y', in: 'query', description: 'Y indicator slug.', example: 'life-expectancy', required: true }, { name: 'size', in: 'query', description: 'Bubble size indicator.', example: 'population' }, { name: 'from', in: 'query', description: 'First year.', example: '1990' }] }, { id: 'finder', group: 'Analytics (1.1)', template: '/finder', summary: t('apiPage.ep.finder'), params: [{ name: 'f', in: 'query', description: 'Filter slug:op:value (ops gt gte lt lte eq between a..b). One filter here; the API accepts several f= parameters.', example: 'gdp-per-capita:gt:40000', required: true }, { name: 'mode', in: 'query', description: 'Combine filters.', options: ['and', 'or'] }, { name: 'region', in: 'query', description: 'Group slug.' }, { name: 'limit', in: 'query', description: 'Rows (≤ 218).', example: '50' }] }, { id: 'peers', group: 'Analytics (1.1)', template: '/peers', summary: t('apiPage.ep.peers'), params: [{ name: 'y', in: 'query', description: 'Outcome indicator.', example: 'life-expectancy' }, { name: 'x', in: 'query', description: 'Explanatory indicator.', example: 'gdp-per-capita-ppp' }, { name: 'method', in: 'query', description: 'Fit.', options: ['theil-sen', 'ols'] }] }, { id: 'search', group: 'Reference', template: '/search', summary: t('apiPage.ep.search'), params: [{ name: 'q', in: 'query', description: 'Query (try "compare canada usa" or "rank gdp").', example: 'canada gdp', required: true }, { name: 'limit', in: 'query', description: 'Hits.', example: '8' }] }, { id: 'changes', group: 'Reference', template: '/changes', summary: t('apiPage.ep.changes'), params: [{ name: 'kind', in: 'query', description: 'Change kind.', options: ['yoy_jump', 'yoy_drop', 'record_high', 'record_low', 'n_year_high', 'n_year_low', 'sign_flip', 'accelerating', 'decelerating', 'structural_break', 'trend_reversal', 'volatility_spike'] }, { name: 'topic', in: 'query', description: 'Topic id.' }, { name: 'min_severity', in: 'query', description: '0–1.', example: '0.7' }, { name: 'limit', in: 'query', description: 'Rows.', example: '10' }] }, { id: 'updates', group: 'Reference', template: '/updates', summary: t('apiPage.ep.updates'), params: [] }, { id: 'health', group: 'Reference', template: '/health', summary: t('apiPage.ep.health'), params: [] }, ]; const NEW_IN_11 = ['pulse', 'movers', 'extremes', 'scatter', 'trajectory', 'finder', 'peers', 'related', 'distribution', 'frames', 'indicatorQuality', 'race', 'regionsCompare', 'story', 'countryQuality', 'updates'] as const; const NEW_PATHS: Record<(typeof NEW_IN_11)[number], string> = { pulse: '/pulse', movers: '/movers', extremes: '/extremes', scatter: '/scatter', trajectory: '/trajectory', finder: '/finder', peers: '/peers', related: '/indicators/{slug}/related', distribution: '/indicators/{slug}/distribution', frames: '/indicators/{slug}/frames', indicatorQuality: '/indicators/{slug}/quality', race: '/rankings/{indicator}/race', regionsCompare: '/regions/compare', story: '/countries/{id}/story', countryQuality: '/countries/{id}/quality', updates: '/updates', }; const PROVENANCE_FIELDS: Array<[string, string]> = [ ['source / source_name', 'Connector id (worldbank, imf, oecd, eurostat, who, fred, owid, bis, ilo) and its display name.'], ['dataset, series_code', 'Dataset and series inside the source (WDI NY.GDP.PCAP.CD, WEO NGDPDPC, OWID co2_per_capita…).'], ['retrieved_at', 'When the pipeline fetched the payload (UTC).'], ['source_updated_at', 'Last update advertised by the source; null when it publishes none.'], ['url', 'Deep link to the series at the source, with the country when the portal supports it.'], ['transform', 'Expression applied at normalisation (e.g. x*1e9), null if none.'], ['licence', 'Licence of the source for this series.'], ]; async function liveVersion(): Promise { try { const res = await fetch(`${API_URL.replace(/\/$/, '')}/api/v1/health`, { headers: { accept: 'application/json' }, next: { revalidate: 600 } }); if (!res.ok) return null; const j = (await res.json()) as { run_id?: string | null }; return j.run_id ?? null; } catch { return null; } } export default async function ApiPage() { const runId = await liveVersion(); return ( <> {t('apiPage.version', { v: API_VERSION })} {runId ? {t('site.footer.build', { run: runId })} : null} } />

{t('apiPage.intro')}

{t('apiPage.base')}
{BASE}
{t('apiPage.swagger')}
{routes.apiSwagger()}
{t('apiPage.openapi')}
{routes.apiOpenapi()}
    {NEW_IN_11.map((k) => (
  • GET {NEW_PATHS[k]} {t(`apiPage.ep.${k}` as 'apiPage.ep.pulse')}
  • ))}
{GROUP_ORDER.map((g) => (

{t(`apiPage.group.${g}` as 'apiPage.group.countries')}

{ENDPOINTS.filter((e) => e[2] === g).map(([ep, desc]) => ( ))}
{t('apiPage.endpoint')} {t('apiPage.describes')}
{ep} {desc}
))}

{t('apiPage.provenance.text')}

{PROVENANCE_FIELDS.map(([f, d]) => ( ))}
{t('apiPage.field')} {t('apiPage.meaning')}
{f} {d}

{t('apiPage.limits.text')}

{t('apiPage.errors.text')}

{t('apiPage.licence.text')}

{t('sources.title')} → · {t('method.title')} → · {t('download.title')} →

{t('apiPage.contact')}
); }