Files
meshview/docs/API_Documentation.md
T
George 25ada1023d fix: correct traceroute path orientation when building graph edges (#162)
* fix: correct traceroute path orientation when building graph edges

A completed traceroute is observed as a response packet, whose from/to are
reversed with respect to the path being traced: `to` is the node that started
the traceroute and `from` is the node that answered. Meshview assembled paths
as [from, *route, to], so both endpoint-adjacent edges were wrong for every
completed traceroute with at least one intermediate hop. Because the false
edges always attach to the endpoints, a busy initiator or gateway accumulated
false direct links to every remote target it ever traced.

Confirmed against TraceRouteModule::alterReceivedProtobuf, which renders a
response as printRoute(r, p.to, p.from, false), against the meshtastic python
CLI, which starts the forward path at p["to"] and the return path at p["from"],
and against real recorded traceroutes where a request's route[] is a prefix of
the matching response's route[].

Add meshview/traceroute.py and use it from /api/edges, /api/traceroute and
graph_traceroute, which had three separate copies of this logic that had all
drifted. /api/traceroute also gains initiator/target so the traceroute page
stops re-deriving the endpoints itself, which is where a fourth copy lived.

While in /api/edges, skip the NODENUM_BROADCAST placeholders that
insertUnknownHops() writes into route[] (they became a pseudo-node hub in the
graph) and guard against the (gateway, gateway) self edge that
graph_traceroute already guarded against.

* fix: do not invent links across unknown hops or unfinished returns

Review of becf4a8 found two remaining path errors.

Dropping NODENUM_BROADCAST from route[] during assembly joined the nodes
either side of it, so A -> B -> ? -> C -> D became A -> B -> C -> D and a
lone unknown hop collapsed into a false direct A -> D. Keep the placeholder
in the path and skip the edges that touch it instead. On a 2660 row sample
this removes 10 links that do not exist.

done=True only means a response was observed, not that it arrived. A gateway
usually reports one still travelling home with route_back holding just the
hops so far, so always appending the initiator claimed a final link that was
never seen. Append it only when the response reached the initiator, which the
spare snr_back entry from SNRonly=isToUs() marks. Note this is rare in
practice: 2 of 303 real responses, since the entry is added by the initiator
and gateways see packets in flight.

The deduplicated unique_forward_paths and unique_reverse_paths carry no
per-observation provenance, so the traceroute page can no longer tell which
of them reached the far endpoint. It now adds only the adjacent endpoint.

* fix: treat a response as proof the forward path reached the target

Winning path selection required return hops, so a response observed before
its first return hop contributed nothing and the page fell back to the
deduplicated reported paths, which cannot tell which of them reached the
target. A response with route=[B,C] and no route_back rendered A > B > C,
losing the target and the last link.

Select winning paths from response observations instead. A response only
exists because the request arrived, whatever its return trip went on to do.
Dropping the non-empty forward_hops guard at the same time means a zero hop
response contributes its real direct link, so a fully direct traceroute and a
completed direct return are no longer excluded from the graph.

Mark reported forward paths that some response reported, so the page can
close those on the target and they deduplicate against the winning path
instead of drawing a shorter copy underneath it.
2026-09-08 10:30:27 -07:00

11 KiB

API Documentation

Base URL: http(s)://<host>

All endpoints return JSON. Timestamps are either ISO 8601 strings or *_us values in microseconds since epoch.

1. Nodes API

GET /api/nodes

Returns a list of nodes, with optional filtering.

Query Parameters

  • node_id (optional, int): Exact node ID.
  • role (optional, string): Node role.
  • channel (optional, string): Channel name.
  • hw_model (optional, string): Hardware model.
  • days_active (optional, int): Nodes seen within the last N days.

Response Example

{
  "nodes": [
    {
      "id": 42,
      "node_id": 1234,
      "long_name": "Alice",
      "short_name": "A",
      "hw_model": "T-Beam",
      "firmware": "1.2.3",
      "role": "client",
      "last_lat": 377749000,
      "last_long": -1224194000,
      "channel": "main",
      "last_seen_us": 1736370123456789
    }
  ]
}

2. Packets API

GET /api/packets

Returns packets with optional filters.

Query Parameters

  • packet_id (optional, int): Return exactly one packet (overrides other filters).
  • limit (optional, int): Max packets to return, clamped 1-1000. Default: 50.
  • since (optional, int): Only packets imported after this microsecond timestamp.
  • portnum (optional, int): Filter by port number.
  • contains (optional, string): Payload substring filter.
  • from_node_id (optional, int): Filter by sender node ID.
  • to_node_id (optional, int): Filter by recipient node ID.
  • node_id (optional, int): Legacy filter matching either from or to node ID.

Response Example

{
  "packets": [
    {
      "id": 123,
      "import_time_us": 1736370123456789,
      "channel": "main",
      "from_node_id": 5678,
      "to_node_id": 91011,
      "portnum": 1,
      "long_name": "Alice",
      "payload": "Hello, Bob!",
      "to_long_name": "Bob",
      "reply_id": 122
    }
  ],
  "latest_import_time": 1736370123456789
}

Notes

  • For portnum=1 (text messages), packets are filtered to remove sequence-only payloads.
  • latest_import_time is returned when available for incremental polling (microseconds).

3. Channels API

GET /api/channels

Returns channels seen in a time period.

Query Parameters

  • period_type (optional, string): hour or day. Default: hour.
  • length (optional, int): Number of periods to look back. Default: 24.

Response Example

{
  "channels": ["LongFast", "MediumFast", "ShortFast"]
}

4. Stats API

GET /api/stats

Returns packet statistics aggregated by time periods, with optional filtering.

Query Parameters

  • period_type (optional, string): hour or day. Default: hour.
  • length (optional, int): Number of periods to include. Default: 24.
  • channel (optional, string): Filter by channel (case-insensitive).
  • portnum (optional, int): Filter by port number.
  • to_node (optional, int): Filter by destination node ID.
  • from_node (optional, int): Filter by source node ID.
  • node (optional, int): If provided, return combined sent and seen totals for that node.

Response Example (series)

{
  "period_type": "hour",
  "length": 24,
  "channel": "LongFast",
  "portnum": 1,
  "to_node": 12345678,
  "from_node": 87654321,
  "data": [
    { "period": "2025-08-08 14:00", "count": 10 },
    { "period": "2025-08-08 15:00", "count": 7 }
  ]
}

Response Example (node totals)

{
  "node_id": 12345678,
  "period_type": "hour",
  "length": 24,
  "sent": 42,
  "seen": 58
}

GET /api/stats/count

Returns total packet counts, optionally filtered.

Query Parameters

  • packet_id (optional, int): Filter packet_seen by packet ID.
  • period_type (optional, string): hour or day.
  • length (optional, int): Number of periods to include.
  • channel (optional, string): Filter by channel.
  • from_node (optional, int): Filter by source node ID.
  • to_node (optional, int): Filter by destination node ID.

Response Example

{
  "total_packets": 12345,
  "total_seen": 67890
}

GET /api/stats/top

Returns nodes sorted by packets seen, with pagination.

Query Parameters

  • period_type (optional, string): hour or day. Default: day.
  • length (optional, int): Number of periods to include. Default: 1.
  • channel (optional, string): Filter by channel.
  • limit (optional, int): Max nodes to return. Default: 20, max 100.
  • offset (optional, int): Pagination offset. Default: 0.

Response Example

{
  "total": 250,
  "limit": 20,
  "offset": 0,
  "nodes": [
    {
      "node_id": 1234,
      "long_name": "Alice",
      "short_name": "A",
      "channel": "main",
      "sent": 100,
      "seen": 240,
      "avg": 2.4
    }
  ]
}

5. Edges API

GET /api/edges

Returns network edges (connections between nodes) based on traceroutes and neighbor info. Traceroute edges are collected over the last 12 hours. Neighbor edges are based on port 71 packets.

A completed traceroute is observed as a response packet, whose from/to are reversed with respect to the traced path, so edges are built as to -> route[] -> from. route[] may contain 0xFFFFFFFF placeholders for hops the mesh could not identify; the edges on either side of such a placeholder are omitted, because the nodes around an unknown hop are not neighbours.

Query Parameters

  • type (optional, string): traceroute or neighbor. If omitted, returns both.
  • node_id (optional, int): Filter edges to only those touching a node.

Response Example

{
  "edges": [
    { "from": 12345678, "to": 87654321, "type": "traceroute" },
    { "from": 11111111, "to": 22222222, "type": "neighbor" }
  ]
}

6. Config API

GET /api/config

Returns a safe subset of server configuration.

Response Example

{
  "site": {
    "domain": "example.com",
    "language": "en",
    "title": "Meshview",
    "message": "",
    "starting": "/chat",
    "nodes": "true",
    "chat": "true",
    "everything": "true",
    "graphs": "true",
    "stats": "true",
    "net": "true",
    "map": "true",
    "top": "true",
    "map_top_left_lat": 39.0,
    "map_top_left_lon": -123.0,
    "map_bottom_right_lat": 36.0,
    "map_bottom_right_lon": -121.0,
    "map_interval": 3,
    "firehose_interval": 3,
    "weekly_net_message": "Weekly Mesh check-in message.",
    "net_tag": "#BayMeshNet",
    "version": "3.0.0"
  },
  "mqtt": {
    "server": "mqtt.example.com",
    "topics": ["msh/region/#"]
  },
  "cleanup": {
    "enabled": "false",
    "days_to_keep": "14",
    "hour": "2",
    "minute": "0",
    "vacuum": "false"
  }
}

7. Language API

GET /api/lang

Returns translation strings.

Query Parameters

  • lang (optional, string): Language code (e.g., en, es). Default from config or en.
  • section (optional, string): Return only one section (e.g., nodelist, firehose).

Response Example

{
  "title": "Meshview",
  "search_placeholder": "Search..."
}

8. Packets Seen API

GET /api/packets_seen/{packet_id}

Returns packet_seen entries for a packet.

Path Parameters

  • packet_id (required, int): Packet ID.

Response Example

{
  "seen": [
    {
      "packet_id": 123,
      "node_id": 456,
      "rx_time": "2025-07-22T12:45:00",
      "hop_limit": 7,
      "hop_start": 0,
      "channel": "main",
      "rx_snr": 5.0,
      "rx_rssi": -90,
      "topic": "msh/region/#",
      "import_time_us": 1736370123456789
    }
  ]
}

9. Traceroute API

GET /api/traceroute/{packet_id}

Returns traceroute details and derived paths for a packet.

Path Parameters

  • packet_id (required, int): Packet ID.

A completed traceroute is observed as a response packet, whose from/to are reversed with respect to the traced path. initiator and target resolve this: the forward path runs initiator -> forward_hops -> target and the return path runs target -> reverse_hops -> initiator, in both directions of travel. forward_hops and reverse_hops contain only the intermediate hops, never the endpoints.

A gateway usually reports a response that is still travelling home, so reverse_hops is often incomplete. reverse_complete says whether that observation had reached the initiator; only then does the corresponding winning_paths.reverse entry end at the initiator.

winning_paths is built from response observations, since a response proves the request reached the target. complete on a unique_forward_paths entry says whether some response reported that hop list, and so whether it is known to end at the target.

Response Example

{
  "packet": {
    "id": 123,
    "from": 222,
    "to": 111,
    "initiator": 111,
    "target": 222,
    "channel": "main"
  },
  "traceroute_packets": [
    {
      "index": 0,
      "gateway_node_id": 333,
      "done": true,
      "forward_hops": [444, 555],
      "reverse_hops": [555, 444],
      "reverse_complete": true
    }
  ],
  "unique_forward_paths": [
    { "path": [444, 555], "count": 2, "complete": true }
  ],
  "unique_reverse_paths": [
    [555, 444]
  ],
  "winning_paths": {
    "forward": [[111, 444, 555, 222]],
    "reverse": [[222, 555, 444, 111]]
  }
}

10. Health API

GET /health

Returns service health and database status.

Response Example

{
  "status": "healthy",
  "timestamp": "2025-07-22T12:45:00+00:00",
  "version": "3.0.3",
  "git_revision": "abc1234",
  "cleanup": {
    "enabled": true,
    "days_to_keep": 14,
    "scheduled_time": "02:00",
    "vacuum": false,
    "status": "ok",
    "status_file": "dbcleanup.status.json",
    "last_run": {
      "status": "ok",
      "started_at": "2026-06-04T09:00:00+00:00",
      "completed_at": "2026-06-04T09:00:03+00:00",
      "cutoff_at": "2026-05-21T09:00:00+00:00",
      "days_to_keep": 14,
      "vacuum_requested": false,
      "vacuum_completed": false,
      "rows_deleted": {
        "packet": 1200,
        "packet_seen": 3400,
        "traceroute": 42,
        "node": 3
      },
      "error": null
    }
  },
  "backup": {
    "enabled": true,
    "backup_dir": "./backups",
    "scheduled_time": "02:00",
    "status": "ok",
    "status_file": "dbbackup.status.json",
    "last_run": {
      "status": "ok",
      "started_at": "2026-06-04T09:00:00+00:00",
      "completed_at": "2026-06-04T09:00:02+00:00",
      "backup_dir": "./backups",
      "database_path": "packets.db",
      "backup_file": "backups/packets_backup_20260604_090000.db.gz",
      "original_size_bytes": 12939444,
      "compressed_size_bytes": 4211560,
      "compression_percent": 67.5,
      "error": null
    }
  },
  "database": "connected",
  "database_size": "12.34 MB",
  "database_size_bytes": 12939444
}

11. Version API

GET /version

Returns version metadata.

Response Example

{
  "version": "3.0.3",
  "release_date": "2026-1-15",
  "git_revision": "abc1234",
  "git_revision_short": "abc1234"
}