GET /bgp/history/as-graph

AS-Flow snapshot at an instant: autonomous systems as sized bubbles and AS-path adjacencies as weighted edges. The aggregate, AS-centric lens (complements the per-prefix scenario/candidates replay). source=best (default) assembles from the best-path intervals open at at and counts best-path entries (prefix × vantage device); source=candidates assembles from the per-peer RIB — every path the router learned from every peer — and counts candidate entries (prefix × vantage device × peer path). Candidates read live bgp_rib_entry when at is absent or within ±60s of server time (any rib_mode ≠ none), and bgp_rib_entry_history for a historical at behind a scope-aware full-RIB capability gate (as/rd/device/af — a scope the recording never covered answers available:false, not an empty graph). The live lane also answers available:false when no enabled BMP target feeds the scope. A prefix filter is rejected in candidate mode (the candidate lanes do not implement it). Either way the fan is seen from the operator's own routers, never an internet-wide AS graph.

Auth: all authenticated roles

Query params: as_id (required); optional routing_domain_id, device_id, af (ipv4|ipv6), at (RFC 3339, default now), top_origins (default 30, max 200), source (best|candidates, default best), and the optional presence-window pair presence_from/presence_to (RFC 3339, both or neither, presence_from < presence_to) with presence_buckets (default 96, max 200) — see the edge-barcode paragraph below. Ranking and folding: rank_by (origins default | via_transit), rank_at (RFC 3339 — pins fold membership/naming to that instant; lanes that cannot answer there fall back and say so in rank_note), center_asn (re-roots the graph on that AS: only paths traversing it, trimmed at its first occurrence — "AS flow from here"), pin_asns (comma-separated always-named origins, max 50 — the Find control; an organisation is often several ASNs).

Response: 200 OK

{
  "at": "2026-07-03T12:10:00Z",
  "self_asn": 64500,
  "nodes": [
    { "asn": 64500, "role": "self", "count": 850, "reach": 1218, "reach_space": 3121065344 },
    { "asn": 3356, "role": "neighbor", "count": 309, "sessions_down": false, "parent": 64500, "hop": 1, "reach": 811, "reach_space": 2011065344 },
    { "asn": 2914, "role": "transit", "count": 310, "parent": 3356, "hop": 2, "originated": 12, "space": 393216, "deagg": 1.4, "reach": 640, "reach_space": 1811065344 },
    { "asn": 13335, "role": "origin", "count": 214, "parent": 2914, "hop": 3, "originated": 214, "space": 1704960, "deagg": 2.1 },
    { "asn": 0, "role": "other", "count": 414 }
  ],
  "edges": [
    { "from": 64500, "to": 3356, "count": 309 },
    { "from": 3356, "to": 2914, "count": 310 }
  ],
  "other": { "origins": 1204, "count": 414 },
  "folds": [
    { "anchor_asn": 2914, "origins": 802, "count": 291, "space": 88211456 },
    { "anchor_asn": 3356, "origins": 402, "count": 123, "space": 40211456 }
  ],
  "spine": [
    { "asn": 2914, "reach": 640, "reach_space": 1811065344, "coverage_pct": 58.0, "folded_origins": 802, "folded_count": 291, "folded_space": 88211456 },
    { "asn": 6453, "reach": 301, "reach_space": 811065344, "coverage_pct": 84.0 }
  ],
  "off_tree_pct": 17.5,
  "deep_pct": 38.0,
  "origins_total": 1218,
  "origin_mass": 764,
  "unit": "best_path_entries",
  "truncated": true,
  "source": "best",
  "available": true,
  "rank_by": "origins"
}

role is self | neighbor (a directly-peered upstream / first AS-path hop) | transit | origin | other. The other-cloud node uses asn: 0 (RFC 7607 reserved, a safe sentinel) and folds origins beyond top_origins; truncated is then true. Folding hides only the origin bubble — the folded path's transit edges stay visible and its last visible AS edges into the other-cloud (B2); a first-hop (neighbour) AS never folds, even when it originates the prefix itself (such paths are fully drawn and not counted in other). sessions_down marks a neighbour whose sessions to that peer-AS are all down at at. The self node's count is external entries only — locally-originated/internal prefixes (empty AS_PATH) are not exits and are excluded, so it equals the sum of the self → * edges, in both sources.

