{"openapi":"3.1.0","jsonSchemaDialect":"https://spec.openapis.org/oas/3.1/dialect/base","info":{"title":"Swift Fox API","version":"1","description":"Read your network's devices, backhauls, CPEs, issues and metrics, and run live tests from your pingboxes.\n\n**Authentication.** Send an API key in the Authorization header: `Authorization: Bearer sfk_…`. Never put a key in the URL: a request with one there is refused (400) without the key being checked.\n\n**ISPs.** Most endpoints take `isp`, an ISP code from `GET /isps`. Leave it out when your key reaches exactly one ISP.\n\n**CSV.** Every list endpoint also returns CSV: add `format=csv`, or send `Accept: text/csv`. Columns follow the JSON field order, and nested fields are flattened with dots (`ap.name`).\n\n**Paging.** Lists return `next_cursor`, plus a `Link: <…>; rel=\"next\"` header. Pass it back as `cursor` to get the next page; it's null on the last one. Treat cursors as opaque.\n\n**Errors.** Every error is `application/problem+json` (RFC 9457) with a non-2xx status, so `wget` and `curl --fail` exit non-zero instead of saving an error as data. Branch on `type`; `detail` is written for people.\n\n**Scopes.** A key carries scopes: `read` for every GET, `actions` for the POST endpoints (live tests and config collection), and `pii` to see subscriber names in CPE results. A key can never do more than the user or service account that owns it.\n\n**Rate limits.** Per key, 120 reads and 10 actions a minute; per ISP, 600 reads a minute. Over a limit you get 429 with `Retry-After`.\n\n**Versioning.** New fields, endpoints and optional parameters aren't breaking changes, so ignore fields you don't recognise. Fields documented as \"more may be added\" (a pingbox's status, for example) can gain new values, so handle ones you don't recognise. A breaking change means a new version, with at least 12 months of overlap.","contact":{"name":"Swift Fox support","url":"https://swiftfox.net","email":"support@swift-fox.net"}},"servers":[{"url":"https://api.swiftfox.net/v1"}],"security":[{"apiKey":[]}],"tags":[{"name":"ISPs","description":"The ISPs a key can access, and the codes to pass as isp"},{"name":"Sites","description":"Towers and other locations, and the regions they belong to"},{"name":"Devices","description":"Infrastructure devices (routers, APs, backhaul radios, switches): detail, live health and config history"},{"name":"Backhauls","description":"Backhaul links: endpoints, capacity, usage and cross-link pings"},{"name":"CPEs","description":"Customer radios: plan, signal, shaping and live health"},{"name":"Pingboxes","description":"The vantage points inside your network that ping your devices and run live tests"},{"name":"Issues","description":"Issues and alerts, open and resolved"},{"name":"RF","description":"The RF channel plan"},{"name":"Events","description":"What changed around a time: config and channel changes, issues"},{"name":"Addresses","description":"What's known about an IP address"},{"name":"Live tests","description":"Ping and traceroute from a pingbox inside the network"}],"paths":{"/backhauls":{"get":{"operationId":"list_backhauls","summary":"List backhauls","description":"Your backhaul links: the router and radio at each end, the capacity Swift Fox uses for the link and where it comes from (fixed by hand, bandwidth tests or the radios), the latest traffic, the latest ping across the link, and alert_state: ok, recovering (alerted recently but back to good), or the severity of the worst open issue. Filter by site or region (matching any end) or search names with q. While ping monitoring is on, backhauls are pinged router to router almost continuously, so get_backhaul_history (series pings) has detailed history. Directions are named for the link's ends: ap_to_su is toward the SU end (down) and su_to_ap is from the SU end (up). The ap_router/ap_radio and su_router/su_radio objects say which devices those ends are.","tags":["Backhauls"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"site","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Restrict to backhauls with any end at this site: site_id or site code"},{"name":"region","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to backhauls with any end in this region: region_id from list_regions"},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Substring match on backhaul name"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max rows (default 100, max 300)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"backhauls":{"type":"array","items":{"type":"object","properties":{"bhid":{"type":"integer"},"name":{"type":"string"},"link_type":{"type":"string","description":"e.g. wireless, wired"},"ap_router":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name","site"],"description":"The router at the AP end"},"su_router":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name","site"],"description":"The router at the SU end"},"ap_radio":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name","site"],"description":"The radio at the AP end"},"su_radio":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name","site"],"description":"The radio at the SU end"},"capacity":{"type":"object","properties":{"source":{"type":"string","description":"Where the capacity figure comes from: fixed (set by hand), tcptest (bandwidth tests) or wireless (the radios' link rate)"},"current_mbps":{"type":["object","null"],"properties":{"ap_to_su":{"type":["number","null"]},"su_to_ap":{"type":["number","null"]}},"required":["ap_to_su","su_to_ap"],"description":"The capacity Swift Fox uses now: the fixed figure, or the latest measurement"},"measured_at":{"type":["string","null"],"format":"date-time","description":"When current_mbps was measured; null when it's fixed"}},"required":["source","current_mbps","measured_at"]},"traffic_mbps":{"type":["object","null"],"properties":{"ap_to_su":{"type":["number","null"]},"su_to_ap":{"type":["number","null"]}},"required":["ap_to_su","su_to_ap"],"description":"Latest traffic across the link (Mbps)"},"speedtest_enabled":{"type":"boolean"},"ping_enabled":{"type":"boolean"},"ping":{"type":["object","null"],"properties":{"latency_ms":{"type":"number"},"loss_pct":{"type":"number"}},"required":["latency_ms","loss_pct"],"description":"Latest cross-link ping; null when ping monitoring is off or has no data"},"alert_state":{"type":"string","enum":["ok","recovering","info","warning","outage","critical"],"description":"ok (no issues), recovering (alerted recently, back to good), or the worst open issue's severity"}},"required":["bhid","name","link_type","ap_router","su_router","ap_radio","su_radio","capacity","traffic_mbps","speedtest_enabled","ping_enabled","ping","alert_state"]}}},"required":["count","next_cursor","backhauls"]},"example":{"count":1,"next_cursor":null,"backhauls":[{"bhid":12,"name":"RB-HT","link_type":"wireless","ap_router":{"infid":101,"name":"Core Router","site":{"site_id":3,"code":"RB-CORE","name":"Riverbend Data Centre"}},"su_router":{"infid":214,"name":"Hilltop Router","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"}},"ap_radio":{"infid":215,"name":"RB-HT AP","site":{"site_id":3,"code":"RB-CORE","name":"Riverbend Data Centre"}},"su_radio":{"infid":216,"name":"RB-HT SU","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"}},"capacity":{"source":"tcptest","current_mbps":{"ap_to_su":612.4,"su_to_ap":598.1},"measured_at":"2026-10-08T22:06:26Z"},"traffic_mbps":{"ap_to_su":534.2,"su_to_ap":38.2},"speedtest_enabled":true,"ping_enabled":true,"ping":{"latency_ms":1.12,"loss_pct":0},"alert_state":"warning"}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/backhauls/{bhid}":{"get":{"operationId":"get_backhaul","summary":"Get a backhaul","description":"Everything about one backhaul: the router and radio at each end, how its capacity is set and each source's latest reading (the bandwidth test's, with the traffic during the test, and the radios'), the latest traffic, ping across the link, and open issues. A router end's speedtest_ip is the address bandwidth tests and link pings use on that end, not a management address; each end's ip is the device's own address. History: get_backhaul_history with series pings, traffic or capacity. Directions are named for the link's ends: ap_to_su is toward the SU end (down) and su_to_ap is from the SU end (up). The ap_router/ap_radio and su_router/su_radio objects say which devices those ends are.","tags":["Backhauls"],"x-scope":"read","parameters":[{"name":"bhid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"Backhaul id from list_backhauls"},{"$ref":"#/components/parameters/isp"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"bhid":{"type":"integer"},"name":{"type":"string"},"link_type":{"type":"string"},"ap_router":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"ip":{"type":["string","null"]},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"model":{"type":["string","null"]},"interface":{"type":["string","null"],"description":"The backhaul's interface on this router"},"speedtest_ip":{"type":["string","null"],"description":"The address bandwidth tests and link pings target on this end; not a management address"}},"required":["infid","name","site","ip","hardware_type","model","interface","speedtest_ip"],"description":"The router at the AP end"},"su_router":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"ip":{"type":["string","null"]},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"model":{"type":["string","null"]},"interface":{"type":["string","null"],"description":"The backhaul's interface on this router"},"speedtest_ip":{"type":["string","null"],"description":"The address bandwidth tests and link pings target on this end; not a management address"}},"required":["infid","name","site","ip","hardware_type","model","interface","speedtest_ip"],"description":"The router at the SU end"},"ap_radio":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"ip":{"type":["string","null"]},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"model":{"type":["string","null"]}},"required":["infid","name","site","ip","hardware_type","model"],"description":"The radio at the AP end"},"su_radio":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"ip":{"type":["string","null"]},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"model":{"type":["string","null"]}},"required":["infid","name","site","ip","hardware_type","model"],"description":"The radio at the SU end"},"capacity":{"type":"object","properties":{"source":{"type":"string","description":"Where the capacity figure comes from: fixed (set by hand), tcptest (bandwidth tests) or wireless (the radios' link rate)"},"current_mbps":{"type":["object","null"],"properties":{"ap_to_su":{"type":["number","null"]},"su_to_ap":{"type":["number","null"]}},"required":["ap_to_su","su_to_ap"],"description":"The capacity Swift Fox uses now: the fixed figure, or the latest measurement"},"measured_at":{"type":["string","null"],"format":"date-time","description":"When current_mbps was measured; null when it's fixed"},"fixed_mbps":{"type":["object","null"],"properties":{"ap_to_su":{"type":["number","null"]},"su_to_ap":{"type":["number","null"]}},"required":["ap_to_su","su_to_ap"],"description":"The figure set by hand (used when source is fixed)"},"tcptest":{"type":["object","null"],"properties":{"ap_to_su_mbps":{"type":["number","null"]},"su_to_ap_mbps":{"type":["number","null"]},"used_during_test_mbps":{"type":["object","null"],"properties":{"ap_to_su":{"type":["number","null"]},"su_to_ap":{"type":["number","null"]}},"required":["ap_to_su","su_to_ap"],"description":"Traffic already on the link while the test ran"},"at":{"type":["string","null"],"format":"date-time"}},"required":["ap_to_su_mbps","su_to_ap_mbps","used_during_test_mbps","at"],"description":"The latest bandwidth test"},"wireless":{"type":["object","null"],"properties":{"ap_to_su_mbps":{"type":["number","null"]},"su_to_ap_mbps":{"type":["number","null"]},"as_of":{"type":["string","null"],"format":"date-time"}},"required":["ap_to_su_mbps","su_to_ap_mbps","as_of"],"description":"The radios' latest link rate"}},"required":["source","current_mbps","measured_at","fixed_mbps","tcptest","wireless"]},"traffic_mbps":{"type":["object","null"],"properties":{"ap_to_su":{"type":["number","null"]},"su_to_ap":{"type":["number","null"]},"as_of":{"type":["string","null"],"format":"date-time"}},"required":["ap_to_su","su_to_ap","as_of"],"description":"Latest traffic across the link (Mbps)"},"speedtest_enabled":{"type":"boolean"},"speedtest_streams":{"type":["integer","null"],"description":"TCP streams per bandwidth test"},"ping":{"type":["object","null"],"properties":{"latency_ms":{"type":"number"},"loss_pct":{"type":["number","null"]},"recent_avg_latency_ms":{"type":"number"}},"required":["latency_ms","loss_pct","recent_avg_latency_ms"]},"issues":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string"},"details":{"type":"string"},"severity":{"type":"string","enum":["ok","info","warning","outage","critical"],"description":"ok while recovering, otherwise info, warning, outage or critical"},"since":{"type":["string","null"],"format":"date-time","description":"When the issue started"},"status":{"type":"string","enum":["active","pending","recovering"]},"recovered_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the metric went back to good"},"resolves_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the issue clears if it stays good"}},"required":["metric","details","severity","since","status","recovered_at","resolves_at"]}}},"required":["bhid","name","link_type","ap_router","su_router","ap_radio","su_radio","capacity","traffic_mbps","speedtest_enabled","speedtest_streams","ping","issues"]},"example":{"bhid":12,"name":"RB-HT","link_type":"wireless","ap_router":{"infid":101,"name":"Core Router","site":{"site_id":3,"code":"RB-CORE","name":"Riverbend Data Centre"},"ip":"10.10.0.1","hardware_type":{"id":"mikrotik","name":"Mikrotik"},"model":"CCR2116-12G-4S+","interface":"sfp1-HT","speedtest_ip":"10.10.250.1"},"su_router":{"infid":214,"name":"Hilltop Router","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"ip":"10.10.7.1","hardware_type":{"id":"mikrotik","name":"Mikrotik"},"model":"CCR2004-16G-2S+","interface":"sfp1-RB","speedtest_ip":"10.10.250.2"},"ap_radio":{"infid":215,"name":"RB-HT AP","site":{"site_id":3,"code":"RB-CORE","name":"Riverbend Data Centre"},"ip":"10.10.250.10","hardware_type":{"id":"ubiquiti","name":"Ubiquiti"},"model":"airFiber 60 LR"},"su_radio":{"infid":216,"name":"RB-HT SU","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"ip":"10.10.250.11","hardware_type":{"id":"ubiquiti","name":"Ubiquiti"},"model":"airFiber 60 LR"},"capacity":{"source":"tcptest","current_mbps":{"ap_to_su":612.4,"su_to_ap":598.1},"measured_at":"2026-10-08T22:06:26Z","fixed_mbps":null,"tcptest":{"ap_to_su_mbps":612.4,"su_to_ap_mbps":598.1,"used_during_test_mbps":{"ap_to_su":498.3,"su_to_ap":35.6},"at":"2026-10-08T22:06:25Z"},"wireless":{"ap_to_su_mbps":1850,"su_to_ap_mbps":1850,"as_of":"2026-10-08T22:09:40Z"}},"traffic_mbps":{"ap_to_su":534.2,"su_to_ap":38.2,"as_of":"2026-10-08T22:09:45Z"},"speedtest_enabled":true,"speedtest_streams":3,"ping":{"latency_ms":1.08,"loss_pct":0,"recent_avg_latency_ms":1.12},"issues":[{"metric":"Backhaul:Usage Percent Down","details":"Warning - 87.2% down used - 534 / 612 mbit","severity":"warning","since":"2026-10-08T21:55:03Z","status":"active","recovered_at":null,"resolves_at":null}]}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/backhauls/{bhid}/health":{"get":{"operationId":"get_backhaul_health","summary":"Get a backhaul's health","description":"The live monitored metrics for one device, backhaul or CPE: each metric's latest value and unit, its threshold state and severity, and any open issue. It's the same list as get_device's health, for backhauls and CPEs too. For devices, entries with has_history have history in get_device_history (series = the metric name). An issue's status is active (alerting now), pending (alerting, but still inside its grace period, so not alert-worthy yet) or recovering (back to good, but held open a while in case it comes back: recovered_at is when it went good, and resolves_at is when it clears if it stays good).","tags":["Backhauls"],"x-scope":"read","parameters":[{"name":"bhid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"infid / bhid / cpeid"},{"$ref":"#/components/parameters/isp"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"id":{"type":"integer"},"metrics":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string"},"sub":{"type":"string","description":"Which value of a metric with several on one entity (a radio chain, a vulnerability)"},"description":{"type":"string"},"value":{"type":["number","null"],"description":"Latest collected value"},"unit":{"type":"string"},"state":{"type":"string","description":"Threshold state name for the value, e.g. Good, Warning, Down; Inactive = the metric has no thresholds set"},"severity":{"type":["string","null"],"enum":["ok","info","warning","outage","critical",null],"description":"ok, info, warning, outage or critical for the state's level; null when the metric has no thresholds"},"value_ttl_seconds":{"type":["integer","null"],"description":"Seconds until the value is considered stale; -1 = no expiry set"},"issue":{"type":["object","null"],"properties":{"details":{"type":"string"},"severity":{"type":"string","enum":["ok","info","warning","outage","critical"],"description":"ok while recovering, otherwise info, warning, outage or critical"},"since":{"type":["string","null"],"format":"date-time","description":"When the issue started"},"status":{"type":"string","enum":["active","pending","recovering"]},"recovered_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the metric went back to good"},"resolves_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the issue clears if it stays good"}},"required":["details","severity","since","status","recovered_at","resolves_at"]},"has_history":{"type":"boolean","description":"Devices: true when the metric's history is available (REST: /devices/{infid}/history/{metric})"}},"required":["metric","value","state","severity","value_ttl_seconds","issue"]}}},"required":["type","id","metrics"]},"example":{"type":"backhaul","id":12,"metrics":[{"metric":"Backhaul:Ping Loss","description":"The percentage packet loss from a high-res ping test between routers on each side of a backhaul","value":0,"unit":"%","state":"Good","severity":"ok","value_ttl_seconds":604798,"issue":null},{"metric":"Backhaul:Usage Percent Down","description":"Percentage of the link's capacity in use toward the SU end","value":87.2,"unit":"%","state":"Warning","severity":"warning","value_ttl_seconds":604798,"issue":{"details":"Warning - 87.2% down used - 534 / 612 mbit","severity":"warning","since":"2026-10-08T21:55:03Z","status":"active","recovered_at":null,"resolves_at":null}}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/backhauls/{bhid}/history/{series}":{"get":{"operationId":"get_backhaul_history","summary":"Get a backhaul's history","description":"History for one device, backhaul or CPE, rolled up into time buckets (up to 200, or max_points up to 500), so long ranges stay small. Series: devices have pings, quality, signal, traffic (with interface), or a health metric name from get_device whose has_history is true; backhauls have pings, quality, traffic or capacity; CPEs have pings, traffic, plan, quality or signal. Each point has its bucket's start time (at), the values for the series and how many readings went into it (samples). pings: loss_avg_pct, loss_max_pct, latency_avg_ms, latency_min_ms, latency_max_ms. traffic, capacity and plan: avg and max Mbps per direction (an interface's in/out, a backhaul's ap_to_su/su_to_ap, a CPE's down/up). quality: quality_avg, quality_min (0 down to 6 perfect). signal: one point per radio chain per bucket. Health metrics: value_avg, value_min, value_max in unit. A missing bucket means nothing was recorded then.","tags":["Backhauls"],"x-scope":"read","parameters":[{"name":"bhid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"The infid, bhid or cpeid"},{"name":"series","in":"path","required":true,"schema":{"type":"string"},"description":"Which history; see the operation's description"},{"$ref":"#/components/parameters/isp"},{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["tcptest","wireless"]},"description":"Backhaul capacity only: which measurement (default: the backhaul's own capacity source)"},{"name":"period","in":"query","required":false,"schema":{"type":"string","enum":["1hour","4hours","day","week","month","year"]},"description":"Lookback from now (default day). Use from/to instead for a past window"},{"name":"from","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range start: unix seconds or 'YYYY-MM-DD HH:MM:SS' UTC"},{"name":"to","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range end (default: now)"},{"name":"max_points","in":"query","required":false,"schema":{"type":"integer"},"description":"Point budget (default 200, max 500); buckets are sized to stay under it"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"id":{"type":"integer"},"series":{"type":"string"},"interface":{"type":"string","description":"Device traffic: the interface"},"source":{"type":"string","description":"Backhaul capacity: the measurement shown"},"unit":{"type":["string","null"],"description":"Health metrics: the value's unit. Other series carry the unit in their field names"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"bucket_seconds":{"type":"integer"},"point_count":{"type":"integer"},"points":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","format":"date-time","description":"Bucket start"},"chain":{"type":"string","description":"signal only: the radio chain"}},"required":["at"],"additionalProperties":{"type":["number","null"]},"description":"One bucket; the value fields depend on the series (see the operation's description)"}}},"required":["type","id","series","unit","from","to","bucket_seconds","point_count","points"]},"example":{"type":"backhaul","id":12,"series":"pings","unit":null,"from":"2026-10-08T21:00:00Z","to":"2026-10-08T22:00:00Z","bucket_seconds":1800,"point_count":2,"points":[{"at":"2026-10-08T21:00:00Z","loss_avg_pct":0,"loss_max_pct":0,"latency_avg_ms":1.11,"latency_min_ms":0.93,"latency_max_ms":14.2,"samples":180},{"at":"2026-10-08T21:30:00Z","loss_avg_pct":0.4,"loss_max_pct":5,"latency_avg_ms":2.48,"latency_min_ms":0.94,"latency_max_ms":79.6,"samples":180}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/channels":{"get":{"operationId":"list_channels","summary":"List RF channels","description":"Your RF channel plan: every wireless radio's centre frequency, channel width and duplex, with live signal, noise and MCS. overlap_candidates lists the other radios whose channels overlap it, which is handy for chasing interference and planning channels. Overlap only means the channels collide; whether the radios can hear each other depends on where they are and which way they point. Pass infid to also get that device's channel history. Radios that haven't reported in 7 days are left out, and at most 10,000 radios are considered.","tags":["RF"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"site","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Restrict to radios at this site: site_id or site code"},{"name":"infid","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to one device and include its channel change history"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max radios per page (default 200, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"radios":{"type":"array","items":{"type":"object","properties":{"device":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The radio"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"radio":{"type":"string","description":"Radio group on the device"},"center_mhz":{"type":"number"},"width_mhz":{"type":"number"},"range_mhz":{"type":["string","null"],"description":"e.g. 5170-5190"},"duplex":{"type":"string"},"chains":{"type":"integer"},"signal_dbm":{"type":["number","null"],"description":"Worst chain"},"noise_dbm":{"type":["number","null"],"description":"Worst chain"},"mcs":{"type":["number","null"],"description":"Lowest chain"},"as_of":{"type":"string","format":"date-time"},"overlap_candidates":{"type":"array","items":{"type":"object","properties":{"device":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The other radio"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"center_mhz":{"type":"number"}},"required":["device","site","center_mhz"]},"description":"Radios in this result whose channel overlaps this one's (spectral collision only)"}},"required":["device","site","radio","center_mhz","width_mhz","range_mhz","duplex","chains","signal_dbm","noise_dbm","mcs","as_of","overlap_candidates"]}},"channel_history":{"type":"array","items":{"type":"object","properties":{"radio":{"type":"string"},"center_mhz":{"type":"number"},"width_mhz":{"type":"number"},"duplex":{"type":"string"},"observed_at":{"type":"string","format":"date-time"}},"required":["radio","center_mhz","width_mhz","duplex","observed_at"]},"description":"With infid only: up to 30 changes, newest first"}},"required":["count","next_cursor","radios"]},"example":{"count":2,"next_cursor":null,"radios":[{"device":{"infid":230,"name":"Hilltop Sector 1"},"site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"radio":"Wireless","center_mhz":5745,"width_mhz":40,"range_mhz":"5725-5765","duplex":"Half","chains":2,"signal_dbm":null,"noise_dbm":-94,"mcs":null,"as_of":"2026-10-08T22:10:05Z","overlap_candidates":[{"device":{"infid":318,"name":"Mill Valley Sector 2"},"site":{"site_id":9,"code":"MV","name":"Mill Valley Tower"},"center_mhz":5755}]},{"device":{"infid":318,"name":"Mill Valley Sector 2"},"site":{"site_id":9,"code":"MV","name":"Mill Valley Tower"},"radio":"Wireless","center_mhz":5755,"width_mhz":20,"range_mhz":"5745-5765","duplex":"Half","chains":2,"signal_dbm":null,"noise_dbm":-91,"mcs":null,"as_of":"2026-10-08T22:10:02Z","overlap_candidates":[{"device":{"infid":230,"name":"Hilltop Sector 1"},"site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"center_mhz":5745}]}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/cpes":{"get":{"operationId":"list_cpes","summary":"List CPEs","description":"Your customers' radios (CPEs). Filter by access point, site or region (through the access point), plan, disconnected accounts, or account_number (the number on the customer's invoices), or search with q, which matches MAC and IP addresses, and names too with the pii scope. Each one comes with its billing plan's rates and effective_rate_mbps: what the shaper is applying right now, which is the plan, lowered when the network is congested or the customer is over a usage cap. Subscriber names need the pii scope; without it, CPEs are named by MAC address and account number.","tags":["CPEs"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"ap","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to CPEs associated to this access point (its infid)"},{"name":"site","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Restrict to CPEs whose access point is at this site: site_id or site code"},{"name":"region","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to CPEs whose access point is in this region: region_id from list_regions"},{"name":"plan","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to one billing plan: a plan_id from a CPE's plan"},{"name":"disconnected","in":"query","required":false,"schema":{"type":"boolean"},"description":"true: only CPEs on disconnected accounts; false: only connected ones"},{"name":"account_number","in":"query","required":false,"schema":{"type":"string"},"description":"Exact customer account number (as shown on invoices)"},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Substring match: MAC/IP always; account+device names only with pii scope"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max rows (default 200, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"cpes":{"type":"array","items":{"type":"object","properties":{"cpeid":{"type":"integer"},"mac":{"type":"string"},"ip":{"type":["string","null"]},"ap":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The access point it's associated to"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The access point's site"},"account_number":{"type":["string","null"],"description":"The customer-visible account number, as on invoices"},"account_disconnected":{"type":["boolean","null"]},"signal_dbm":{"type":["number","null"]},"plan":{"type":["object","null"],"properties":{"plan_id":{"type":["integer","null"],"description":"Pass as plan to list_cpes"},"name":{"type":["string","null"]},"down_mbps":{"type":["number","null"]},"up_mbps":{"type":["number","null"]}},"required":["plan_id","name","down_mbps","up_mbps"],"description":"The billing plan and its configured rates"},"effective_rate_mbps":{"type":["object","null"],"properties":{"down":{"type":["number","null"]},"up":{"type":["number","null"]}},"required":["down","up"],"description":"What the shaper applies right now: the plan reduced by congestion multipliers or usage caps"},"monitored":{"type":"boolean"},"last_assoc":{"type":["string","null"],"format":"date-time"},"name":{"type":"string","description":"The subscriber's name with the pii scope; otherwise 'CPE <mac> (account <number>)'"},"account_name":{"type":["string","null"],"description":"pii scope only"},"description":{"type":["string","null"],"description":"pii scope only"},"discovered_name":{"type":["string","null"],"description":"pii scope only: the name the radio reports"}},"required":["cpeid","mac","ip","ap","site","account_number","account_disconnected","signal_dbm","plan","effective_rate_mbps","monitored","last_assoc","name"]}}},"required":["count","next_cursor","cpes"]},"example":{"count":1,"next_cursor":"eyJ2IjoxLCJvIjoyMDB9","cpes":[{"cpeid":5012,"mac":"00:00:5E:00:53:1A","ip":"10.50.7.112","ap":{"infid":230,"name":"Hilltop Sector 1"},"site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"account_number":"10442","account_disconnected":false,"signal_dbm":-61,"plan":{"plan_id":4,"name":"Residential 50","down_mbps":50,"up_mbps":10},"effective_rate_mbps":{"down":50,"up":10},"monitored":true,"last_assoc":"2026-10-08T22:01:37Z","name":"CPE 00:00:5E:00:53:1A (account 10442)"}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/cpes/{cpeid}":{"get":{"operationId":"get_cpe","summary":"Get a CPE","description":"One customer radio: who it belongs to, its access point, billing plan, what the shaper is applying right now, its signal and last association, and its live health. History: get_cpe_history with series pings, traffic, plan, quality or signal. Subscriber names need the pii scope; without it, CPEs are named by MAC address and account number. An issue's status is active (alerting now), pending (alerting, but still inside its grace period, so not alert-worthy yet) or recovering (back to good, but held open a while in case it comes back: recovered_at is when it went good, and resolves_at is when it clears if it stays good).","tags":["CPEs"],"x-scope":"read","parameters":[{"name":"cpeid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"CPE id from list_cpes / ip_lookup / list_issue_events"},{"$ref":"#/components/parameters/isp"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"cpeid":{"type":"integer"},"mac":{"type":"string"},"ip":{"type":["string","null"]},"ap":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The access point it's associated to"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The access point's site"},"account_number":{"type":["string","null"],"description":"The customer-visible account number, as on invoices"},"account_disconnected":{"type":["boolean","null"]},"signal_dbm":{"type":["number","null"]},"plan":{"type":["object","null"],"properties":{"plan_id":{"type":["integer","null"],"description":"Pass as plan to list_cpes"},"name":{"type":["string","null"]},"down_mbps":{"type":["number","null"]},"up_mbps":{"type":["number","null"]}},"required":["plan_id","name","down_mbps","up_mbps"],"description":"The billing plan and its configured rates"},"effective_rate_mbps":{"type":["object","null"],"properties":{"down":{"type":["number","null"]},"up":{"type":["number","null"]}},"required":["down","up"],"description":"What the shaper applies right now: the plan reduced by congestion multipliers or usage caps"},"monitored":{"type":"boolean"},"last_assoc":{"type":["string","null"],"format":"date-time"},"name":{"type":"string","description":"The subscriber's name with the pii scope; otherwise 'CPE <mac> (account <number>)'"},"account_name":{"type":["string","null"],"description":"pii scope only"},"description":{"type":["string","null"],"description":"pii scope only"},"discovered_name":{"type":["string","null"],"description":"pii scope only: the name the radio reports"},"health":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string"},"sub":{"type":"string","description":"Which value of a metric with several on one entity (a radio chain, a vulnerability)"},"description":{"type":"string"},"value":{"type":["number","null"],"description":"Latest collected value"},"unit":{"type":"string"},"state":{"type":"string","description":"Threshold state name for the value, e.g. Good, Warning, Down; Inactive = the metric has no thresholds set"},"severity":{"type":["string","null"],"enum":["ok","info","warning","outage","critical",null],"description":"ok, info, warning, outage or critical for the state's level; null when the metric has no thresholds"},"value_ttl_seconds":{"type":["integer","null"],"description":"Seconds until the value is considered stale; -1 = no expiry set"},"issue":{"type":["object","null"],"properties":{"details":{"type":"string"},"severity":{"type":"string","enum":["ok","info","warning","outage","critical"],"description":"ok while recovering, otherwise info, warning, outage or critical"},"since":{"type":["string","null"],"format":"date-time","description":"When the issue started"},"status":{"type":"string","enum":["active","pending","recovering"]},"recovered_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the metric went back to good"},"resolves_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the issue clears if it stays good"}},"required":["details","severity","since","status","recovered_at","resolves_at"]},"has_history":{"type":"boolean","description":"Devices: true when the metric's history is available (REST: /devices/{infid}/history/{metric})"}},"required":["metric","value","state","severity","value_ttl_seconds","issue"]}}},"required":["cpeid","mac","ip","ap","site","account_number","account_disconnected","signal_dbm","plan","effective_rate_mbps","monitored","last_assoc","name","health"]},"example":{"cpeid":5012,"mac":"00:00:5E:00:53:1A","ip":"10.50.7.112","ap":{"infid":230,"name":"Hilltop Sector 1"},"site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"account_number":"10442","account_disconnected":false,"signal_dbm":-61,"plan":{"plan_id":4,"name":"Residential 50","down_mbps":50,"up_mbps":10},"effective_rate_mbps":{"down":50,"up":10},"monitored":true,"last_assoc":"2026-10-08T22:01:37Z","name":"CPE 00:00:5E:00:53:1A (account 10442)","health":[{"metric":"Ping:Quality","description":"A weighted quality rating based on packet loss and latency.","value":5,"unit":"","state":"Good","severity":"ok","value_ttl_seconds":604780,"issue":null},{"metric":"Wireless:Signal","sub":"Chain 0","description":"Receive Signal Strength (RSSI) in dBm","value":-61,"unit":"dBm","state":"Good","severity":"ok","value_ttl_seconds":604780,"issue":null}]}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/cpes/{cpeid}/health":{"get":{"operationId":"get_cpe_health","summary":"Get a CPE's health","description":"The live monitored metrics for one device, backhaul or CPE: each metric's latest value and unit, its threshold state and severity, and any open issue. It's the same list as get_device's health, for backhauls and CPEs too. For devices, entries with has_history have history in get_device_history (series = the metric name). An issue's status is active (alerting now), pending (alerting, but still inside its grace period, so not alert-worthy yet) or recovering (back to good, but held open a while in case it comes back: recovered_at is when it went good, and resolves_at is when it clears if it stays good).","tags":["CPEs"],"x-scope":"read","parameters":[{"name":"cpeid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"infid / bhid / cpeid"},{"$ref":"#/components/parameters/isp"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"id":{"type":"integer"},"metrics":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string"},"sub":{"type":"string","description":"Which value of a metric with several on one entity (a radio chain, a vulnerability)"},"description":{"type":"string"},"value":{"type":["number","null"],"description":"Latest collected value"},"unit":{"type":"string"},"state":{"type":"string","description":"Threshold state name for the value, e.g. Good, Warning, Down; Inactive = the metric has no thresholds set"},"severity":{"type":["string","null"],"enum":["ok","info","warning","outage","critical",null],"description":"ok, info, warning, outage or critical for the state's level; null when the metric has no thresholds"},"value_ttl_seconds":{"type":["integer","null"],"description":"Seconds until the value is considered stale; -1 = no expiry set"},"issue":{"type":["object","null"],"properties":{"details":{"type":"string"},"severity":{"type":"string","enum":["ok","info","warning","outage","critical"],"description":"ok while recovering, otherwise info, warning, outage or critical"},"since":{"type":["string","null"],"format":"date-time","description":"When the issue started"},"status":{"type":"string","enum":["active","pending","recovering"]},"recovered_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the metric went back to good"},"resolves_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the issue clears if it stays good"}},"required":["details","severity","since","status","recovered_at","resolves_at"]},"has_history":{"type":"boolean","description":"Devices: true when the metric's history is available (REST: /devices/{infid}/history/{metric})"}},"required":["metric","value","state","severity","value_ttl_seconds","issue"]}}},"required":["type","id","metrics"]},"example":{"type":"cpe","id":5012,"metrics":[{"metric":"Ping:Quality","description":"A weighted quality rating based on packet loss and latency.","value":5,"unit":"","state":"Good","severity":"ok","value_ttl_seconds":604780,"issue":null},{"metric":"Wireless:Signal","sub":"Chain 0","description":"Receive Signal Strength (RSSI) in dBm","value":-61,"unit":"dBm","state":"Good","severity":"ok","value_ttl_seconds":604780,"issue":null}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/cpes/{cpeid}/history/{series}":{"get":{"operationId":"get_cpe_history","summary":"Get a CPE's history","description":"History for one device, backhaul or CPE, rolled up into time buckets (up to 200, or max_points up to 500), so long ranges stay small. Series: devices have pings, quality, signal, traffic (with interface), or a health metric name from get_device whose has_history is true; backhauls have pings, quality, traffic or capacity; CPEs have pings, traffic, plan, quality or signal. Each point has its bucket's start time (at), the values for the series and how many readings went into it (samples). pings: loss_avg_pct, loss_max_pct, latency_avg_ms, latency_min_ms, latency_max_ms. traffic, capacity and plan: avg and max Mbps per direction (an interface's in/out, a backhaul's ap_to_su/su_to_ap, a CPE's down/up). quality: quality_avg, quality_min (0 down to 6 perfect). signal: one point per radio chain per bucket. Health metrics: value_avg, value_min, value_max in unit. A missing bucket means nothing was recorded then.","tags":["CPEs"],"x-scope":"read","parameters":[{"name":"cpeid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"The infid, bhid or cpeid"},{"name":"series","in":"path","required":true,"schema":{"type":"string"},"description":"Which history; see the operation's description"},{"$ref":"#/components/parameters/isp"},{"name":"period","in":"query","required":false,"schema":{"type":"string","enum":["1hour","4hours","day","week","month","year"]},"description":"Lookback from now (default day). Use from/to instead for a past window"},{"name":"from","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range start: unix seconds or 'YYYY-MM-DD HH:MM:SS' UTC"},{"name":"to","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range end (default: now)"},{"name":"max_points","in":"query","required":false,"schema":{"type":"integer"},"description":"Point budget (default 200, max 500); buckets are sized to stay under it"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"id":{"type":"integer"},"series":{"type":"string"},"interface":{"type":"string","description":"Device traffic: the interface"},"source":{"type":"string","description":"Backhaul capacity: the measurement shown"},"unit":{"type":["string","null"],"description":"Health metrics: the value's unit. Other series carry the unit in their field names"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"bucket_seconds":{"type":"integer"},"point_count":{"type":"integer"},"points":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","format":"date-time","description":"Bucket start"},"chain":{"type":"string","description":"signal only: the radio chain"}},"required":["at"],"additionalProperties":{"type":["number","null"]},"description":"One bucket; the value fields depend on the series (see the operation's description)"}}},"required":["type","id","series","unit","from","to","bucket_seconds","point_count","points"]},"example":{"type":"cpe","id":5012,"series":"traffic","unit":null,"from":"2026-10-08T21:00:00Z","to":"2026-10-08T22:00:00Z","bucket_seconds":1800,"point_count":2,"points":[{"at":"2026-10-08T21:00:00Z","down_avg_mbps":12.4,"down_max_mbps":41.7,"up_avg_mbps":1.1,"up_max_mbps":3.9,"samples":30},{"at":"2026-10-08T21:30:00Z","down_avg_mbps":18.9,"down_max_mbps":47.2,"up_avg_mbps":1.6,"up_max_mbps":4.4,"samples":30}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices":{"get":{"operationId":"list_devices","summary":"List devices","description":"Your infrastructure devices: routers, access points, backhaul radios, switches and so on (customer radios are in list_cpes). Filter by site, region, the pingbox they're pinged from, device class or hardware type, or search names and IP addresses with q. get_device has a device's interfaces, addresses and live health.","tags":["Devices"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"site","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Restrict to one site: site_id or site code from list_sites"},{"name":"region","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to sites in this region: region_id from list_regions"},{"name":"pingbox","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to devices pinged from this pingbox: a pingboxid from list_pingboxes"},{"name":"device_class","in":"query","required":false,"schema":{"type":"string"},"description":"Exact device class, e.g. Router, AP, Switch"},{"name":"hardware_type","in":"query","required":false,"schema":{"type":"string"},"description":"A hardware_type id, e.g. mikrotik, ubiquiti, cambium, snmp"},{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Substring match on device name or IP address"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max rows (default 200, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"devices":{"type":"array","items":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"},"device_class":{"type":"string","description":"e.g. Router, AP, Switch, Backhaul AP"},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"model":{"type":["string","null"]},"ip":{"type":["string","null"],"description":"Management IPv4 address"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"pingbox":{"type":"object","properties":{"pingboxid":{"type":"integer"},"name":{"type":"string"}},"required":["pingboxid","name"],"description":"The pingbox this device is pinged from (list_pingboxes)"},"parent":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The device it hangs off in the network map"}},"required":["infid","name","device_class","hardware_type","model","ip","site","pingbox","parent"]}}},"required":["count","next_cursor","devices"]},"example":{"count":3,"next_cursor":"eyJ2IjoxLCJvIjozfQ","devices":[{"infid":101,"name":"Core Router","device_class":"Router","hardware_type":{"id":"mikrotik","name":"Mikrotik"},"model":"CCR2116-12G-4S+","ip":"10.10.0.1","site":{"site_id":3,"code":"RB-CORE","name":"Riverbend Data Centre"},"pingbox":{"pingboxid":0,"name":"Riverbend Cloud"},"parent":null},{"infid":214,"name":"Hilltop Router","device_class":"Router","hardware_type":{"id":"mikrotik","name":"Mikrotik"},"model":"CCR2004-16G-2S+","ip":"10.10.7.1","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"pingbox":{"pingboxid":1,"name":"Hilltop"},"parent":{"infid":101,"name":"Core Router"}},{"infid":230,"name":"Hilltop Sector 1","device_class":"AP","hardware_type":{"id":"cambium","name":"Cambium"},"model":"ePMP 4500","ip":"10.10.7.21","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"pingbox":{"pingboxid":1,"name":"Hilltop"},"parent":{"infid":214,"name":"Hilltop Router"}}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}":{"get":{"operationId":"get_device","summary":"Get a device","description":"Everything about one infrastructure device: what and where it is, its parent device, its interfaces and IP addresses, and its live health. Health has an entry for each metric the device reports, with the latest value, its threshold state and severity, and any open issue. A metric with several values on one device, like signal per radio chain, repeats with a sub naming each one. Entries with has_history have history in get_device_history (series = the metric name), and each interface's traffic is in get_interface_traffic. value_ttl_seconds is how long until a value counts as stale (-1 means it doesn't expire). Interface names that carry a subscriber's login (PPPoE and similar) and staff comments need the pii scope. An issue's status is active (alerting now), pending (alerting, but still inside its grace period, so not alert-worthy yet) or recovering (back to good, but held open a while in case it comes back: recovered_at is when it went good, and resolves_at is when it clears if it stays good).","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"Device infrastructure id from list_devices / list_backhauls"},{"$ref":"#/components/parameters/isp"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"},"device_class":{"type":"string","description":"e.g. Router, AP, Switch, Backhaul AP"},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"model":{"type":["string","null"]},"ip":{"type":["string","null"],"description":"Management IPv4 address"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"pingbox":{"type":"object","properties":{"pingboxid":{"type":"integer"},"name":{"type":"string"}},"required":["pingboxid","name"],"description":"The pingbox this device is pinged from (list_pingboxes)"},"firmware":{"type":["string","null"]},"parent":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The device it hangs off in the network map"},"main_ip_interface_label":{"type":["string","null"],"description":"User-entered label for the interface holding the main IP; often stale"},"interfaces":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Pass as interface for its traffic history (REST: /devices/{infid}/traffic?interface=<name>)"},"type":{"type":["string","null"]},"mac":{"type":["string","null"]},"comment":{"type":["string","null"]},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["name","type","mac","comment","last_seen"]}},"local_ips":{"type":"array","items":{"type":"object","properties":{"ip":{"type":"string"},"interface":{"type":["string","null"]},"comment":{"type":["string","null"]}},"required":["ip","interface","comment"]}},"health":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string"},"sub":{"type":"string","description":"Which value of a metric with several on one entity (a radio chain, a vulnerability)"},"description":{"type":"string"},"value":{"type":["number","null"],"description":"Latest collected value"},"unit":{"type":"string"},"state":{"type":"string","description":"Threshold state name for the value, e.g. Good, Warning, Down; Inactive = the metric has no thresholds set"},"severity":{"type":["string","null"],"enum":["ok","info","warning","outage","critical",null],"description":"ok, info, warning, outage or critical for the state's level; null when the metric has no thresholds"},"value_ttl_seconds":{"type":["integer","null"],"description":"Seconds until the value is considered stale; -1 = no expiry set"},"issue":{"type":["object","null"],"properties":{"details":{"type":"string"},"severity":{"type":"string","enum":["ok","info","warning","outage","critical"],"description":"ok while recovering, otherwise info, warning, outage or critical"},"since":{"type":["string","null"],"format":"date-time","description":"When the issue started"},"status":{"type":"string","enum":["active","pending","recovering"]},"recovered_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the metric went back to good"},"resolves_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the issue clears if it stays good"}},"required":["details","severity","since","status","recovered_at","resolves_at"]},"has_history":{"type":"boolean","description":"Devices: true when the metric's history is available (REST: /devices/{infid}/history/{metric})"}},"required":["metric","value","state","severity","value_ttl_seconds","issue"]}}},"required":["infid","name","device_class","hardware_type","model","ip","site","pingbox","firmware","parent","main_ip_interface_label","interfaces","local_ips","health"]},"example":{"infid":230,"name":"Hilltop Sector 1","device_class":"AP","hardware_type":{"id":"cambium","name":"Cambium"},"model":"ePMP 4500","ip":"10.10.7.21","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"pingbox":{"pingboxid":1,"name":"Hilltop"},"firmware":"5.10.2","parent":{"infid":214,"name":"Hilltop Router"},"main_ip_interface_label":null,"interfaces":[{"name":"eth0","type":"ethernetCsmacd","mac":"00:00:5E:00:53:21","comment":null,"last_seen":"2026-10-08T22:08:07Z"}],"local_ips":[{"ip":"10.10.7.21","interface":"br0","comment":null}],"health":[{"metric":"Health:CPU Usage","description":"CPU percentage used as reported by the device","value":23,"unit":"%","state":"Good","severity":"ok","value_ttl_seconds":604790,"issue":null,"has_history":true},{"metric":"Health:Temperature","description":"Device temperature is outside threshold","value":71,"unit":"deg C","state":"Warning","severity":"warning","value_ttl_seconds":604790,"issue":{"details":"Warning - 71 deg C","severity":"warning","since":"2026-10-08T21:40:12Z","status":"active","recovered_at":null,"resolves_at":null},"has_history":true},{"metric":"Wireless:Signal","sub":"Chain 0","description":"Receive Signal Strength (RSSI) in dBm","value":-58,"unit":"dBm","state":"Good","severity":"ok","value_ttl_seconds":604790,"issue":null,"has_history":false},{"metric":"Ping:Quality","description":"A weighted quality rating based on packet loss and latency. 1 = Down/Black … 6 = Bright Green.","value":6,"unit":"","state":"Good","severity":"ok","value_ttl_seconds":604800,"issue":null,"has_history":false}]}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/config-collections":{"post":{"operationId":"collect_config_now","summary":"Collect a config now","description":"Collect a device's config backup now instead of waiting for its turn. It tells you whether the config changed: backups are only stored when it does, so collected with no change is the normal, healthy result. Works for MikroTik, Ubiquiti and Cambium devices, and needs the edit_inf permission.\n\nNeeds the `actions` scope.","tags":["Devices"],"x-scope":"actions","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"Device infrastructure id"},{"$ref":"#/components/parameters/isp"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"device":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The device"},"collected":{"type":"boolean","description":"false when the device answered but returned no config"},"config_changed":{"type":"boolean"},"latest_revision":{"type":["object","null"],"properties":{"revision":{"type":"integer"},"changed_at":{"type":"string","format":"date-time"}},"required":["revision","changed_at"],"description":"The newest stored revision, for get_config or diff_configs"},"last_collected_at":{"type":["string","null"],"format":"date-time","description":"When the config was last read successfully, whether or not it changed"}},"required":["device","collected","config_changed","latest_revision","last_collected_at"]},"example":{"device":{"infid":101,"name":"Core Router"},"collected":true,"config_changed":false,"latest_revision":{"revision":1791468760,"changed_at":"2026-10-08T14:12:40Z"},"last_collected_at":"2026-10-08T22:16:05Z"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/configs":{"get":{"operationId":"list_config_changes","summary":"List config changes","description":"When a device's config changed, newest first. Pass a revision to get_config for that config, or two of them to diff_configs to see what changed. A backup is only stored when the config changes, so a revision's time is when the config changed. last_collected_at is when the config was last read, changed or not: if it's recent, an old newest revision just means a stable config; if it's old, collection is failing, and the device's Task:Connection issue usually says why.","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"Device infrastructure id"},{"$ref":"#/components/parameters/isp"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max entries (default 50, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"infid":{"type":"integer"},"last_collected_at":{"type":["string","null"],"format":"date-time","description":"When the config was last read successfully, whether or not it changed; null if no successful read is recorded"},"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"changes":{"type":"array","items":{"type":"object","properties":{"revision":{"type":"integer","description":"The revision's id (unix seconds): pass it to get_config or diff_configs"},"changed_at":{"type":"string","format":"date-time"},"format":{"type":"string","description":"The config's syntax: mikrotik (a RouterOS export), ubiquiti or cambium"},"size_bytes":{"type":"integer"},"content_hash":{"type":"string"}},"required":["revision","changed_at","format","size_bytes","content_hash"]},"description":"Newest first"}},"required":["infid","last_collected_at","count","next_cursor","changes"]},"example":{"infid":101,"last_collected_at":"2026-10-08T22:00:12Z","count":2,"next_cursor":null,"changes":[{"revision":1791468760,"changed_at":"2026-10-08T14:12:40Z","format":"mikrotik","size_bytes":18214,"content_hash":"3f1c2a9e6b0d47c8a5e2f19b7d6c4a0e8b5f3d21"},{"revision":1789981391,"changed_at":"2026-09-21T09:03:11Z","format":"mikrotik","size_bytes":18166,"content_hash":"9a0e4b1c7d2f5e8a3b6c9d0e1f2a3b4c5d6e7f80"}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/configs/{revision}":{"get":{"operationId":"get_config","summary":"Get a config revision","description":"A device's config backup as text: the newest revision, or a particular one if you pass revision (from list_config_changes). To see what changed between two revisions, diff_configs is quicker than fetching both. Backups can contain passwords and keys, so this needs the network_viewconfigs permission. A backup is only stored when the config changes, so a revision's time is when the config changed. last_collected_at is when the config was last read, changed or not: if it's recent, an old newest revision just means a stable config; if it's old, collection is failing, and the device's Task:Connection issue usually says why.","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"Device infrastructure id"},{"name":"revision","in":"path","required":true,"schema":{"type":["integer","string"]},"description":"A revision from list_config_changes (unix seconds; 'YYYY-MM-DD HH:MM:SS' UTC also works)"},{"$ref":"#/components/parameters/isp"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"infid":{"type":"integer"},"revision":{"type":"integer","description":"The revision's id (unix seconds): pass it to get_config or diff_configs"},"changed_at":{"type":"string","format":"date-time"},"format":{"type":"string","description":"The config's syntax: mikrotik (a RouterOS export), ubiquiti or cambium"},"size_bytes":{"type":"integer"},"content_hash":{"type":"string"},"last_collected_at":{"type":["string","null"],"format":"date-time","description":"When the config was last read successfully, whether or not it changed; null if no successful read is recorded"},"config_text":{"type":"string"}},"required":["infid","revision","changed_at","format","size_bytes","content_hash","last_collected_at","config_text"]},"example":{"infid":101,"revision":1791468760,"changed_at":"2026-10-08T14:12:40Z","format":"mikrotik","size_bytes":18214,"content_hash":"3f1c2a9e6b0d47c8a5e2f19b7d6c4a0e8b5f3d21","last_collected_at":"2026-10-08T22:00:12Z","config_text":"# 2026-10-08 14:12:40 by RouterOS 7.20.1\n/interface bridge\nadd name=bridge-lan\n/ip address\nadd address=10.10.0.1/24 interface=bridge-lan\n…"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/configs/diff":{"get":{"operationId":"diff_configs","summary":"Diff two config revisions","description":"What changed between two config revisions of a device, as a unified diff: from is the older revision and to the newer one, both from list_config_changes. It's a good next step when list_config_changes or events_around shows a change near an incident. Like get_config, it needs the network_viewconfigs permission.","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"Device infrastructure id"},{"$ref":"#/components/parameters/isp"},{"name":"from","in":"query","required":true,"schema":{"type":["integer","string"]},"description":"The older revision, from list_config_changes"},{"name":"to","in":"query","required":true,"schema":{"type":["integer","string"]},"description":"The newer revision"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"infid":{"type":"integer"},"from":{"type":"object","properties":{"revision":{"type":"integer"},"changed_at":{"type":"string","format":"date-time"}},"required":["revision","changed_at"],"description":"The older revision"},"to":{"type":"object","properties":{"revision":{"type":"integer"},"changed_at":{"type":"string","format":"date-time"}},"required":["revision","changed_at"],"description":"The newer revision"},"lines_added":{"type":"integer"},"lines_removed":{"type":"integer"},"identical":{"type":"boolean"},"diff":{"type":"string","description":"Unified diff: lines starting - are only in from, + only in to, and a space are unchanged context"}},"required":["infid","from","to","lines_added","lines_removed","identical","diff"]},"example":{"infid":101,"from":{"revision":1789981391,"changed_at":"2026-09-21T09:03:11Z"},"to":{"revision":1791468760,"changed_at":"2026-10-08T14:12:40Z"},"lines_added":1,"lines_removed":1,"identical":false,"diff":"@@ old line ~212, new line ~212 @@\n /ip service\n-set ssh address=10.0.0.0/8\n+set ssh address=10.0.0.0/8,10.99.0.0/24\n set telnet disabled=yes\n"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/configs/latest":{"get":{"operationId":"get_config_latest","summary":"Get the latest config","description":"A device's config backup as text: the newest revision, or a particular one if you pass revision (from list_config_changes). To see what changed between two revisions, diff_configs is quicker than fetching both. Backups can contain passwords and keys, so this needs the network_viewconfigs permission. A backup is only stored when the config changes, so a revision's time is when the config changed. last_collected_at is when the config was last read, changed or not: if it's recent, an old newest revision just means a stable config; if it's old, collection is failing, and the device's Task:Connection issue usually says why.","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"Device infrastructure id"},{"$ref":"#/components/parameters/isp"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"infid":{"type":"integer"},"revision":{"type":"integer","description":"The revision's id (unix seconds): pass it to get_config or diff_configs"},"changed_at":{"type":"string","format":"date-time"},"format":{"type":"string","description":"The config's syntax: mikrotik (a RouterOS export), ubiquiti or cambium"},"size_bytes":{"type":"integer"},"content_hash":{"type":"string"},"last_collected_at":{"type":["string","null"],"format":"date-time","description":"When the config was last read successfully, whether or not it changed; null if no successful read is recorded"},"config_text":{"type":"string"}},"required":["infid","revision","changed_at","format","size_bytes","content_hash","last_collected_at","config_text"]},"example":{"infid":101,"revision":1791468760,"changed_at":"2026-10-08T14:12:40Z","format":"mikrotik","size_bytes":18214,"content_hash":"3f1c2a9e6b0d47c8a5e2f19b7d6c4a0e8b5f3d21","last_collected_at":"2026-10-08T22:00:12Z","config_text":"# 2026-10-08 14:12:40 by RouterOS 7.20.1\n/interface bridge\nadd name=bridge-lan\n/ip address\nadd address=10.10.0.1/24 interface=bridge-lan\n…"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/health":{"get":{"operationId":"get_device_health","summary":"Get a device's health","description":"The live monitored metrics for one device, backhaul or CPE: each metric's latest value and unit, its threshold state and severity, and any open issue. It's the same list as get_device's health, for backhauls and CPEs too. For devices, entries with has_history have history in get_device_history (series = the metric name). An issue's status is active (alerting now), pending (alerting, but still inside its grace period, so not alert-worthy yet) or recovering (back to good, but held open a while in case it comes back: recovered_at is when it went good, and resolves_at is when it clears if it stays good).","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"infid / bhid / cpeid"},{"$ref":"#/components/parameters/isp"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"id":{"type":"integer"},"metrics":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string"},"sub":{"type":"string","description":"Which value of a metric with several on one entity (a radio chain, a vulnerability)"},"description":{"type":"string"},"value":{"type":["number","null"],"description":"Latest collected value"},"unit":{"type":"string"},"state":{"type":"string","description":"Threshold state name for the value, e.g. Good, Warning, Down; Inactive = the metric has no thresholds set"},"severity":{"type":["string","null"],"enum":["ok","info","warning","outage","critical",null],"description":"ok, info, warning, outage or critical for the state's level; null when the metric has no thresholds"},"value_ttl_seconds":{"type":["integer","null"],"description":"Seconds until the value is considered stale; -1 = no expiry set"},"issue":{"type":["object","null"],"properties":{"details":{"type":"string"},"severity":{"type":"string","enum":["ok","info","warning","outage","critical"],"description":"ok while recovering, otherwise info, warning, outage or critical"},"since":{"type":["string","null"],"format":"date-time","description":"When the issue started"},"status":{"type":"string","enum":["active","pending","recovering"]},"recovered_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the metric went back to good"},"resolves_at":{"type":["string","null"],"format":"date-time","description":"recovering only: when the issue clears if it stays good"}},"required":["details","severity","since","status","recovered_at","resolves_at"]},"has_history":{"type":"boolean","description":"Devices: true when the metric's history is available (REST: /devices/{infid}/history/{metric})"}},"required":["metric","value","state","severity","value_ttl_seconds","issue"]}}},"required":["type","id","metrics"]},"example":{"type":"device","id":230,"metrics":[{"metric":"Health:CPU Usage","description":"CPU percentage used as reported by the device","value":23,"unit":"%","state":"Good","severity":"ok","value_ttl_seconds":604790,"issue":null,"has_history":true},{"metric":"Health:Temperature","description":"Device temperature is outside threshold","value":71,"unit":"deg C","state":"Warning","severity":"warning","value_ttl_seconds":604790,"issue":{"details":"Warning - 71 deg C","severity":"warning","since":"2026-10-08T21:40:12Z","status":"active","recovered_at":null,"resolves_at":null},"has_history":true},{"metric":"Wireless:Signal","sub":"Chain 0","description":"Receive Signal Strength (RSSI) in dBm","value":-58,"unit":"dBm","state":"Good","severity":"ok","value_ttl_seconds":604790,"issue":null,"has_history":false},{"metric":"Ping:Quality","description":"A weighted quality rating based on packet loss and latency. 1 = Down/Black … 6 = Bright Green.","value":6,"unit":"","state":"Good","severity":"ok","value_ttl_seconds":604800,"issue":null,"has_history":false}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/history/{series}":{"get":{"operationId":"get_device_history","summary":"Get a device's history","description":"History for one device, backhaul or CPE, rolled up into time buckets (up to 200, or max_points up to 500), so long ranges stay small. Series: devices have pings, quality, signal, traffic (with interface), or a health metric name from get_device whose has_history is true; backhauls have pings, quality, traffic or capacity; CPEs have pings, traffic, plan, quality or signal. Each point has its bucket's start time (at), the values for the series and how many readings went into it (samples). pings: loss_avg_pct, loss_max_pct, latency_avg_ms, latency_min_ms, latency_max_ms. traffic, capacity and plan: avg and max Mbps per direction (an interface's in/out, a backhaul's ap_to_su/su_to_ap, a CPE's down/up). quality: quality_avg, quality_min (0 down to 6 perfect). signal: one point per radio chain per bucket. Health metrics: value_avg, value_min, value_max in unit. A missing bucket means nothing was recorded then.","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"The infid, bhid or cpeid"},{"name":"series","in":"path","required":true,"schema":{"type":"string"},"description":"Which history; see the operation's description"},{"$ref":"#/components/parameters/isp"},{"name":"period","in":"query","required":false,"schema":{"type":"string","enum":["1hour","4hours","day","week","month","year"]},"description":"Lookback from now (default day). Use from/to instead for a past window"},{"name":"from","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range start: unix seconds or 'YYYY-MM-DD HH:MM:SS' UTC"},{"name":"to","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range end (default: now)"},{"name":"max_points","in":"query","required":false,"schema":{"type":"integer"},"description":"Point budget (default 200, max 500); buckets are sized to stay under it"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"id":{"type":"integer"},"series":{"type":"string"},"interface":{"type":"string","description":"Device traffic: the interface"},"source":{"type":"string","description":"Backhaul capacity: the measurement shown"},"unit":{"type":["string","null"],"description":"Health metrics: the value's unit. Other series carry the unit in their field names"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"bucket_seconds":{"type":"integer"},"point_count":{"type":"integer"},"points":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","format":"date-time","description":"Bucket start"},"chain":{"type":"string","description":"signal only: the radio chain"}},"required":["at"],"additionalProperties":{"type":["number","null"]},"description":"One bucket; the value fields depend on the series (see the operation's description)"}}},"required":["type","id","series","unit","from","to","bucket_seconds","point_count","points"]},"example":{"type":"device","id":230,"series":"Health:Temperature","unit":"deg C","from":"2026-10-08T21:00:00Z","to":"2026-10-08T22:00:00Z","bucket_seconds":1800,"point_count":2,"points":[{"at":"2026-10-08T21:00:00Z","value_avg":68.4,"value_min":67,"value_max":70,"samples":30},{"at":"2026-10-08T21:30:00Z","value_avg":70.6,"value_min":69,"value_max":72,"samples":30}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/devices/{infid}/traffic":{"get":{"operationId":"get_interface_traffic","summary":"Get an interface's traffic","description":"History for one device, backhaul or CPE, rolled up into time buckets (up to 200, or max_points up to 500), so long ranges stay small. Series: devices have pings, quality, signal, traffic (with interface), or a health metric name from get_device whose has_history is true; backhauls have pings, quality, traffic or capacity; CPEs have pings, traffic, plan, quality or signal. Each point has its bucket's start time (at), the values for the series and how many readings went into it (samples). pings: loss_avg_pct, loss_max_pct, latency_avg_ms, latency_min_ms, latency_max_ms. traffic, capacity and plan: avg and max Mbps per direction (an interface's in/out, a backhaul's ap_to_su/su_to_ap, a CPE's down/up). quality: quality_avg, quality_min (0 down to 6 perfect). signal: one point per radio chain per bucket. Health metrics: value_avg, value_min, value_max in unit. A missing bucket means nothing was recorded then.","tags":["Devices"],"x-scope":"read","parameters":[{"name":"infid","in":"path","required":true,"schema":{"type":"integer","minimum":0},"description":"The infid, bhid or cpeid"},{"$ref":"#/components/parameters/isp"},{"name":"interface","in":"query","required":false,"schema":{"type":"string"},"description":"Device traffic only: the interface name, as get_device lists it"},{"name":"period","in":"query","required":false,"schema":{"type":"string","enum":["1hour","4hours","day","week","month","year"]},"description":"Lookback from now (default day). Use from/to instead for a past window"},{"name":"from","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range start: unix seconds or 'YYYY-MM-DD HH:MM:SS' UTC"},{"name":"to","in":"query","required":false,"schema":{"type":["integer","string"]},"description":"Range end (default: now)"},{"name":"max_points","in":"query","required":false,"schema":{"type":"integer"},"description":"Point budget (default 200, max 500); buckets are sized to stay under it"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"id":{"type":"integer"},"series":{"type":"string"},"interface":{"type":"string","description":"Device traffic: the interface"},"source":{"type":"string","description":"Backhaul capacity: the measurement shown"},"unit":{"type":["string","null"],"description":"Health metrics: the value's unit. Other series carry the unit in their field names"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"bucket_seconds":{"type":"integer"},"point_count":{"type":"integer"},"points":{"type":"array","items":{"type":"object","properties":{"at":{"type":"string","format":"date-time","description":"Bucket start"},"chain":{"type":"string","description":"signal only: the radio chain"}},"required":["at"],"additionalProperties":{"type":["number","null"]},"description":"One bucket; the value fields depend on the series (see the operation's description)"}}},"required":["type","id","series","unit","from","to","bucket_seconds","point_count","points"]},"example":{"type":"device","id":101,"series":"traffic","interface":"sfp1-HT","unit":null,"from":"2026-10-08T21:00:00Z","to":"2026-10-08T22:00:00Z","bucket_seconds":1800,"point_count":2,"points":[{"at":"2026-10-08T21:00:00Z","in_avg_mbps":41.2,"in_max_mbps":63.5,"out_avg_mbps":498.1,"out_max_mbps":571.3,"samples":30},{"at":"2026-10-08T21:30:00Z","in_avg_mbps":38.7,"in_max_mbps":55,"out_avg_mbps":526.4,"out_max_mbps":589.9,"samples":30}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/events":{"get":{"operationId":"events_around","summary":"List what changed around a time","description":"Everything that changed around a time: radio channel and width changes (tracked separately from config backups), config changes, issues that started or ended in the window, and issues that started in it and are still open. Repeats of the same issue are folded into one entry with occurrences and total_minutes_in_state. Use it to line an outage, or a step in a graph, up with what caused it. Narrow it down with type, or with metric_q to match issue names (for example Ping or Usage). The window is centred on timestamp and up to 48 hours wide; issue history goes back about 90 days and config history about a year.","tags":["Events"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"timestamp","in":"query","required":true,"schema":{"type":["integer","string"]},"description":"Center of the window: unix seconds or 'YYYY-MM-DD HH:MM:SS' UTC"},{"name":"window_hours","in":"query","required":false,"schema":{"type":"integer"},"description":"Total window width in hours, centered on timestamp (default 4, max 48)"},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["device","backhaul","cpe"]},"description":"Restrict the issue sections to device, backhaul or cpe (config and channel changes are always shown)"},{"name":"metric_q","in":"query","required":false,"schema":{"type":"string"},"description":"Substring filter on issue metric names, e.g. 'Ping' or 'Usage' — use to cut peak-hour usage-warning noise"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"window":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"centered_on":{"type":"string","format":"date-time"}},"required":["from","to","centered_on"]},"channel_changes":{"type":"array","items":{"type":"object","properties":{"device":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The radio"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"radio":{"type":"string"},"center_mhz":{"type":"number"},"width_mhz":{"type":"number"},"duplex":{"type":"string"},"observed_at":{"type":"string","format":"date-time"}},"required":["device","site","radio","center_mhz","width_mhz","duplex","observed_at"]}},"config_changes":{"type":"array","items":{"type":"object","properties":{"device":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The device"},"revision":{"type":"integer"},"changed_at":{"type":"string","format":"date-time"},"format":{"type":"string"},"size_bytes":{"type":"integer"}},"required":["device","revision","changed_at","format","size_bytes"]}},"resolved_issues_overlapping":{"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"entity_id":{"type":"integer","description":"The infid, bhid or cpeid, by type"},"name":{"type":"string"},"metric":{"type":"string"},"severity":{"type":["string","null"],"enum":["info","warning","outage","critical",null],"description":"info, warning, outage or critical; outage and critical would page"},"details":{"type":"string"},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":"string","format":"date-time"}},"required":["type","entity_id","name","metric","severity","details","started_at","ended_at"],"description":"A single occurrence"},{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"entity_id":{"type":"integer","description":"The infid, bhid or cpeid, by type"},"name":{"type":"string"},"metric":{"type":"string"},"severity":{"type":["string","null"],"enum":["info","warning","outage","critical",null],"description":"info, warning, outage or critical; outage and critical would page"},"flapping":{"type":"boolean"},"occurrences":{"type":"integer"},"first_started_at":{"type":"string","format":"date-time"},"last_ended_at":{"type":"string","format":"date-time"},"total_minutes_in_state":{"type":"integer"},"latest_details":{"type":"string"}},"required":["type","entity_id","name","metric","severity","flapping","occurrences","first_started_at","last_ended_at","total_minutes_in_state","latest_details"],"description":"Repeats of one entity + metric, folded"}]}},"active_issues_started_in_window":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"entity_id":{"type":"integer","description":"The infid, bhid or cpeid, by type"},"name":{"type":"string"},"metric":{"type":"string"},"severity":{"type":["string","null"],"enum":["info","warning","outage","critical",null],"description":"info, warning, outage or critical; outage and critical would page"},"details":{"type":"string"},"started_at":{"type":"string","format":"date-time"}},"required":["type","entity_id","name","metric","severity","details","started_at"],"description":"Devices only"}}},"required":["window","channel_changes","config_changes","resolved_issues_overlapping","active_issues_started_in_window"]},"example":{"window":{"from":"2026-10-08T20:00:00Z","to":"2026-10-08T22:00:00Z","centered_on":"2026-10-08T21:00:00Z"},"channel_changes":[{"device":{"infid":230,"name":"Hilltop Sector 1"},"site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"radio":"Wireless","center_mhz":5745,"width_mhz":40,"duplex":"Half","observed_at":"2026-10-08T20:41:06Z"}],"config_changes":[{"device":{"infid":214,"name":"Hilltop Router"},"revision":1791449932,"changed_at":"2026-10-08T20:38:52Z","format":"mikrotik","size_bytes":15880}],"resolved_issues_overlapping":[{"type":"backhaul","entity_id":12,"name":"RB-HT","metric":"Backhaul:Ping Latency","severity":"warning","details":"Warning - 12.8 ms avg latency","started_at":"2026-10-08T20:44:10Z","ended_at":"2026-10-08T20:46:11Z"},{"type":"device","entity_id":230,"name":"Hilltop Sector 1","metric":"Ping:Quality","severity":"outage","flapping":true,"occurrences":4,"first_started_at":"2026-10-08T20:42:30Z","last_ended_at":"2026-10-08T21:05:02Z","total_minutes_in_state":6,"latest_details":"Bad - 20% packet loss"}],"active_issues_started_in_window":[{"type":"device","entity_id":230,"name":"Hilltop Sector 1","metric":"Health:Temperature","severity":"warning","details":"Warning - 71 deg C","started_at":"2026-10-08T21:40:12Z"}]}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/ip/{ip}":{"get":{"operationId":"ip_lookup","summary":"Look up an IP address","description":"Everything we know about an IPv4 address in your network: what our collectors have seen recently (ARP, DHCP leases, CDP/LLDP neighbours, traffic flows and Ubiquiti discovery, the same data as the network map's IP hover, refreshed every few minutes), which device, interface or CPE it belongs to, with ids for get_device and get_cpe, and the network owner for public addresses when we already know it. No cache entry only means nothing has seen the address in the last few minutes, not that it's unused. Hostnames, discovered device names and comments can name subscribers, so they need the pii scope.","tags":["Addresses"],"x-scope":"read","parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"description":"Dotted IPv4 address"},{"$ref":"#/components/parameters/isp"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string"},"is_private_or_reserved":{"type":"boolean"},"cache_entry":{"type":["object","null"],"properties":{"type":{"type":"string","description":"What the address is, as the cache classifies it"},"sources":{"type":"object","properties":{"arp":{"type":"object","properties":{"seen_by":{"type":["object","null"],"properties":{"infid":{"type":["integer","null"]},"name":{"type":["string","null"]},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name"],"description":"The router with it in its ARP table"},"interface":{"type":["string","null"]},"mac":{"type":["string","null"]},"mac_manufacturer":{"type":["string","null"]},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["seen_by","interface","mac","mac_manufacturer","last_seen"],"description":"In a router's ARP table"},"dhcp":{"type":"object","properties":{"lease_on":{"type":["object","null"],"properties":{"infid":{"type":["integer","null"]},"name":{"type":["string","null"]},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name"],"description":"The DHCP server holding the lease"},"interface":{"type":["string","null"]},"hostname":{"type":["string","null"],"description":"null without the pii scope"},"mac":{"type":["string","null"]},"dhcp_server":{"type":["string","null"]},"static_lease":{"type":"boolean"},"comment":{"type":["string","null"],"description":"null without the pii scope"},"lease_time_s":{"type":["number","null"]},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["lease_on","interface","hostname","mac","dhcp_server","static_lease","comment","lease_time_s","last_seen"],"description":"A DHCP lease"},"cdp":{"type":"object","properties":{"found_by":{"type":["object","null"],"properties":{"infid":{"type":["integer","null"]},"name":{"type":["string","null"]},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name"],"description":"The device that saw it as a neighbour"},"interface":{"type":["string","null"]},"identity":{"type":["string","null"],"description":"null without the pii scope"},"platform":{"type":["string","null"]},"version":{"type":["string","null"]},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["found_by","interface","identity","platform","version","last_seen"],"description":"A CDP/LLDP/MNDP neighbour"},"flow":{"type":"object","properties":{"throughput_rx_tx_mbit":{"type":["string","null"]},"packets_rx_tx_per_sec":{"type":["string","null"]},"sessions_rx_tx":{"type":["string","null"]},"as_of":{"type":["string","null"],"format":"date-time"}},"required":["throughput_rx_tx_mbit","packets_rx_tx_per_sec","sessions_rx_tx","as_of"],"description":"Traffic flows (NetFlow)"},"ubnt":{"type":"object","properties":{"name":{"type":["string","null"],"description":"null without the pii scope"},"model":{"type":["string","null"]},"firmware":{"type":["string","null"]},"uptime":{"type":["string","null"]},"ssid":{"type":["string","null"]},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["name","model","firmware","uptime","ssid","last_seen"],"description":"Ubiquiti discovery"},"cpe":{"type":"object","properties":{"ap":{"type":["object","null"],"properties":{"infid":{"type":["integer","null"]},"name":{"type":["string","null"]},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name"],"description":"The CPE's access point"},"cpe_mac":{"type":["string","null"]},"mac_manufacturer":{"type":["string","null"]},"account_name":{"type":["string","null"],"description":"pii scope only"},"cpe_description":{"type":["string","null"],"description":"pii scope only"},"cpe_discovered_name":{"type":["string","null"],"description":"pii scope only"},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["ap","cpe_mac","mac_manufacturer","last_seen"],"description":"The address of a CPE radio itself"},"client":{"type":"object","properties":{"meaning":{"type":"string","description":"What the match means: the CPE itself, or a customer device behind it"},"is_cpe_itself":{"type":["boolean","null"]},"ap":{"type":["object","null"],"properties":{"infid":{"type":["integer","null"]},"name":{"type":["string","null"]},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name"],"description":"The CPE's access point"},"cpe_mac":{"type":["string","null"]},"device_mac":{"type":["string","null"]},"device_mac_manufacturer":{"type":["string","null"]},"account_name":{"type":["string","null"],"description":"pii scope only"},"cpe_description":{"type":["string","null"],"description":"pii scope only"},"cpe_discovered_name":{"type":["string","null"],"description":"pii scope only"},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["meaning","is_cpe_itself","ap","cpe_mac","device_mac","device_mac_manufacturer","last_seen"],"description":"An address in a CPE's service package (shaping and disconnect)"},"sn":{"type":"object","properties":{"subnet":{"type":["string","null"]},"gateway_router":{"type":["object","null"],"properties":{"infid":{"type":["integer","null"]},"name":{"type":["string","null"]},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name"],"description":"The router that's the subnet's gateway"},"gateway_ip":{"type":["string","null"]},"routers_on_subnet":{"type":"integer"}},"required":["subnet","gateway_router","gateway_ip"],"description":"The subnet it's in"},"infra":{"type":"object","properties":{"device":{"type":["object","null"],"properties":{"infid":{"type":["integer","null"]},"name":{"type":["string","null"]},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"}},"required":["infid","name"],"description":"The device"},"device_class":{"type":["string","null"]},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"interface":{"type":["string","null"]},"comment":{"type":["string","null"],"description":"null without the pii scope"},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["device","device_class","hardware_type","interface","comment","last_seen"],"description":"An address on one of your devices"}},"additionalProperties":{"type":"object","properties":{"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site"},"details":{"type":"array","items":{"type":"string"},"description":"pii scope only; empty without it"},"last_seen":{"type":["string","null"],"format":"date-time"}},"required":["details","last_seen"]},"description":"What each source saw, by source. A source kind not listed here may appear, with site, details and last_seen."}},"required":["type","sources"]},"devices":{"type":"array","items":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"},"device_class":{"type":"string"},"hardware_type":{"type":["object","null"],"properties":{"id":{"type":"string","description":"e.g. mikrotik, ubiquiti, cambium, snmp (any standard-SNMP device), generic"},"name":{"type":"string","description":"As the app shows it"}},"required":["id","name"],"description":"The kind of device, which decides how Swift Fox collects from it"},"match":{"type":"string"}},"required":["infid","name","device_class","hardware_type","match"],"description":"Devices whose main IP this is"}},"interface_ips":{"type":"array","items":{"type":"object","properties":{"device":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The device"},"interface":{"type":["string","null"]},"match":{"type":"string"}},"required":["device","interface","match"],"description":"Devices with this IP on an interface"}},"cpes":{"type":"array","items":{"type":"object","properties":{"cpeid":{"type":"integer"},"mac":{"type":"string"},"ip":{"type":["string","null"]},"ap":{"type":["object","null"],"properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"The access point it's associated to"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The access point's site"},"account_number":{"type":["string","null"],"description":"The customer-visible account number, as on invoices"},"account_disconnected":{"type":["boolean","null"]},"signal_dbm":{"type":["number","null"]},"plan":{"type":["object","null"],"properties":{"plan_id":{"type":["integer","null"],"description":"Pass as plan to list_cpes"},"name":{"type":["string","null"]},"down_mbps":{"type":["number","null"]},"up_mbps":{"type":["number","null"]}},"required":["plan_id","name","down_mbps","up_mbps"],"description":"The billing plan and its configured rates"},"effective_rate_mbps":{"type":["object","null"],"properties":{"down":{"type":["number","null"]},"up":{"type":["number","null"]}},"required":["down","up"],"description":"What the shaper applies right now: the plan reduced by congestion multipliers or usage caps"},"monitored":{"type":"boolean"},"last_assoc":{"type":["string","null"],"format":"date-time"},"name":{"type":"string","description":"The subscriber's name with the pii scope; otherwise 'CPE <mac> (account <number>)'"},"account_name":{"type":["string","null"],"description":"pii scope only"},"description":{"type":["string","null"],"description":"pii scope only"},"discovered_name":{"type":["string","null"],"description":"pii scope only: the name the radio reports"}},"required":["cpeid","mac","ip","ap","site","account_number","account_disconnected","signal_dbm","plan","effective_rate_mbps","monitored","last_assoc","name"]}},"external":{"type":["object","null"],"properties":{"asn":{"type":["string","null"]},"owner":{"type":["string","null"]},"city":{"type":["string","null"]},"region":{"type":["string","null"]}},"required":["asn","owner","city","region"],"description":"Public IPs only"}},"required":["ip","is_private_or_reserved","cache_entry","devices","interface_ips","cpes","external"]},"example":{"ip":"10.10.7.21","is_private_or_reserved":true,"cache_entry":{"type":"infra","sources":{"arp":{"seen_by":{"infid":214,"name":"Hilltop Router","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"}},"interface":"bridge-sectors","mac":"00:00:5E:00:53:21","mac_manufacturer":"Cambium Networks Limited","last_seen":"2026-10-08T22:13:05Z"},"infra":{"device":{"infid":230,"name":"Hilltop Sector 1","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"}},"device_class":"AP","hardware_type":{"id":"cambium","name":"Cambium"},"interface":"br0","comment":null,"last_seen":"2026-10-08T22:13:05Z"}}},"devices":[{"infid":230,"name":"Hilltop Sector 1","device_class":"AP","hardware_type":{"id":"cambium","name":"Cambium"},"match":"device main IP"}],"interface_ips":[{"device":{"infid":230,"name":"Hilltop Sector 1"},"interface":"br0","match":"discovered interface IP"}],"cpes":[],"external":null}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/isps":{"get":{"operationId":"list_isps","summary":"List ISPs","description":"The ISPs your key can access. Every other operation takes one of these ISP codes to say which ISP it works on, and you can leave it out when your key reaches only one.","tags":["ISPs"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"isps":{"type":"array","items":{"type":"object","properties":{"ispcode":{"type":"string","description":"Pass as ispcode (MCP) or ?isp= (REST)"},"name":{"type":"string"}},"required":["ispcode","name"]}}},"required":["isps"]},"example":{"isps":[{"ispcode":"riverbend","name":"Riverbend Wireless"},{"ispcode":"riverbendnorth","name":"Riverbend North"}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/issues":{"get":{"operationId":"list_issue_events","summary":"List issue events","description":"Open and resolved issues as one event log. What's wrong now and what happened since a time, in one feed: one row per issue occurrence, open rows first, then resolved ones. `issue_id` stays the same from the moment an issue opens until it resolves, so a poller can de-duplicate across polls. A paging poller wants `state=all&since=<last poll>`; a report wants `state=resolved&since=<period start>`. Issues hidden by the ISP's ignore rules are left out unless `include_ignored=true`.","tags":["Issues"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"state","in":"query","required":false,"schema":{"type":"string","enum":["open","resolved","all"]},"description":"open (default): active now. resolved: ended at or after since. all: both, open first."},{"name":"since","in":"query","required":false,"schema":{"type":"string"},"description":"Resolved rows: ended at or after this time. ISO 8601 with Z or an offset, \"YYYY-MM-DD HH:MM:SS\" (UTC), or unix seconds. Default: 24 hours ago."},{"name":"type","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of device, backhaul, cpe. Default: all three."},{"name":"metric","in":"query","required":false,"schema":{"type":"string"},"description":"Metric name prefix, e.g. Ping: or Wireless:Signal"},{"name":"region","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Only issues on things in this region (region_id from GET /regions). Backhauls match on any end."},{"name":"min_severity","in":"query","required":false,"schema":{"type":"string","enum":["info","warning","outage","critical"]},"description":"Leave out rows below this severity: info, warning, outage or critical. outage = would page."},{"name":"include_ignored","in":"query","required":false,"schema":{"type":"boolean"},"description":"Also return open issues hidden by the ISP's ignore rules (muted devices, ignored metrics). Default false."},{"name":"flaps","in":"query","required":false,"schema":{"type":"string","enum":["collapse","expand"]},"description":"collapse (default) folds short repeats of one entity + metric in the same clock hour into one row; expand lists every one."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000},"description":"Rows per page. Default 200 for JSON, 1000 for CSV."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page (also in the Link header)"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"isp":{"type":"string"},"state":{"type":"string","enum":["open","resolved","all"]},"resolved_since":{"type":"string","format":"date-time","description":"The since in effect, given or defaulted"},"flaps":{"type":"string","enum":["collapse","expand"]},"count":{"type":"integer"},"issues":{"type":"array","items":{"type":"object","properties":{"issue_id":{"type":"string","description":"Stable per occurrence: the same while the issue is open and once it's resolved. Use it to de-duplicate across polls."},"isp":{"type":"string"},"state":{"type":"string","enum":["open","resolved"]},"type":{"type":"string","description":"device, backhaul or cpe; more types may be added"},"entity_id":{"type":"integer","description":"The infid, bhid or cpeid, by type"},"name":{"type":"string","description":"Device or backhaul name. CPEs: the subscriber's name with the pii scope, otherwise 'CPE <mac> (account <number>)'"},"ip":{"type":["string","null"],"description":"Backhauls: the AP end's IP"},"site":{"type":["object","null"],"properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"The ISP's short site code"},"name":{"type":"string"}},"required":["site_id","code","name"],"description":"The site. Backhauls: the AP end's site; CPEs: their access point's"},"region":{"type":["object","null"],"properties":{"region_id":{"type":"integer"},"name":{"type":"string"}},"required":["region_id","name"],"description":"The site's region"},"metric":{"type":"string","description":"e.g. Ping:Down, Backhaul:Ping Loss, Wireless:Signal"},"severity":{"type":["string","null"],"enum":["info","warning","outage","critical",null],"description":"Peak severity reached: info, warning, outage or critical (outage and critical would page). null = recorded before 2026-10-08"},"details":{"type":"string"},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time","description":"null while open"},"occurrences":{"type":"integer","description":"Short repeats folded into this row (flaps=collapse); 1 otherwise"},"flapping":{"type":"boolean"},"ignored":{"type":["boolean","null"],"description":"Open rows: hidden by an ignore rule (only returned with include_ignored=true). null on resolved rows"}},"required":["issue_id","isp","state","type","entity_id","name","ip","site","region","metric","severity","details","started_at","ended_at","occurrences","flapping","ignored"]},"description":"Open rows first (newest started_at first), then resolved rows (newest ended_at first)"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"}},"required":["isp","state","resolved_since","flaps","count","issues","next_cursor"]},"example":{"isp":"riverbend","state":"all","resolved_since":"2026-10-08T00:00:00Z","flaps":"collapse","count":2,"issues":[{"issue_id":"8c1f2e9a0b7d4c36","isp":"riverbend","state":"open","type":"device","entity_id":230,"name":"Hilltop Sector 1","ip":"10.10.7.21","site":{"site_id":7,"code":"HT","name":"Hilltop Tower"},"region":{"region_id":2,"name":"North"},"metric":"Health:Temperature","severity":"warning","details":"Warning - 71 deg C","started_at":"2026-10-08T21:40:12Z","ended_at":null,"occurrences":1,"flapping":false,"ignored":false},{"issue_id":"5e0a9d3b71c2f846","isp":"riverbend","state":"resolved","type":"backhaul","entity_id":12,"name":"RB-HT","ip":"10.10.0.1","site":{"site_id":3,"code":"RB-CORE","name":"Riverbend Data Centre"},"region":{"region_id":1,"name":"Riverbend"},"metric":"Backhaul:Ping Latency","severity":"warning","details":"Warning - 12.8 ms avg latency","started_at":"2026-10-08T20:44:10Z","ended_at":"2026-10-08T20:46:11Z","occurrences":1,"flapping":false,"ignored":null}],"next_cursor":null}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/ping":{"post":{"operationId":"ping","summary":"Ping","description":"Ping an IP address, hostname or device (by infid) from a pingbox inside your network. It sends count probes 200 ms apart and returns each probe plus a summary. You often won't need it: ping history for backhauls (almost continuous), devices (about every 10 seconds) and CPEs (about every minute) is already in get_device_history, get_backhaul_history and get_cpe_history (series pings). It's handy for addresses we don't monitor, a particular pingbox's point of view, or a quick burst. A device is pinged from the pingbox that monitors it and anything else from your network default, unless you pass a pingboxid from list_pingboxes. Hostnames are looked up in public DNS.\n\nNeeds the `actions` scope.","tags":["Live tests"],"x-scope":"actions","parameters":[{"$ref":"#/components/parameters/isp"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":["string","integer"],"description":"Dotted IPv4, hostname, or device infid"},"count":{"type":"integer","description":"Probes to send (default 10, max 20)"},"pingboxid":{"type":"integer","description":"Pingbox to run from, from list_pingboxes (default: the device's own pingbox for an infid, otherwise your network default)"}},"required":["target"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"object","properties":{"input":{"type":"string"},"ip":{"type":"string"},"name":{"type":["string","null"],"description":"Device name for an infid, the hostname for a hostname"}},"required":["input","ip","name"]},"pingbox":{"type":"object","properties":{"pingboxid":{"type":"integer"},"name":{"type":"string"}},"required":["pingboxid","name"],"description":"The pingbox the test ran from"},"sent":{"type":"integer"},"received":{"type":"integer"},"loss_pct":{"type":"number"},"rtt_ms":{"type":["object","null"],"properties":{"min":{"type":"number"},"avg":{"type":"number"},"max":{"type":"number"}},"required":["min","avg","max"],"description":"Milliseconds; null when nothing answered"},"probes":{"type":"array","items":{"type":"object","properties":{"rtt_ms":{"type":["number","null"]},"ttl":{"type":["integer","null"]},"error":{"type":["string","null"]}},"required":["rtt_ms","ttl","error"]}}},"required":["target","pingbox","sent","received","loss_pct","rtt_ms","probes"]},"example":{"target":{"input":"230","ip":"10.10.7.21","name":"Hilltop Sector 1"},"pingbox":{"pingboxid":1,"name":"Hilltop"},"sent":3,"received":3,"loss_pct":0,"rtt_ms":{"min":2.31,"avg":2.6,"max":3.02},"probes":[{"rtt_ms":2.31,"ttl":62,"error":null},{"rtt_ms":2.47,"ttl":62,"error":null},{"rtt_ms":3.02,"ttl":62,"error":null}]}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/pingboxes":{"get":{"operationId":"list_pingboxes","summary":"List pingboxes","description":"Your pingboxes: the vantage points inside your network that ping your devices and run live tests. Each device is pinged from one pingbox (get_device shows which, and list_devices can filter by it), and the network default covers devices with no other assignment. A pingbox's hardware is the box it runs on. status is online, offline, or no_hardware (nothing is running it, so its devices aren't being pinged); more states may be added.","tags":["Pingboxes"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max rows (default 200, max 500)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"pingboxes":{"type":"array","items":{"type":"object","properties":{"pingboxid":{"type":"integer","description":"Pass as pingboxid to ping and traceroute, or as pingbox to list_devices"},"name":{"type":"string"},"is_default":{"type":"boolean","description":"The network default: devices with no other assignment are pinged from it"},"status":{"type":"string","description":"online, offline or no_hardware; more states may be added"},"hardware":{"type":["object","null"],"properties":{"kind":{"type":"string","description":"cloud, vm, on_prem, pathbox, container or unknown; more kinds may be added"}},"required":["kind"],"description":"The box this pingbox runs on; null when it has none"},"device_count":{"type":"integer","description":"Infrastructure devices pinged from it. CPEs are pinged from their AP's pingbox"}},"required":["pingboxid","name","is_default","status","hardware","device_count"]}}},"required":["count","next_cursor","pingboxes"]},"example":{"count":3,"next_cursor":null,"pingboxes":[{"pingboxid":0,"name":"Riverbend Cloud","is_default":true,"status":"online","hardware":{"kind":"cloud"},"device_count":41},{"pingboxid":1,"name":"Hilltop","is_default":false,"status":"online","hardware":{"kind":"on_prem"},"device_count":18},{"pingboxid":2,"name":"Mill Valley","is_default":false,"status":"no_hardware","hardware":null,"device_count":0}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/regions":{"get":{"operationId":"list_regions","summary":"List regions","description":"Your regions: the groups your sites belong to. Pass a region_id as region to list_sites, list_devices, list_backhauls, list_cpes or the issues feed to narrow them to one region.","tags":["Sites"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max rows (default 500, max 1000)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"regions":{"type":"array","items":{"type":"object","properties":{"region_id":{"type":"integer"},"name":{"type":"string"},"site_count":{"type":"integer","description":"Sites in the region"}},"required":["region_id","name","site_count"]}}},"required":["count","next_cursor","regions"]},"example":{"count":3,"next_cursor":null,"regions":[{"region_id":2,"name":"North","site_count":4},{"region_id":1,"name":"Riverbend","site_count":2},{"region_id":3,"name":"South","site_count":3}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/sites":{"get":{"operationId":"list_sites","summary":"List sites","description":"Your sites (towers and other locations): each one's code, name, region, whether it's enabled, and how many infrastructure devices it has. Filter by region with a region_id from list_regions. To see a site's devices, pass its id or code to list_devices.","tags":["Sites"],"x-scope":"read","parameters":[{"$ref":"#/components/parameters/isp"},{"name":"region","in":"query","required":false,"schema":{"type":"integer"},"description":"Restrict to one region: region_id from list_regions"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Max rows (default 500, max 1000)"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page"},{"$ref":"#/components/parameters/format"}],"responses":{"200":{"description":"OK","headers":{"Link":{"description":"The next page: <?…&cursor=…>; rel=\"next\". Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to get the next page; null on the last page"},"sites":{"type":"array","items":{"type":"object","properties":{"site_id":{"type":"integer"},"code":{"type":"string","description":"Short site code, as typed by the ISP"},"name":{"type":"string"},"region":{"type":["object","null"],"properties":{"region_id":{"type":"integer"},"name":{"type":"string"}},"required":["region_id","name"],"description":"The site's region"},"enabled":{"type":"boolean"},"device_count":{"type":"integer","description":"Infrastructure devices at the site"}},"required":["site_id","code","name","region","enabled","device_count"]}}},"required":["count","next_cursor","sites"]},"example":{"count":3,"next_cursor":null,"sites":[{"site_id":3,"code":"RB-CORE","name":"Riverbend Data Centre","region":{"region_id":1,"name":"Riverbend"},"enabled":true,"device_count":6},{"site_id":7,"code":"HT","name":"Hilltop Tower","region":{"region_id":2,"name":"North"},"enabled":true,"device_count":9},{"site_id":9,"code":"MV","name":"Mill Valley Tower","region":{"region_id":3,"name":"South"},"enabled":true,"device_count":7}]}},"text/csv":{"schema":{"type":"string"},"example":"header,row\r\n…"}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}},"/traceroute":{"post":{"operationId":"traceroute","summary":"Traceroute","description":"A traceroute from a pingbox inside your network to an IP address, hostname or device (by infid). It runs a few sweeps and merges them per hop with loss and latency, like MTR. Hops are labelled with your device names and infids, the backhaul crossed between two known devices (backhaul_from_prev, for get_backhaul), or the network owner for public hops when we already know it. Empty first sweeps are retried automatically (see rounds_completed). A device is traced from the pingbox that monitors it and anything else from your network default, unless you pass a pingboxid from list_pingboxes. Hostnames are looked up in public DNS.\n\nNeeds the `actions` scope.","tags":["Live tests"],"x-scope":"actions","parameters":[{"$ref":"#/components/parameters/isp"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":["string","integer"],"description":"Dotted IPv4, hostname, or device infid"},"rounds":{"type":"integer","description":"TTL sweeps to run (default 3, max 5)"},"pingboxid":{"type":"integer","description":"Pingbox to run from, from list_pingboxes (default: the device's own pingbox for an infid, otherwise your network default)"}},"required":["target"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"object","properties":{"input":{"type":"string"},"ip":{"type":"string"},"name":{"type":["string","null"],"description":"Device name for an infid, the hostname for a hostname"}},"required":["input","ip","name"]},"pingbox":{"type":"object","properties":{"pingboxid":{"type":"integer"},"name":{"type":"string"}},"required":["pingboxid","name"],"description":"The pingbox the test ran from"},"rounds_requested":{"type":"integer"},"rounds_completed":{"type":"integer"},"sweeps_attempted":{"type":"integer"},"reached":{"type":"boolean"},"warning":{"type":"string"},"hops":{"type":"array","items":{"type":"object","properties":{"ttl":{"type":"integer"},"ip":{"type":["string","null"],"description":"null = no reply at this hop"},"device":{"type":"object","properties":{"infid":{"type":"integer"},"name":{"type":"string"}},"required":["infid","name"],"description":"A Swift Fox device with this IP"},"asn":{"type":"string","description":"Public hops"},"owner":{"type":"string","description":"Public hops"},"loss_pct":{"type":"number"},"rtt_ms":{"type":["object","null"],"properties":{"min":{"type":"number"},"avg":{"type":"number"},"max":{"type":"number"}},"required":["min","avg","max"],"description":"Milliseconds; null when nothing answered"},"backhaul_from_prev":{"type":"object","properties":{"bhid":{"type":"integer"},"name":{"type":"string"}},"required":["bhid","name"],"description":"The backhaul crossed from the previous hop"}},"required":["ttl","ip","loss_pct","rtt_ms"]}}},"required":["target","pingbox","rounds_requested","rounds_completed","sweeps_attempted","reached","hops"]},"example":{"target":{"input":"230","ip":"10.10.7.21","name":"Hilltop Sector 1"},"pingbox":{"pingboxid":0,"name":"Riverbend Cloud"},"rounds_requested":3,"rounds_completed":3,"sweeps_attempted":3,"reached":true,"hops":[{"ttl":1,"ip":"10.10.0.1","device":{"infid":101,"name":"Core Router"},"loss_pct":0,"rtt_ms":{"min":0.31,"avg":0.35,"max":0.41}},{"ttl":2,"ip":"10.10.7.1","device":{"infid":214,"name":"Hilltop Router"},"loss_pct":0,"rtt_ms":{"min":1.02,"avg":1.14,"max":1.38},"backhaul_from_prev":{"bhid":12,"name":"RB-HT"}},{"ttl":3,"ip":"10.10.7.21","device":{"infid":230,"name":"Hilltop Sector 1"},"loss_pct":0,"rtt_ms":{"min":2.29,"avg":2.55,"max":2.98}}]}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"406":{"$ref":"#/components/responses/406"},"429":{"$ref":"#/components/responses/429"},"500":{"$ref":"#/components/responses/500"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"sfk_…","description":"An API key in the Authorization header. Never in the URL."}},"parameters":{"isp":{"name":"isp","in":"query","required":false,"schema":{"type":"string"},"description":"ISP code from GET /isps. Optional when the key reaches exactly one ISP."},"format":{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["json","csv"]},"description":"json (default) or csv. Accept: text/csv works too."}},"schemas":{"Problem":{"type":"object","description":"RFC 9457 problem details","properties":{"type":{"type":"string","format":"uri","description":"Stable per kind of error, e.g. https://api.swiftfox.net/problems/rate-limited"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string","description":"What went wrong, written for people"}},"required":["type","title","status","detail"]}},"responses":{"400":{"description":"Invalid request: an unknown or malformed parameter, a missing required one, or an API key in the URL","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing, invalid, revoked or expired API key","headers":{"WWW-Authenticate":{"schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key lacks the scope or permission, or can't access that ISP","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No such device, backhaul, CPE or revision","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"406":{"description":"format=csv on an endpoint that doesn't return a list","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limited","headers":{"Retry-After":{"description":"Seconds until you can retry","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"e.g. 120;w=60","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Failed on our side; it has been logged","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}