SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
7.2 KB · 105 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import { Section } from '@/components/ui/primitives';3import { SITE_URL } from '@/lib/site';45export const revalidate = 3600;6export const metadata: Metadata = { title: 'API', description: 'Public, rate-limited JSON API and Server-Sent Events stream of InternetPressure.io — the same API the site uses.' };78const ENDPOINTS: { path: string; desc: string; params?: string }[] = [9  { path: '/api/v1/status', desc: 'Instrument health: engine, ingest, probes, BGP, stores, internal_status.' },10  { path: '/api/v1/pressure/global', desc: 'The index: pressure, level, Δ1h/Δ24h, velocity, acceleration, volatility, confidence, coverage, components with drivers, explain rows, 1-h sparkline.' },11  { path: '/api/v1/pressure/history', desc: 'Server-side aggregated series for any scope.', params: 'scope_type=global|region|country|asn|service|component · scope_id · range=1h|6h|24h|7d|30d|1y' },12  { path: '/api/v1/pressure/regions', desc: 'All regions with pressure, level, Δ1h, component scores, probes, targets, coverage.' },13  { path: '/api/v1/pressure/region/{id}', desc: 'Region detail: 24-h history, 7-d baseline, incidents, top ASNs/services, probes, latency matrix.' },14  { path: '/api/v1/pressure/countries', desc: 'Countries where we hold at least one probe or anchored target — nothing else is pretended.' },15  { path: '/api/v1/pressure/country/{cc}', desc: 'Country detail with history, baseline, incidents, ASNs, services, probes and targets.' },16  { path: '/api/v1/asns', desc: 'Autonomous systems index.' },17  { path: '/api/v1/pressure/asn/{asn}', desc: 'ASN detail: components, BGP stats and 24-h series, regions observed, targets, incidents.' },18  { path: '/api/v1/services', desc: 'Services index with observed availability and vendor status indicator.' },19  { path: '/api/v1/service/{slug}', desc: 'Observed vs vendor, discrepancy, probe × target matrix, affected regions.' },20  { path: '/api/v1/targets', desc: 'Target registry.' },21  { path: '/api/v1/target/{id}', desc: 'Target detail: latest measurement per probe, 24-h series, DNS resolvers.' },22  { path: '/api/v1/probes', desc: 'Probe network: status, last seen, version, uptime, clock offset, capabilities.' },23  { path: '/api/v1/incidents', desc: 'Incident list.', params: 'status=active|resolved|all · limit · offset' },24  { path: '/api/v1/incident/{slug}', desc: 'Incident detail: timeline, evidence, series with global overlay, probes, targets, BGP, annotations.' },25  { path: '/api/v1/fronts', desc: 'Active Pressure Fronts (source → destination arcs).' },26  { path: '/api/v1/bgp/stats', desc: 'BGP rates vs baseline, collectors, 1-h series, top origins.' },27  { path: '/api/v1/latency', desc: 'Global latency medians, inter-region matrix, per-probe view.' },28  { path: '/api/v1/ticker', desc: 'The live ticker counters.' },29  { path: '/api/v1/routes', desc: 'Route Explorer: baseline vs current traceroute, diff, 24-h hashes, 7-d share.', params: 'probe · target' },30  { path: '/api/v1/routes/pairs', desc: 'Sampled (probe, target) pairs with 24-h change counts.' },31  { path: '/api/v1/history/summary', desc: 'History explorer summary.', params: 'year · month' },32  { path: '/api/v1/explain', desc: 'Deep explainability: components → signals with current, median, MAD, robust z, samples, stress, contribution.' },33  { path: '/api/v1/methodology', desc: 'Public copy of the scoring configuration.' },34  { path: '/api/v1/search', desc: 'Search countries, regions, ASNs, services, targets, incidents.', params: 'q' },35  { path: '/api/v1/live', desc: 'Server-Sent Events: snapshot, global_pressure_update, regional_pressure_update, ticker, bgp_stats, probe_stats, incident_created/updated, service_degradation, front_update, internal_status.' },36];3738export default function ApiPage() {39  return (40    <div className="pb-8">41      <header className="pt-6 pb-4">42        <p className="label">Developers</p>43        <h1 className="mt-1 text-[26px] font-medium tracking-tight sm:text-[32px]">Public API</h1>44        <p className="mt-2 max-w-[780px] text-[14px] leading-relaxed text-ink-2">The website is built on the same API. It is free and rate-limited (120 requests per minute per IP, 4 concurrent SSE connections), returns JSON with UTC ISO-8601 timestamps and plain numbers, and is served with <span className="num">Cache-Control: no-store</span> except long-range history (30 s). Unknown scopes return <span className="num">404 {'{"error":"not_found"}'}</span>. Higher limits, webhooks and historical exports: contact@spboucher.ai.</p>45      </header>4647      <Section label="Quick start">48        <pre className="num overflow-x-auto rounded-[4px] border border-line bg-panel p-4 text-[12px] leading-relaxed text-ink">49          {`# the index right now50curl -s ${SITE_URL}/api/v1/pressure/global | jq '{pressure, level, delta_1h, confidence}'5152# 24 h of global pressure, 1-minute step53curl -s "${SITE_URL}/api/v1/pressure/history?scope_type=global&range=24h" | jq '.summary'5455# a country, an ASN, a service56curl -s ${SITE_URL}/api/v1/pressure/country/CA | jq '.pressure'57curl -s ${SITE_URL}/api/v1/pressure/asn/13335 | jq '.bgp'58curl -s ${SITE_URL}/api/v1/service/cloudflare | jq '{observed, vendor_status, discrepancy}'5960# live stream (SSE)61curl -N ${SITE_URL}/api/v1/live`}62        </pre>63      </Section>6465      <Section label="Endpoints" right={<span>{ENDPOINTS.length} public routes</span>}>66        <div className="scroll-x -mx-3 px-3">67        <table className="tbl min-w-[640px]">68          <thead>69            <tr>70              <th>GET</th>71              <th>Returns</th>72              <th className="hidden md:table-cell">Query</th>73            </tr>74          </thead>75          <tbody>76            {ENDPOINTS.map((e) => (77              <tr key={e.path}>78                <td className="num align-top text-ink">79                  <a href={e.path.includes('{') ? undefined : e.path} className={e.path.includes('{') ? '' : 'hover:text-accent'}>80                    {e.path}81                  </a>82                </td>83                <td className="whitespace-normal align-top text-ink-2">{e.desc}</td>84                <td className="num hidden whitespace-normal align-top text-[11px] text-ink-3 md:table-cell">{e.params ?? ''}</td>85              </tr>86            ))}87          </tbody>88        </table>89        </div>90      </Section>9192      <Section label="Levels & status">93        <div className="grid grid-cols-[minmax(0,1fr)] gap-6 text-[13px] text-ink-2 md:grid-cols-2">94          <p>95            Every <span className="num text-ink">level</span> field is one of <span className="num text-ink">calm ≤10 · normal ≤25 · elevated ≤40 · stressed ≤55 · high ≤70 · severe ≤85 · extreme ≤100</span>; <span className="num text-ink">level_label</span> is its human label.96          </p>97          <p>98            <span className="num text-ink">internal_status</span> is <span className="num">ok</span>, <span className="num">degraded</span> (too few fresh probes, stale BGP or unhealthy stores — the score is frozen and <span className="num">stale: true</span>) or <span className="num">stale</span> (engine has not run). Clients must show a degraded state instead of interpreting a frozen number.99          </p>100        </div>101      </Section>102    </div>103  );104}105