Ranking fields (all additive; measured over the FULL graph, folded origins included): per node parent (dominant predecessor = heaviest incoming path-adjacency, ties to the lower ASN; absent on the root), hop (BFS distance from the root over ALL observed adjacencies — hop distance over observed adjacencies, not a per-route minimum), originated (entries originating at the AS — 94% of transits originate too), space (unique IPv4 address space it originates: an interval union, never a sum; default routes and AS0-carrying rows excluded), deagg (originated IPv4 prefixes per merged contiguous block), reach/reach_space (distinct originating ASNs in the dominant-tree cone and the TREE-sum of their space — the true per-AS union lives in as-detail's transit_space), and offering_peers (candidate lanes only: distinct peer sessions offering that origin's prefixes). Graph-level: off_tree_pct (share of node-entering mass via a non-dominant predecessor — what a spanning tree cannot draw), deep_pct (origin mass at hop ≥ 5), origins_total/origin_mass (distinct known origins and their entry mass, folded included), spine[] (greedy transit spine with CUMULATIVE coverage_pct; per-vantage — another vantage yields another spine; each member additionally carries folded_origins/folded_count/folded_space: the EXACT rollup of the folds whose anchor's dominant-parent chain first meets that member — the distinct origin union, their additive entry mass, and each distinct origin's union space once — which the reach presentation uses instead of summing the overlapping per-anchor arcs), folds[] (the per-anchor fold list next to the wire-compatible other; fold membership is TRAVERSAL-based — the anchor is the folded path's last visible AS — so one origin can fold under several anchors and Σ folds[].origins ≥ other.origins (measured 1059 vs 968 on the DN42 best lane); consumers must never sum origins/space across anchors — only count partitions (Σ folds[].count = other.count); space per fold sums each anchor's distinct folded origins once — an aggregate claim, never per-origin), rank_by/rank_note/rank_at echoes, and center_asn (on a re-rooted graph the root node carries the center ASN with role self, self_asn stays the true self, and sessions_down is never set — session evidence exists only on the true self's first hop). With rank_by=via_transit the named set is the top-N originating ASNs at hop ≥ 2 by unique IPv4 space (a view, not a fold: only the named candidates' paths are drawn, folds/other stay empty, and the residue is stated in via_transit: {candidates, space_total, named_space}); a scope without IPv4 space figures refuses with rank_note and falls back to origins.

Meta honesty (AG-UNIT/F7): unit is best_path_entries or candidate_entries and MUST be read before comparing counts across captures. source echoes the input lane; candidates additionally carry data_source (live|history), rib_modes (live: distinct enabled bmp_target.rib_mode values in scope; history: ["loc_rib"] when every recorded interval at at carries is_best — the data-derived "candidates ≡ best at this instant" statement — else the targets' current modes), and scope_warning, always present in candidate mode: entries multiply by the peers offering each prefix, and additionally by the number of vantage devices when no device_id is given. available is false only for source=candidates with a historical at outside full-RIB history coverage. Errors: 400 on a missing as_id or invalid af/at/source/rank_by/rank_at/center_asn/pin_asns (pins capped at 50), and on a malformed or half-specified presence window.

Edge-barcode presence lane: when presence_from/presence_to are given and the graph resolves to source=best, each edge additionally carries "presence" — a '0'/'1' string of presence_buckets characters, oldest→newest, marking the window buckets in which the adjacency appears on a recorded best-path interval — and the graph carries presence_buckets/presence_from/presence_to echoes. An all-'0' bitmap is an honest "never recorded in this window" (the query ran); an absent presence key means no bitmap was computed (candidate lane, other-cloud edge, or no window requested). Adjacency extraction mirrors the graph's unfolded weave (self→first hop, consecutive external hops with prepends collapsed, last→origin), so folding never changes recorded presence — and it applies the same center_asn trim and AS0 drop as the graph walk. Candidate-lane graphs never carry presence (no window index on bgp_rib_entry_history, by design), and fold arcs never do: a fold is not an adjacency, and an aggregate stripe would imply the per-origin claim the ⬡-dialect forbids.