InternetPressure public API (v1) — contract
Served by apps/api (FastAPI) on :8352; the Next app (apps/web, :8351) reaches it through the edge on the same
origin (/api/* → api, /ingest/* → api, everything else → web). Browser code therefore always calls relative URLs
(/api/v1/...). All timestamps are UTC ISO-8601 with Z. All numbers are plain JSON numbers (never strings).
Every response carries Cache-Control: no-store unless stated. Unknown scope → 404 {"error":"not_found"}.
Rate limit (public tier): 300 req/min per IP (internal SSR calls exempt); SSE connections: 4 per IP. 429 with Retry-After.
Pressure levels (from packages/config/pressure.yaml): calm ≤10, normal ≤25, elevated ≤40, stressed ≤55,
high ≤70, severe ≤85, extreme ≤100. Every level field is one of these ids; level_label is its label.
Internal status (self-exclusion, §57 of the spec): internal_status is "ok", "degraded" (too few fresh probes /
BGP stale / stores unhealthy — score is frozen at its last value and stale: true) or "stale" (engine hasn't run).
The UI must show a visible "instrument degraded" state instead of interpreting a frozen number.
GET /api/v1/status
{ "ok": true, "ts": "…", "internal_status": "ok", "engine": { "last_run": "…", "cycle_ms": 412, "cycle_seconds": 10 },
"ingest": { "last_batch": "…", "batches_5m": 312, "measurements_5m": 41200 },
"probes": { "fresh": 8, "total": 8, "excluded": [] },
"bgp": { "fresh": true, "last_message": "…", "collectors": 12 },
"stores": { "clickhouse": true, "postgres": true, "redis": true }, "version": "0.1.0" }GET /api/v1/pressure/global
{
"ts": "…", "pressure": 42.7, "level": "stressed", "level_label": "Stressed",
"delta_1h": 6.3, "delta_24h": -2.1, "velocity_per_h": 7.2, "acceleration_per_h2": 3.1, "volatility_1h": 2.4,
"trend": "rising", // rising | falling | stable
"confidence": 0.86, "stale": false, "internal_status": "ok",
"coverage": { "probes_active": 8, "probes_total": 8, "probe_regions": 5, "targets": 212,
"measurements_5m": 41200, "bgp_collectors": 12, "baseline_days": 6.2 },
"components": [
{ "id": "routing", "label": "Routing", "score": 57.1, "weight": 0.25, "contribution": 14.3,
"trend": "rising", "delta_1h": 11.0, "confidence": 0.9,
"drivers": [ { "label": "BGP withdrawals/s 4.8× baseline", "points": 9.1, "scope_type": "bgp", "scope_id": "withdrawals" } ] },
{ "id": "latency", … }, { "id": "dns", … }, { "id": "availability", … }, { "id": "http_tls", … },
{ "id": "path", … }, { "id": "corroboration", … }
],
"explain": [
{ "text": "+14.3 points from elevated BGP route churn", "points": 14.3, "component": "routing", "scope_type": "global", "scope_id": null },
{ "text": "+11.0 from North America East packet loss", "points": 11.0, "component": "latency", "scope_type": "region", "scope_id": "na-east" },
{ "text": "−3.1 because Western Europe remains stable", "points": -3.1, "component": "latency", "scope_type": "region", "scope_id": "eu-west" }
],
"sparkline_1h": [ 36.1, 36.4, … ] // 60 points, 1 per minute, oldest first (null where missing)
}GET /api/v1/pressure/history?scope_type=global|region|country|asn|service|component&scope_id=…&range=1h|6h|24h|7d|30d|1y
Server-side aggregation; step is chosen by the server (1h→10s, 6h→1m, 24h→1m, 7d→5m, 30d→1h, 1y→1d).
{ "scope_type": "global", "scope_id": null, "range": "24h", "step_seconds": 60,
"points": [ { "ts": "…", "pressure": 41.2, "components": { "routing": 55.0, "latency": 40.1, "dns": 12.0,
"availability": 30.3, "http_tls": 22.0, "path": 47.9, "corroboration": 0.0 }, "confidence": 0.85 } ],
"summary": { "min": 21.0, "max": 61.4, "avg": 33.7, "max_ts": "…" } }Cache-Control: public, max-age=30 for ranges ≥ 24h.
GET /api/v1/pressure/regions
{ "ts": "…", "regions": [
{ "id": "na-east", "name": "North America East", "continent": "North America", "lat": 43, "lon": -76,
"pressure": 51.3, "level": "stressed", "level_label": "Stressed", "delta_1h": 12.1, "trend": "rising", "confidence": 0.8,
"components": { "routing": null, "latency": 63.2, "dns": 10.1, "availability": 35.0, "http_tls": 20.0, "path": 58.0 },
"probes": 3, "targets": 41, "incidents": 1, "coverage_ok": true, "role": "both" } ] } // role: probe | target | bothRegional pressure = stress observed from probes in the region (source view) blended with stress observed toward
targets anchored in the region (destination view); routing is null for regions without ASN attribution.
GET /api/v1/pressure/region/{id}
Region object above plus: history_24h ({step_seconds, points:[{ts,pressure}]}), baseline_7d ({median, p90}),
incidents (list, see incidents), top_asns ([{asn, name, pressure}]), top_services ([{slug, name, pressure, observed_availability_24h}]),
probes (probe list objects), matrix (latency matrix rows for this region, see /latency).
GET /api/v1/pressure/countries
{ "ts": "…", "countries": [ { "cc": "CA", "name": "Canada", "region": "na-east", "lat": 56.1, "lon": -106.3,
"pressure": 34.0, "level": "elevated", "level_label": "Elevated", "delta_1h": 2.0, "trend": "stable",
"components": { … }, "probes": 2, "targets": 18, "role": "both", "coverage_ok": true } ] }Only countries where we have at least one probe or one anchored target appear. The map colours these; every other country is drawn neutral (we do not pretend to observe it).
GET /api/v1/pressure/country/{cc}
Country object plus history_24h, baseline_7d, incidents, asns, services, probes, targets
([{target_id, name, category, pressure, ok_ratio_1h, ttfb_ms_median}]).
GET /api/v1/asns
{ "ts": …, "asns": [ { "asn": 13335, "name": "Cloudflare", "country": "US", "pressure": 18.2, "level": "normal", "routing": 12.0, "latency": 22.1, "availability": 9.0, "targets": 6, "prefixes_observed": 1200, "importance": 5 } ] }
GET /api/v1/pressure/asn/{asn}
{ "asn": 13335, "name": "Cloudflare, Inc.", "country": "US", "importance": 5, "ts": "…",
"pressure": 18.2, "level": "normal", "level_label": "Normal", "delta_1h": -1.0, "trend": "falling", "confidence": 0.7,
"components": { "routing": 12.0, "latency": 22.1, "availability": 9.0, "dns": 4.0, "http_tls": 6.0, "path": 15.0 },
"bgp": { "prefixes_observed_24h": 1200, "announcements_1h": 340, "withdrawals_1h": 12, "churn_ratio": 1.1,
"origin_changes_1h": 0, "path_stability": 0.97, "series_24h": [ { "ts": "…", "announcements": 5, "withdrawals": 0 } ] },
"regions_observed": [ "na-east", "eu-west", "eu-east-med" ],
"targets": [ { "target_id": "cloudflare-www", "name": "Cloudflare", "pressure": 12.0, "ok_ratio_1h": 1.0, "ttfb_ms_median": 71.0 } ],
"history_24h": { "step_seconds": 60, "points": [ … ] }, "incidents": [ … ] }GET /api/v1/services
{ "services": [ { "slug": "cloudflare", "name": "Cloudflare", "category": "cdn", "pressure": 12.0, "level": "normal", "observed_availability_24h": 0.9994, "targets": 6, "affected_regions": [], "vendor_status": { "indicator": "none", "incidents": 0, "source": "status.cloudflare.com", "checked_at": "…" } } ] }
vendor_status is null when we have no connector for that provider.
GET /api/v1/service/{slug}
Service object plus:
{ "observed": { "availability_24h": 0.9994, "availability_1h": 1.0, "ttfb_ms_median_1h": 71.0, "ttfb_ms_baseline": 68.0,
"tls_ms_median_1h": 30.1, "failures_1h": 2 },
"affected_regions": [ { "id": "na-east", "name": "…", "observation": "Elevated TLS latency from 2 probes" } ],
"vendor_status": { "indicator": "minor", "incidents": 1, "titles": ["…"], "source": "…", "url": "…", "checked_at": "…" },
"discrepancy": "Vendor reports no incident; we observe elevated TLS latency from 4 probe regions.", // or null
"matrix": [ { "probe_id": "ca-qc-01", "probe_region": "na-east", "targets": [ { "target_id": "…", "ok": true, "ttfb_ms": 70.0, "z": 0.4, "ts": "…" } ] } ],
"targets": [ … ], "history_24h": { … }, "incidents": [ … ] }GET /api/v1/targets · GET /api/v1/target/{id}
List: { "targets": [ { "target_id", "name", "hostname", "category", "provider", "service_id", "country", "region", "importance", "tier", "pressure", "ok_ratio_1h", "ttfb_ms_median_1h" } ] }.
Detail adds latest_by_probe ([{probe_id, kind, ts, ok, error, dns_ms, tcp_ms, tls_ms, ttfb_ms, http_status, resolved_ip, packet_loss, rtt_avg_ms, z}]),
series_24h ({step_seconds, points:[{ts, ttfb_ms_p50, ok_ratio}]}) and dns ({ resolvers: [{resolver, rcode, answers, ms}], disagreement: false }).
GET /api/v1/probes
{ "probes": [ { "probe_id": "ca-qc-01", "name": "Québec City (Bell)", "region": "na-east", "country": "CA", "city": "Québec",
"provider": "Bell Canada", "asn": 577, "lat": 46.8, "lon": -71.2, "status": "online", // online | stale | offline | excluded
"last_seen": "…", "version": "0.1.0", "measurements_1h": 5120, "uptime_24h": 0.998, "clock_offset_ms": -14,
"capabilities": ["http","dns","ping","traceroute"] } ] }GET /api/v1/incidents?status=active|resolved|all&limit=50&offset=0
{ "total": 3, "incidents": [ {
"event_id": "evt_01J…", "slug": "2026-09-12-north-america-east-latency-anomaly",
"type": "regional_latency", // regional_latency | dns_disruption | routing_instability | service_degradation | availability_loss | path_instability | global_pressure
"title": "North America East latency anomaly", "summary": "Elevated latency and packet loss observed from 3 probes toward 41 targets.",
"status": "active", // detected | developing | active | recovering | resolved
"scope_type": "region", "scope_id": "na-east", "scope_label": "North America East",
"started_at": "…", "updated_at": "…", "ended_at": null, "duration_s": 1260,
"peak_pressure": 76.0, "current_pressure": 71.2, "confidence": 0.93,
"affected_probes": 3, "affected_targets": 41, "affected_asns": [577, 16276], "affected_services": ["aws", "github"],
"hypotheses": [ { "text": "Possible upstream transit issue", "confidence": 0.6,
"evidence": ["Route fingerprints changed on 62 % of paths", "Latency rose on paths crossing AS6453"] } ]
} ] }GET /api/v1/incident/{slug}
Incident object plus timeline ([{ts, status, pressure, note}]), evidence ([{signal_id, label, scope_type, scope_id, current, baseline, robust_z, samples, ts}]), series ({step_seconds, points:[{ts, pressure, global_pressure}]} from 30 min
before start to now/end), probes ([{probe_id, region, observation}]), targets ([{target_id, name, service_id, observation}]),
bgp ({withdrawals_ratio, announcements_ratio, origin_changes} or null), annotations ([{ts, author, text}]).
GET /api/v1/fronts
{ "ts": "…", "fronts": [ { "id": "front_na-east_eu-west", "name": "North Atlantic Pressure Front", "status": "developing",
"intensity": 74.0, "confidence": 0.89, "direction": "east", "since": "…",
"from": { "region": "na-east", "name": "North America East", "lat": 43, "lon": -76 },
"to": { "region": "eu-west", "name": "Western Europe", "lat": 49, "lon": 3 },
"observed": { "latency_pct": 43.0, "churn_x": 4.8, "loss_pct": 3.1, "pairs": 17, "targets": 17, "route_changes": 9 } } ] }GET /api/v1/bgp/stats
{ "ts": "…", "fresh": true, "updates_per_s": 812.4, "announcements_per_s": 760.0, "withdrawals_per_s": 52.4,
"baseline": { "announcements_per_s": 640.0, "withdrawals_per_s": 11.0 }, "ratio": { "announcements": 1.19, "withdrawals": 4.76 },
"unique_prefixes_1m": 14211, "unique_origins_1m": 2210, "origin_changes_1m": 3, "peers": 1450,
"collectors": [ { "id": "rrc00", "location": "Amsterdam", "announcements_per_s": 120.1, "withdrawals_per_s": 8.0, "peers": 210, "last_message": "…", "fresh": true } ],
"series_1h": [ { "ts": "…", "announcements": 7600, "withdrawals": 520 } ], // per minute
"top_origins_1h": [ { "asn": 13335, "name": "Cloudflare", "announcements": 340, "withdrawals": 12 } ] }GET /api/v1/latency
{ "ts": "…", "global": { "rtt_ms_median": 41.2, "rtt_ms_baseline": 39.0, "ttfb_ms_median": 118.0, "ttfb_ms_baseline": 110.0, "packet_loss_pct": 0.3 },
"matrix": [ { "from": "na-east", "to": "eu-west", "rtt_ms": 92.1, "rtt_ms_baseline": 88.0, "ttfb_ms": 160.0, "loss_pct": 0.0, "z": 0.6, "pairs": 37 } ],
"by_probe": [ { "probe_id": "ca-qc-01", "rtt_ms_median": 30.1, "ttfb_ms_median": 90.0, "loss_pct": 0.0, "z": 0.2 } ] }GET /api/v1/ticker
{ "ts": "…", "bgp_updates_per_s": 812.4, "bgp_withdrawals_per_s": 52.4, "bgp_updates_per_min": 48744,
"probes_active": 8, "probes_total": 8, "measurements_per_s": 12.3, "measurements_per_min": 738,
"targets_degraded": 4, "targets_total": 212, "regions_elevated": 3, "regions_normal": 9, "regions_severe": 0,
"dns_failures_per_min": 2, "median_global_rtt_ms": 41.2, "route_changes_per_min": 1.2, "active_incidents": 1,
"internal_status": "ok" }GET /api/v1/routes?probe={probe_id}&target={target_id}
{ "probe": { "probe_id": "…", "name": "…", "asn": 577 }, "target": { "target_id": "…", "name": "…", "hostname": "…", "asn": 13335 },
"current": { "ts": "…", "route_hash": "…", "reached": true, "total_ms": 30.2,
"hops": [ { "n": 1, "ip": "192.168.2.1", "asn": null, "asn_name": null, "rtt_ms": 1.2, "private": true } ] },
"baseline": { "route_hash": "…", "share_7d": 0.82, "first_seen": "…", "last_seen": "…", "hops": [ … ] },
"diff": { "changed": true, "added": [ { "n": 6, "ip": "…", "asn": 6453 } ], "removed": [ … ],
"asn_path_current": [577, 6453, 13335], "asn_path_baseline": [577, 577, 13335], "latency_shift_ms": 12.4, "hop_delta": 1 },
"history_24h": [ { "ts": "…", "route_hash": "…", "hop_count": 11, "total_ms": 30.2 } ],
"route_share_7d": [ { "route_hash": "…", "share": 0.82, "asn_path": [ … ] } ] }GET /api/v1/routes/pairs → { "pairs": [ { "probe_id", "target_id", "changed_24h": 3, "current_route_hash", "stable": false } ] }.
GET /api/v1/history/summary?year=2026&month=9
{ "year": 2026, "month": 9, "days": [ { "date": "2026-09-12", "min": 18.0, "max": 61.4, "avg": 30.2, "events": 2 } ],
"top_events": [ incident objects, by peak_pressure ], "top_asns": [ { "asn", "name", "events", "max_pressure" } ],
"top_regions": [ { "id", "name", "events", "max_pressure", "hours_elevated" } ],
"largest": { "pressure": {…incident}, "routing": {…}, "dns": {…}, "latency": {…} }, "available_months": ["2026-09"] }Without month → per-month rows in months instead of days. Without year → all years.
GET /api/v1/explain
Deep explainability: { "ts", "pressure", "components": [ { "id", "score", "weight", "contribution", "signals": [ { "signal_id": "ttfb_z", "label": "…", "scope_type": "region", "scope_id": "na-east", "current": 161.0, "baseline_median": 110.0, "mad": 9.0, "robust_z": 5.6, "samples": 412, "stress": 0.71, "contribution": 6.2 } ] } ], "excluded_probes": [], "notes": [ "…" ] }
GET /api/v1/methodology
Public copy of the scoring config: { "weights": {…}, "levels": [ … ], "engine": { "cycle_seconds", "baseline_days", "z_anomaly", … }, "version": 1, "updated_at": "…" }.
GET /api/v1/search?q=
{ "results": [ { "type": "country|asn|service|region|target|incident", "id": "…", "label": "…", "href": "/asn/13335", "pressure": 12.0 } ] }
GET /api/v1/live — Server-Sent Events
Headers Content-Type: text/event-stream, Cache-Control: no-store, X-Accel-Buffering: no. Sends retry: 5000, an id: per
message, a : ping comment every 15 s, and on connect an immediate snapshot event.
| event | data |
|---|---|
snapshot |
{ "global": <GET /pressure/global>, "ticker": <GET /ticker>, "regions": <regions[]>, "fronts": <fronts[]>, "incidents": <active incidents[]> } |
global_pressure_update |
same shape as GET /pressure/global (sent every engine cycle, i.e. only when the engine actually ran) |
regional_pressure_update |
{ "ts", "regions": [ … ], "countries": [ { "cc", "pressure", "level", "delta_1h" } ] } |
ticker |
<GET /ticker> (every 5 s, from real counters) |
bgp_stats |
{ "ts", "updates_per_s", "announcements_per_s", "withdrawals_per_s", "ratio": {…}, "fresh" } (every 5 s) |
probe_stats |
{ "ts", "probes_active", "probes_total", "measurements_per_s", "excluded": [] } |
incident_created / incident_updated |
incident object |
service_degradation |
{ "ts", "slug", "name", "pressure", "regions": [ … ], "observation": "…" } |
front_update |
<GET /fronts> |
internal_status |
{ "ts", "internal_status", "reason" } |
Nothing is ever emitted without a corresponding backend computation — if the engine pauses, the stream only pings.
Admin API (/api/admin/*, header X-IP-Admin-Token: <token>)
GET /api/admin/overview→{ "probes": [ { …probe, "health": { "uptime_24h", "clock_offset_ms", "missing_ratio_1h", "error_rate_1h", "buffered", "spool_bytes", "version", "last_health" } } ], "ingest": { "batches_per_min", "measurements_per_min", "rejected_per_min", "last_batch" }, "stores": { "clickhouse": { "ok", "inserts_per_s", "tables": [ { "name", "rows", "bytes", "oldest", "newest" } ] }, "postgres": { "ok", "size_bytes" }, "redis": { "ok", "used_memory_bytes", "keys" } }, "bgp": { "collectors": [ … ], "messages_per_s", "fresh", "reconnects_24h" }, "engine": { "last_run", "cycle_ms_p50", "cycle_ms_max", "runs_1h", "errors_1h", "internal_status", "excluded_probes" }, "corroboration": [ { "id", "name", "ok", "last_fetch", "incidents" } ] }GET /api/admin/targets·POST /api/admin/targets(target object) ·PATCH /api/admin/targets/{id}·DELETE /api/admin/targets/{id}GET /api/admin/probes·POST /api/admin/probes{probe_id, name, region, country, city, provider, asn, lat, lon}→{ …probe, "key": "<hex, shown once>" }·PATCH /api/admin/probes/{id}(enabled, metadata) ·POST /api/admin/probes/{id}/rotate-key→ new keyGET /api/admin/config→ fullpressure.yamlas JSON ·PUT /api/admin/config(validates: weights sum to 1 ± 0.001) → stored in Postgresconfigand applied next cycleGET /api/admin/baselines?signal_id=&scope_type=&scope_id=→{ "signal_id", "points": [ { "ts", "value", "median", "mad", "z" } ], "samples", "baseline_days" }GET /api/admin/raw?table=measurements|traceroutes|bgp_events|bgp_stats|pressure_history|signal_features|probe_health&probe_id=&target_id=&limit=200→{ "columns": [ … ], "rows": [ [ … ] ] }GET /api/admin/incidents?status=·PATCH /api/admin/incidents/{id}{ "review": "confirmed|dismissed|unreviewed", "note": "…" }POST /api/admin/annotations{ "ts", "scope_type", "scope_id", "text" }·GET /api/admin/annotationsPOST /api/admin/replay{ "from": "…", "to": "…", "weights": {…} }→{ "step_seconds", "points": [ { "ts", "pressure_original", "pressure_replayed" } ] }POST /api/admin/boost{ "targets": [ … ], "factor": 0.5, "seconds": 900 }— manual sampling boost pushed to probes.
Errors: 401 {"error":"unauthorized"}, 422 {"error":"validation", "detail": …}.