Routes & Interfaces (Read)

All route and interface endpoints support multi-scope querying. In addition to area_id, you can pass pi_id (protocol instance), rd_id (routing domain), or network_id to query across all areas within that scope. The backend resolves the scope to a set of area IDs using the hierarchy. Exactly one scope parameter is required.

List Inter-Area Route Summary

GET /api/v1/routes/inter-area/summary?area_id={areaID}
GET /api/v1/routes/inter-area/summary?pi_id={piID}
GET /api/v1/routes/inter-area/summary?rd_id={rdID}
GET /api/v1/routes/inter-area/summary?network_id={networkID}

Paginated inter-area routes grouped by network prefix. Returns aggregate statistics per prefix for the summary/drill-down UI pattern.

Query parameters (mutually exclusive, one required):

Optional:

Response: 200 OK

{
  "summaries": [
    {
      "network": "10.1.0.0/16",
      "router_count": 3,
      "min_metric": 10,
      "max_metric": 30,
      "area_count": 2
    }
  ],
  "total": 142
}

Error: 400 Bad Request — Missing scope parameter

List Inter-Area Routes

GET /api/v1/routes/inter-area?area_id={areaID}
GET /api/v1/routes/inter-area?pi_id={piID}
GET /api/v1/routes/inter-area?rd_id={rdID}
GET /api/v1/routes/inter-area?network_id={networkID}

List inter-area routes (Type 3 Summary-LSA) for a specific scope. When network is provided, returns paginated per-router detail for that prefix (drill-down mode with display_name enrichment). Without network, returns all routes (legacy behavior for time-travel).

Query parameters (mutually exclusive, one required):

Optional:

Response (with network): 200 OK

{
  "routes": [
    {
      "id": "uuid",
      "network": "10.1.0.0/16",
      "adv_router": "10.0.0.2",
      "device_id": "uuid",
      "display_name": "rtr-core-01",
      "metric": 20,
      "area_id": "uuid",
      "first_seen": "2026-02-17T10:00:00Z",
      "last_seen": "2026-02-18T14:00:00Z"
    }
  ],
  "total": 3
}

Response (without network, legacy): 200 OK — flat JSON array of routes (no pagination envelope).

Error: 400 Bad Request — Missing scope parameter

List External Route Summary

GET /api/v1/routes/external/summary?area_id={areaID}
GET /api/v1/routes/external/summary?pi_id={piID}
GET /api/v1/routes/external/summary?rd_id={rdID}
GET /api/v1/routes/external/summary?network_id={networkID}

Paginated stored external-route advertisements grouped by (network, metric_type, is_nssa, protocol_instance_id). asbr_count counts distinct stored devices within each group, not independent default origins. is_nssa separates Type 5 (E1/E2) and Type 7 (N1/N2). protocol_instance_id and protocol_context identify network, AS, routing domain, protocol, process and address family. Sorting by process sorts the context label; all summary sorts append the full group key as a tie-breaker.

The defaults preset uses the exact server regex ^(0[.]0[.]0[.]0/0|::/0)$. Exact-prefix detail includes all types/processes in the selected scope, with PI identity/context and one row per stored area advertisement. These are report-only joins over existing tables. Both report readers exclude Type 5 in stub, NSSA, totally_stub and totally_nssa areas. Collection and shared routing readers are unchanged; no freshness or completeness claim follows.

Scope parameters (choose one):

Optional:

Response: 200 OK

{
  "data": [
    {
      "network": "0.0.0.0/0",
      "metric_type": 2,
      "is_nssa": false,
      "protocol_instance_id": "uuid",
      "protocol_context": "Example / AS 65001 / global / ospfv2 1 ipv4",
      "asbr_count": 2,
      "min_metric": 10,
      "max_metric": 20,
      "has_nssa": false,
      "has_fwd_addr": false,
      "first_seen": "2026-09-09T12:34:56Z",
      "last_seen": "2026-09-10T09:00:00Z"
    }
  ],
  "total": 1,
  "limit": 100,
  "offset": 0
}

Error: 400 Bad Request — Missing scope parameter

List External Routes

GET /api/v1/routes/external?area_id={areaID}
GET /api/v1/routes/external?pi_id={piID}
GET /api/v1/routes/external?rd_id={rdID}
GET /api/v1/routes/external?network_id={networkID}

Lists stored Type 5/7 advertisements in scope. With network, the match is exact CIDR text, not containment; returns paginated per-area rows, enriched with display_name, area_label, protocol_instance_id and protocol_context. The context includes network/AS/routing domain/protocol/process/AF. Detail includes all types/processes for the prefix in the selected scope. Without network, the live list is paginated; only the historical reader returns the legacy flat array.

Query parameters (mutually exclusive, one required):

Optional:

Response (with network): 200 OK

