Core API Endpoints
All endpoints use JSON request/response bodies unless noted. Protected endpoints require the access_token cookie. See Authentication for login details.
http://device-ip:8000/docs when MeshDash is running.System Status
GET /api/status
Returns live system health. Also silently refreshes JWT session if it has less than half its lifetime remaining. No auth required.
GET /api/status
Example response:
{
"api_status": "online",
"connection_status": "Connected",
"is_system_ready": true,
"local_node_info": {
"node_id": "!aabbccdd",
"node_num": 2864434397,
"long_name": "My Node",
"short_name": "NODE",
"hardware_model_string": "TLORA_V2_1_1P6",
"firmware_version": "2.3.14.5678",
"battery_level": 87,
"lora_region": "EU_868",
"lora_hop_limit": 3,
"channel_count": 2,
"last_updated": 1705329000.123
},
"last_error": null,
"public_mode": false
}
GET /api/stats
Session statistics since last startup.
{
"packets_received_session": 1420,
"text_messages_session": 47,
"position_updates_session": 203,
"telemetry_reports_session": 612,
"nodes_seen_session": 8,
"channels_seen_session": 2,
"elapsed_time_session": 86400.0,
"start_time": 1705242600.0
}
GET /api/system/connection_history?limit=60
Returns timestamped connection log entries. Used for the connection uptime graph.
limit (int)60. Records are ordered oldest-first.Example response item:
{"timestamp": 1705329000.0, "value": 0.9, "status": "Connected"}
value: 0.9 = connected, 0.5 = transitioning/reconnecting, 0.1 = disconnected.
Nodes
GET /api/nodes
All nodes currently known to MeshDash (in-memory dict, populated from DB on startup + live packets).
GET /api/nodes
Returns a dict keyed by node ID (!xxxxxxxx):
{
"!aabbccdd": {
"node_id": "!aabbccdd",
"node_num": 2864434397,
"long_name": "Alpha Base",
"short_name": "ALPH",
"hw_model": "TLORA_V2_1_1P6",
"isLocal": true,
"snr": 8.5,
"rssi": -82,
"lastHeard": 1705329000,
"battery_level": 87,
"voltage": 3.95,
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 12,
"user": { "longName": "Alpha Base", "shortName": "ALPH", "macaddr": "..." },
"deviceMetrics": { "batteryLevel": 87, "voltage": 3.95, "channelUtilization": 2.1, "airUtilTx": 0.3 },
"source": "RF",
"source_confidence": 0.92
}
}
GET /api/nodes/{node_id}
Single node by its hex ID. Returns 404 if not found.
GET /api/nodes/!aabbccdd
GET /api/nodes/{node_id}/history/{table_name}
Historical data for a specific node from the database.
table_namepositions | telemetry | packetslimit (int)1000.start (float)end (float)GET /api/nodes/!aabbccdd/history/telemetry?limit=200&start=1705200000
GET /api/nodes/{node_id}/count/{item_type}
Count of records for a node in the database. Useful for analytics and pagination.
item_typemessages_sent | positions | telemetrystart / end (float)GET /api/nodes/!aabbccdd/count/messages_sent
→ {"node_id": "!aabbccdd", "item_type": "messages_sent", "count": 47}
GET /api/local_node/full
Comprehensive info about the local radio, including LoRa config, WiFi SSID, Bluetooth state, and all operational parameters read directly from the device.
{
"node_id": "!aabbccdd",
"node_num": 2864434397,
"hardware_model_string": "TLORA_V2_1_1P6",
"firmware_version": "2.3.14.5678",
"long_name": "Alpha Base",
"short_name": "ALPH",
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 12,
"battery_level": 87,
"voltage": 3.95,
"channel_utilization": 2.1,
"air_util_tx": 0.3,
"uptime_seconds": 86400,
"lora_region": "EU_868",
"lora_hop_limit": 3,
"lora_tx_power": 20,
"lora_tx_enabled": true,
"wifi_ssid": "HomeNetwork",
"bluetooth_enabled": true,
"node_info_broadcast_secs": 900,
"position_broadcast_secs": 300,
"gps_mode": "ENABLED",
"role": "CLIENT",
"region": "EU_868",
"max_channels": 8,
"nodedb_count": 12
}
Returns a stub {"status": "connecting"} when in WebSerial mode and the radio is still initialising.
Packets
GET /api/packets?limit=50
Returns packets from the in-memory RAM buffer (most recent first). Fastest endpoint for the live feed.
limit (int)50. Cannot exceed MAX_PACKETS_MEMORY.GET /api/packets/history?limit=100
Returns packets from the SQLite database. Includes fully decoded payloads, source detection results, and signal data.
limit (int)100.Each packet includes a source field (RF | MQTT | UNKNOWN | LOCAL) and source_confidence (0–1) derived from the packet source detection engine.
Messages
GET /api/messages/history
Returns text message history from the database.
from_id (str)to_id (str)^all for broadcast channel messages.channel (int)to_id is a specific node (not ^all).limit (int)100.# All messages on channel 0
GET /api/messages/history?channel=0&limit=50
# Direct messages to/from a specific node
GET /api/messages/history?from_id=!aabbccdd&to_id=!11223344
Each message includes a status field: SENT, DELIVERED, BROADCAST, or FAILED. Status is updated asynchronously when ACK/NACK routing packets are received.
POST /api/messages — Send a Message (Auth Required)
POST /api/messages
Content-Type: application/json
{
"message": "Hello from the dashboard!",
"destination": "^all",
"channel": 0
}
message (str, required)destination (str)!xxxxxxxx) for direct message, or ^all for broadcast. Default: ^all.channel (int)0.Example response:
{
"status": "broadcast",
"channel": 0,
"packet_id": 3141592653,
"timestamp": 1705329000
}
status will be broadcast for ^all or sent for direct messages. Delivery updates arrive later via the SSE stream as message_status_update events.
503 if is_system_ready is false. Always check /api/status before sending.Channels
GET /api/channels
Returns all configured radio channels read directly from the connected device.
[
{
"index": 0,
"name": "LongFast",
"role": "PRIMARY",
"psk": "AQ==",
"uplink": false,
"downlink": false
},
{
"index": 1,
"name": "MyChannel",
"role": "SECONDARY",
"psk": "base64encodedpsk==",
"uplink": false,
"downlink": false
}
]
PSK is returned as base64. Returns 503 if the radio is not connected, unless in WebSerial mode (returns cached data from local_node_info).
Neighbors
GET /api/neighbors
Returns all neighbor table entries from the database. Neighbours are populated from NEIGHBOR_INFO_APP packets.
[
{
"node_id": "!aabbccdd",
"neighbor_id": "!11223344",
"snr": 7.25,
"last_seen": "2024-01-15 14:30:00"
}
]
Traceroutes
GET /api/traceroutes?limit=50
Returns stored traceroute results from the database.
POST /api/traceroute/run — Run a Traceroute (Auth Required)
Sends a live traceroute request to a target node and waits up to 60 seconds for the response.
POST /api/traceroute/run
Content-Type: application/json
{
"node_id": "!11223344",
"hop_limit": 5
}
Example response:
{
"target": "!11223344",
"origin": "!aabbccdd",
"rssi": -90,
"snr": 5.25,
"hops_used": 2,
"direct_link": false,
"path_to": [
{"from": "!aabbccdd", "to": "!99887766", "snr": 7.5},
{"from": "!99887766", "to": "!11223344", "snr": 4.75}
],
"path_back": [
{"from": "!11223344", "to": "!99887766", "snr": 4.25},
{"from": "!99887766", "to": "!aabbccdd", "snr": 6.0}
],
"timestamp": 1705329000.0
}
Returns 504 if no response is received within 60 seconds.
Waypoints
GET /api/waypoints
Returns all waypoints received from the mesh (from WAYPOINT_APP packets).
[
{
"from_id": "!aabbccdd",
"waypoint_id": 123,
"name": "Camp Alpha",
"latitude": 51.5074,
"longitude": -0.1278,
"description": "Base camp",
"timestamp": 1705329000.0
}
]
Hardware Logs
GET /api/hardware_logs?limit=50
Returns hardware event logs (Admin packet payloads, GPIO events, etc.).
Metrics & Analytics
GET /api/metrics/averages?limit=100
Returns SNR/RSSI averages computed every 5 minutes across all non-local nodes.
{
"most_recent": {
"timestamp": 1705329000.0,
"average_snr": 6.8,
"average_rssi": -88.5,
"node_count": 7
},
"history": [...]
}
GET /api/counts/totals
Database-wide record counts.
{
"total_messages": 1247,
"total_positions": 8934,
"total_telemetry": 23401
}
Geocoding (Reverse)
GET /api/geocode?lat={lat}&lon={lon}
Reverse geocodes a coordinate using Nominatim (OpenStreetMap). Results are cached to disk (lat_lon_to_location.json) to respect the 1 req/s rate limit.
GET /api/geocode?lat=51.5074&lon=-0.1278
{
"cached": false,
"key": "51.5074,-0.1278",
"short": "Palace of Westminster, Westminster",
"full": "Palace of Westminster, ...",
"city": "London",
"county": "Greater London",
"country": "United Kingdom",
"postcode": "SW1A 0AA"
}
Console (Meshtastic CLI Bridge)
POST /api/console — Auth Required
Executes a meshtastic CLI-style command directly against the connected radio. The full command string is parsed and dispatched via the Meshtastic Python SDK. Runs in a thread with a 30-second timeout.
POST /api/console
Content-Type: application/json
{"command": "meshtastic --info"}
Response is plain text. Supported commands include:
| Flag | Description |
|---|---|
--info | Print owner name, node count, firmware |
--nodes | Table of all nodes with SNR and last heard |
--channels | Channel list |
--set section.key value | Set a localConfig field |
--get field | Get a config field |
--set-owner "Long Name" | Set long name |
--set-owner-short "SHRT" | Set short name |
--setlat/--setlon/--setalt | Set fixed GPS position |
--remove-position | Clear fixed position, re-enable GPS |
--ch-index N --ch-add "Name" | Add a channel |
--ch-index N --ch-del | Delete/disable a channel |
--ch-index N --ch-set key val | Set channel setting |
--sendtext "msg" --dest !id | Send a text message |
--traceroute !id | Run a traceroute |
--gpio-wrb PIN STATE --dest !id | Write remote GPIO |
--gpio-rd MASK --dest !id | Read remote GPIO |
--reboot | Reboot the radio |
--set-canned-message "msg1;msg2" | Set canned messages |
Alert
POST /api/alert?msg={message}
Broadcasts a system alert message over the SSE stream to all connected dashboard clients. Does not send to the radio.
POST /api/alert?msg=Power+outage+detected
→ {"status": "Alert broadcasted"}
Monitor (Website Scraper → Mesh)
POST /api/monitor — Auth Required
Fetches a URL, extracts a specific HTML text block, and sends it as a Meshtastic message. SSRF-protected (rejects private/internal IP ranges).
POST /api/monitor
Content-Type: application/json
{
"url": "https://example.com/status",
"block_id": 2,
"prefix": "STATUS:",
"node_id": "!aabbccdd",
"channel": 0
}
Extract (URL Content)
POST /extract — Auth Required
Fetches a URL and returns structured text blocks extracted from the HTML. SSRF-protected.
POST /extract
Content-Type: application/json
{
"url": "https://example.com",
"block_id": null,
"text_only": false
}
With block_id: null, returns up to 50 blocks: {"blocks": [{"text": "...", "id": 0, "tag": "h1"}, ...]}. With a specific block_id, returns just that block.
SSE (Server-Sent Events)
GET /sse
Real-time event stream. Connect once and receive all live updates. Maximum 50 concurrent clients per slot.
On connection, two bootstrap events are emitted immediately:
connection_status— current radio status stringnodes— full node list as JSON array
Ongoing event types:
| Event | Data |
|---|---|
packet | Fully processed packet object |
node_update | Updated node data for a single node |
stats | Session statistics (broadcast every 10 s) |
connection_status | Connection status string |
local_node_info | Full local node info after connection |
message_status_update | {"mesh_packet_id": N, "status": "DELIVERED"} |
traceroute_result | Result of a live traceroute |
sync_status | {"is_syncing": bool, "current": "..."} |
system_update | System announcements (update available, etc.) |
activity | "RX" or "TX" — for LED/activity indicators |
plugin_update | Plugin status change |
error | Error message string |
ping | Keepalive (every 30 s when idle) |
GET /sse/{slot_id}
SSE stream for a specific radio slot in multi-radio mode. See Slot Management.
Node Configuration (Radio Protobuf)
GET /api/node/config — Auth Required
Reads the full device configuration as a structured JSON snapshot by introspecting the radio's protobuf config objects. Returns all fields across localConfig, moduleConfig, and active channels, along with their types and current values.
{
"identity": {"long_name": "Alpha Base", "short_name": "ALPH"},
"localConfig": {
"lora": [
{"path": "localConfig.lora.region", "name": "region", "type": "enum", "value": 5, "options": {"UNSET": 0, "US": 1, "EU_433": 2, "EU_868": 5, ...}},
{"path": "localConfig.lora.hop_limit", "name": "hop_limit", "type": "int", "value": 3},
{"path": "localConfig.lora.tx_power", "name": "tx_power", "type": "int", "value": 20}
],
"position": [...],
"device": [...]
},
"moduleConfig": { ... },
"channels": {
"0": {"index": 0, "role": "1", "fields": [...]}
}
}
POST /api/node/config/save — Auth Required
Applies a list of configuration changes to the radio and optionally triggers a reboot. Changes are dispatched only when the value has actually changed from the current state.
POST /api/node/config/save
Content-Type: application/json
{
"changes": [
{"path": "localConfig.lora.hop_limit", "value": "4"},
{"path": "localConfig.lora.tx_power", "value": "20"},
{"path": "identity.long_name", "value": "New Node Name"},
{"path": "channels.0.name", "value": "LongFast"}
],
"reboot": true
}
changes (list, required){"path": "...", "value": "..."} objects. Use the path values from GET /api/node/config. All values are sent as strings and coerced to the correct protobuf type.reboot (bool)true and changes were written, the radio is rebooted after 1.5 s. Default: true.Response:
{
"status": "success",
"written": ["localConfig.lora", "localConfig.device"],
"errors": [],
"reboot_triggered": true
}
status is "partial" if some changes failed. Always check the errors array.
C2 Activity Log
GET /api/c2/status — Auth Required
Returns the in-memory C2 bridge activity log and statistics.
{
"stats": {
"heartbeats_sent": 240,
"heartbeat_failures": 2,
"proxy_requests_received": 47,
"proxy_responses_sent": 46,
"outbox_messages_forwarded": 3,
"last_contact": 1705329000.0,
"last_error": null
},
"logs": [...]
}