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.
10 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_timeis 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):hourorday. 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):hourorday. 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 combinedsentandseentotals 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):hourorday.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):hourorday. 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, max100.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. Unknown
hop placeholders (0xFFFFFFFF) are skipped.
Query Parameters
type(optional, string):tracerouteorneighbor. 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 oren.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.
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]
}
],
"unique_forward_paths": [
{ "path": [444, 555], "count": 2 }
],
"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"
}