{
  "data": [
    {
      "id": "uuid",
      "network": "0.0.0.0/0",
      "adv_router": "10.0.0.5",
      "device_id": "uuid",
      "display_name": "rtr-edge-01",
      "metric_type": 2,
      "metric": 1,
      "tag": 0,
      "is_nssa": false,
      "area_id": "uuid",
      "area_label": "0.0.0.0",
      "protocol_instance_id": "uuid",
      "protocol_context": "Example / AS 65001 / global / ospfv2 1 ipv4",
      "first_seen": "2026-02-17T10:00:00Z",
      "last_seen": "2026-02-18T14:00:00Z"
    }
  ],
  "total": 1,
  "limit": 100,
  "offset": 0
}

Response (without network): live responses use the same data/total/limit/offset envelope, without drill-down enrichment. With at, the historical response is a flat JSON array (no pagination envelope); an empty resolved scope returns an empty paginated envelope before either reader runs.

Notes:

Error: 400 Bad Request — Missing scope parameter

List ASBR Entries

GET /api/v1/routes/asbr?area_id={areaID}
GET /api/v1/routes/asbr?pi_id={piID}
GET /api/v1/routes/asbr?rd_id={rdID}
GET /api/v1/routes/asbr?network_id={networkID}

List ASBR reachability entries (Type 4 Summary-LSA) for a specific scope.

Query parameters (mutually exclusive, one required):

Optional:

Response: 200 OK

[
  {
    "id": "uuid",
    "asbr_id": "10.0.0.5",
    "adv_router": "10.0.0.2",
    "device_id": "uuid",
    "metric": 10,
    "area_id": "uuid",
    "first_seen": "2026-02-17T10:00:00Z",
    "last_seen": "2026-02-18T14:00:00Z"
  }
]

Notes:

Error: 400 Bad Request — Missing scope parameter

List Device Interfaces

GET /api/v1/devices/{deviceID}/interfaces

List interfaces for a specific device. Interface data is extracted from Router-LSA P2P and transit link entries.

Response: 200 OK

[
  {
    "id": "uuid",
    "device_id": "uuid",
    "ip_address": "10.0.12.1",
    "mask": "255.255.255.252",
    "peer_id": "10.0.0.2",
    "cost": 10,
    "link_type": "p2p",
    "first_seen": "2026-02-17T10:00:00Z",
    "last_seen": "2026-02-18T14:00:00Z"
  }
]

Notes:

Error: 500 Internal Server Error — Database error

List Device Neighbors

These existing reads are included in the 1.4.4 OpenAPI reference. Ordinary authenticated operator, engineer and admin credentials are supported. Restricted read-only API keys receive 403; documentation inclusion does not change access.

GET /api/v1/devices/{deviceID}/neighbors
GET /api/v1/devices/{deviceID}/neighbors?area_id={areaID}

Reconstruct device neighbors from stored topology links. This is not a live neighbor-table read or a protocol state-machine report. Rows collapse parallel links to one neighbor per area; unresolved endpoints are omitted. The returned area_id is an area label (UUID fallback), and neighbor_id is a router ID. Interface details can be empty. Summary down counts every state other than up.

Path parameters:

Query parameters:

Response: 200 OK

{
  "device_id": "uuid",
  "router_id": "10.0.0.1",
  "hostname": "router1",
  "neighbors": [
    {
      "neighbor_id": "10.0.0.2",
      "device_id": "uuid",
      "hostname": "router2",
      "state": "up",
      "address": "10.1.1.2",
      "local_address": "10.1.1.1",
      "interface": "Gi0/0/1",
      "cost": 10,
      "reverse_cost": 10,
      "link_type": "transit",
      "area_id": "0.0.0.0",
      "is_abr": false,
      "is_asbr": false,
      "first_seen": "2026-02-24T10:00:00Z",
      "last_seen": "2026-02-24T12:00:00Z"
    }
  ],
  "summary": {
    "total": 5,
    "up": 4,
    "down": 1
  }
}

Notes:

Error responses:

Get Device EIGRP

GET /api/v1/devices/{deviceID}/eigrp

A device's EIGRP adjacencies and EIGRP-enabled interfaces, discovered passively via CISCO-EIGRP-MIB (SNMP). All-role read. An unknown device yields empty lists, not an error.

Path parameters:

Response: 200 OK

{
  "neighbors": [
    {
      "id": "uuid",
      "device_id": "uuid",
      "peer_addr": "10.0.12.1",
      "address_family": "ipv4",
      "as_number": 100,
      "vrf_name": "default",
      "local_ifindex": 2,
      "local_if_name": "Et0/1",
      "remote_device_id": null,
      "remote_hostname": "",
      "hold_time": 14,
      "srtt_ms": 1600,
      "rto_ms": 5000,
      "uptime": "01:00:13",
      "source": "snmp",
      "first_seen": "2026-07-20T10:00:00Z",
      "last_seen": "2026-07-20T12:00:00Z"
    }
  ],
  "interfaces": [
    { "if_index": 2, "name": "Et0/1", "hello_interval": 5, "peer_count": 1, "mean_srtt": 1600 }
  ]
}

Notes:

Error responses:

Get Network EIGRP Topology

