BGP churn shapes
GET /bgp/history/churn-shapes
Churn-shape classification per peer session over (from, to]: every close+reopen pair in the candidate changelog is a field diff, classified by SHAPE instead of volume — selection_flips (identical attributes, only is_best moved: the SELECTION oscillates; only assertable about the router itself on a loc_rib feed, otherwise it describes the engine's recomputation), next_hop_only (a re-home inside the neighbour — damp, don't alarm), path_hunting (AS_PATH moved, origin stayed — the MRAI is the resolution floor: sub-MRAI oscillation is invisible by construction and never reads as "stable"), cosmetic (only MED/communities), attribute_changes (the rest), plus genuine announces and terminal withdrawals (seed re-baselines never count). Each session also carries its hot_prefixes — the heavily-skewed hot minority (measured: mean lifetime 40 min vs median 4 min) other views may exclude. Peer-list-driven (peer-scoped range scans, never an AS-wide sweep); a close+reopen within 5 minutes is one event; the per-peer scan is capped (truncated = counts are a lower bound).
Auth: all authenticated roles. Query params: as_id (required); from/to (required, RFC 3339); optional device_id, af (routing_domain_id is accepted but does not apply — bgp_peer carries no routing domain).
Response: 200 OK
{
"from": "2026-08-17T00:00:00Z", "to": "2026-08-18T00:00:00Z",
"peers": [
{ "peer_id": "uuid", "device_id": "uuid", "peer_ip": "172.20.0.1", "address_family": "ipv4", "peer_as": 4242420000,
"selection_flips": 2, "next_hop_only": 41, "path_hunting": 380, "cosmetic": 12, "attribute_changes": 9,
"announces": 55, "withdrawals": 48, "total": 547,
"hot_prefixes": [ { "prefix": "172.23.0.0/24", "transitions": 61 } ], "truncated": false }
],
"full_available": true
}
full_available: false marks emptiness as a coverage refusal (no full-RIB history), never calm. Errors: 400 on a missing as_id or invalid window.