GET /api/v1/networks/{networkID}/eigrp/topology

Every EIGRP neighbor whose device belongs to the network, for the topology overlay / EIGRP Topology Reports panel. The frontend derives undirected adjacency edges from the (device_id, remote_device_id) pairs (an edge is bidirectional when both endpoints observe each other, a half-adjacency otherwise). All-role read.

Path parameters:

Response: 200 OK

{
  "neighbors": [
    {
      "id": "uuid",
      "device_id": "uuid",
      "remote_device_id": "uuid",
      "peer_addr": "10.0.12.2",
      "address_family": "ipv4",
      "as_number": 100,
      "vrf_name": "default",
      "device_hostname": "r1",
      "remote_hostname": "r2"
    }
  ]
}

Notes:

Error responses:

Get EIGRP Observed Path

GET /api/v1/eigrp/path?source_device_id={deviceID}&dest_ip={address}
GET /api/v1/eigrp/path?source_device_id={deviceID}&dest_ip={address}&at={RFC3339}

Reconstructs the EIGRP forwarding path from a source device toward a destination address by chaining the observed next-hops — not by running SPF. EIGRP's composite metric mixes min(bandwidth) with summed delay, so it is not expressible as one additive edge cost, and its components are not readable over SNMP. The routers have already run DUAL, so Osprey reads the resulting distributed FIB (cEigrpTopoTable) instead of modelling it. All-role read.

Query parameters:

Response: 200 OK

{
  "hops": [
    { "device_id": "uuid-a", "route": { "dest_prefix": "10.0.99.0/24", "next_hop": "10.0.12.2", "fdistance": 128256, "route_origin": "Internal" }, "next_device_id": "uuid-b" },
    { "device_id": "uuid-b", "route": { "dest_prefix": "10.0.99.0/24", "route_origin": "Connected" } }
  ],
  "devices": ["uuid-a", "uuid-b"],
  "complete": true,
  "terminated": "delivered"
}

Without at, the existing live response shape is unchanged. With at, the response also contains eigrp_history and uses a historical route DTO that intentionally has no live row id, source, or first/last/create/update timestamps. Coverage is complete, partial, or unavailable; temporal evidence is sampled, bracketed, carried_forward, or unavailable.

Notes:

Error responses:

SPF tree and EIGRP. GET /api/v1/topology/spf-tree refuses an EIGRP area with 400. EIGRP runs DUAL, not SPF, and its links are stored with cost 0 because the composite metric is a property of a whole path rather than of an edge — rendering a Dijkstra tree over them would produce a tidy all-zero result that looks authoritative and means nothing. Use the observed path endpoint above instead. For the same reason EIGRP areas are excluded from the shared per-area graph loader, so path, RIB and simulation never traverse a zero-cost EIGRP segment (which would otherwise win every cheapest-path comparison).

List Interfaces by Scope

GET /api/v1/interfaces?area_id={areaID}
GET /api/v1/interfaces?pi_id={piID}
GET /api/v1/interfaces?rd_id={rdID}
GET /api/v1/interfaces?network_id={networkID}

List all interfaces for devices in a specific scope. Interface data is extracted from Router-LSA P2P and transit link entries. When OSPFv2 and OSPFv3 interfaces share the same (device_id, if_name, area_label), they are merged into a single entry with both IPv4 and IPv6 addresses.

Query parameters (mutually exclusive, one required):

Optional:

Response: 200 OK

[
  {
    "id": "uuid",
    "device_id": "uuid",
    "ip_address": "10.0.12.1",
    "mask": "255.255.255.252",
    "ip_address_v6": "2001:db8::1",
    "mask_v6": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc",
    "peer_id": "10.0.0.2",
    "cost": 10,
    "link_type": "p2p",
    "first_seen": "2026-02-17T10:00:00Z",
    "last_seen": "2026-02-18T14:00:00Z"
  }
]

Notes:

Error: 400 Bad Request — Missing scope parameter

List Stub Networks

GET /api/v1/stub-networks?area_id={areaID}
GET /api/v1/stub-networks?pi_id={piID}
GET /api/v1/stub-networks?rd_id={rdID}
GET /api/v1/stub-networks?network_id={networkID}

List stub networks (host routes, loopbacks, attached networks with no adjacency) for a specific scope. Supports server-side pagination and search for live mode. Time-travel mode (at param) uses the legacy unpaginated path.

Query parameters (mutually exclusive, one required):

Optional:

Response (paginated, no at): 200 OK

{
  "stubs": [
    {
      "id": "uuid",
      "network": "192.168.1.0/24",
      "device_id": "uuid",
      "display_name": "rtr-core-01",
      "cost": 1,
      "area_id": "uuid",
      "first_seen": "2026-02-17T10:00:00Z",
      "last_seen": "2026-02-18T14:00:00Z"
    }
  ],
  "total": 1204
}

Response (legacy, with at): 200 OK — flat JSON array of stub networks (no display_name, no pagination envelope).

Notes:

Error: 400 Bad Request — Missing scope parameter