A stateless thread-safe REST API for Meshtastic.
- restmesh
- Description and features
- Quickstart
- REST API
- Integrations
- Contributing
- Responsible usage policy
- Consulting and custom integrations
- License
- Changelog and trusted source
- Git forge mirrors
- Support this project
Send messages on Meshtastic using a standard REST API:
- thread safe via asyncio
- safe because a FIFO queue avoid overwhelming the mesh
- very simple to integrate in other projects: Apprise is already available
- aims to have 100% unit test coverage
- doesn't use a database, it merely acts as a gateway
- no MQTT, WiFI, bluetooth: just plug in the radio via USB, set it as
CLIENT_MUTEand enjoy
A typical use case for restmesh is for system error reporting, for example when local Internet is down. You could set up a script to interface with restmesh like this:
#!/usr/bin/env bash
# See:
# https://www.iana.org/domains/root/servers
#
# It is very improbable that 3 root DNS server go simultaneously offline.
#
ANYCAST_1='198.41.0.4'
ANYCAST_2='192.36.148.17'
ANYCAST_3='202.12.27.33'
(nc -zu -w 2 "${ANYCAST_1}" 53 || nc -zu -w 2 "${ANYCAST_2}" 53 || nc -zu -w 2 "${ANYCAST_3}" 53) 2>/dev/null && anycast_ok='true' || anycast_ok='false'
if [ "${anycast_ok}" = 'false' ]; then
echo 'Internet unreachable, alerting mesh channel'
# Use Meshtastic channel 1 (a non-primary channel).
# Channel IDs are the same reported in the mobile app.
#
# You need to install apprise first via pip or your distro's package
# manager.
apprise -b 'ERROR: Internet unreachable' "json://127.0.0.1:8000/api/v1/integrations/apprise/channels/1/messages"
fipip install restmeshusage: restmesh [-h] [--host HOST] [--port PORT] [--radio-serial-path RADIO_SERIAL_PATH]
restmesh: stateless thread-safe REST API for Meshtastic
options:
-h, --help show this help message and exit
--host HOST Server host listening address (default: 127.0.0.1)
--port PORT Server listening port (default: 8000)
--radio-serial-path RADIO_SERIAL_PATH
Path of the USB serial device radio (default: /dev/ttyUSB0)
restmesh --host 127.0.0.1 --port 8000 --radio-serial-path /dev/ttyUSB0Connect to http://127.0.0.1/docs for the Swagger page to test the endpoints, or use Apprise directly.
restmesh --host 0.0.0.0 --port 8000 --radio-serial-path /dev/ttyUSB0Warning
At the moment no kind of authentication is implemented!
This endpoint documentation is automatically generated from FastAPI OpenAPI's generator and swagger-markdown.
Send a message to a channel
Broadcast a text message to a specific mesh channel.
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| channel_index | path | The channel index (0 to 7) | Yes | integer |
| Required | Schema |
|---|---|
| Yes | application/json: ChannelBroadcastPayload |
| Code | Description | Schema |
|---|---|---|
| 202 | Successful Response | application/json: MeshActionResponse |
| 422 | Validation Error | application/json: HTTPValidationError |
| 429 | Unable to handle more requests because the FIFO queue is full | application/json: QueueErrorResponse |
| 503 | Meshtastic radio problem. Different errors can be returned. | application/json: RadioErrorResponse |
Send Text To Node
Send a DM text to a node via nodeId string or nodeNum integer.
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| node_target | path | Target destination: can be a lowercase hex string NodeId (e.g. !2c3b4f5a) or a numeric NodeNum < 2^32 (e.g. 60). | Yes | string or integer |
| Required | Schema |
|---|---|
| Yes | application/json: NodeDirectPayload |
| Code | Description | Schema |
|---|---|---|
| 202 | Successful Response | application/json: MeshActionResponse |
| 422 | Validation Error | application/json: HTTPValidationError |
| 429 | Unable to handle more requests because the FIFO queue is full | application/json: QueueErrorResponse |
| 503 | Meshtastic radio problem. Different errors can be returned. | application/json: RadioErrorResponse |
Apprise Gateway Adapter Send Text Channel
Adapter gateway.
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| channel_index | path | The channel index (0 to 7) | Yes | integer |
| Required | Schema |
|---|---|
| Yes | application/json: AppriseJsonChannelBroadcastPayload |
| Code | Description | Schema |
|---|---|---|
| 202 | Successful Response | application/json: MeshActionResponse |
| 422 | Validation Error | application/json: HTTPValidationError |
| 429 | Unable to handle more requests because the FIFO queue is full | application/json: QueueErrorResponse |
| 503 | Meshtastic radio problem. Different errors can be returned. | application/json: RadioErrorResponse |
Apprise Gateway Adapter Send Text Node
Adapter gateway.
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| node_target | path | Target destination: can be a lowercase hex string NodeId (e.g. !2c3b4f5a) or a numeric NodeNum < 2^32 (e.g. 60). | Yes | string or integer |
| Required | Schema |
|---|---|
| Yes | application/json: AppriseJsonNodeDirectPayload |
| Code | Description | Schema |
|---|---|---|
| 202 | Successful Response | application/json: MeshActionResponse |
| 422 | Validation Error | application/json: HTTPValidationError |
| 429 | Unable to handle more requests because the FIFO queue is full | application/json: QueueErrorResponse |
| 503 | Meshtastic radio problem. Different errors can be returned. | application/json: RadioErrorResponse |
| Name | Type | Description | Required |
|---|---|---|---|
| version | string | Yes | |
| title | string or null | Unused parameter | No |
| message | string | UTF-8 text limited to the 237 byte Meshtastic MTU (200 here for safety) | Yes |
| type | string, Available values: "info", "warning", "success", "failure" or null |
Unused parameter | No |
| attachment | [ ], Default: |
Unused parameter | No |
| wantAck | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery) |
No |
| portNum | integer, Default: 1 |
Protobuf application port number | No |
| Name | Type | Description | Required |
|---|---|---|---|
| version | string | Yes | |
| title | string or null | Unused parameter | No |
| message | string | UTF-8 text limited to the 237 byte Meshtastic MTU (200 here for safety) | Yes |
| type | string, Available values: "info", "warning", "success", "failure" or null |
Unused parameter | No |
| attachment | [ ], Default: |
Unused parameter | No |
| wantAck | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery) |
No |
| wantResponse | boolean, Default: true |
true if you want the service on the other side to send an application layer response |
No |
| portNum | integer, Default: 1 |
Protobuf application port number | No |
| Name | Type | Description | Required |
|---|---|---|---|
| text | string | UTF-8 text limited to the 237 byte Meshtastic MTU (200 here for safety) | Yes |
| wantAck | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery) |
No |
| portNum | integer, Default: 1 |
Protobuf application port number | No |
| Name | Type | Description | Required |
|---|---|---|---|
| detail | [ ValidationError ] | No |
| Name | Type | Description | Required |
|---|---|---|---|
| status | string | Yes | |
| routing_mode | string, Available values: "broadcast", "direct" |
Enum: "broadcast", "direct" |
Yes |
| packet | MeshPacketDetails | Yes | |
| onResponse_callback_payload | object or null | No | |
| truncated | boolean | No |
| Name | Type | Description | Required |
|---|---|---|---|
| id | integer | Yes | |
| from | integer or string | Yes | |
| to | integer or string | Yes | |
| channel | integer | The channel index (0 to 7) | Yes |
| portnum | integer | Protobuf application port number | Yes |
| text | string | The message sent to the mesh | Yes |
| wantAck | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery) |
No |
| wantResponse | boolean, Default: true |
true if you want the service on the other side to send an application layer response |
No |
| Name | Type | Description | Required |
|---|---|---|---|
| text | string | UTF-8 text limited to the 237 byte Meshtastic MTU (200 here for safety) | Yes |
| wantAck | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery) |
No |
| wantResponse | boolean, Default: true |
true if you want the service on the other side to send an application layer response |
No |
| portNum | integer, Default: 1 |
Protobuf application port number | No |
| Name | Type | Description | Required |
|---|---|---|---|
| detail | string, Default: Unable to handle more requests, queue full. |
No |
| Name | Type | Description | Required |
|---|---|---|---|
| detail | string, Default: Meshtastic radio problem |
No |
| Name | Type | Description | Required |
|---|---|---|---|
| loc | [ string or integer ] | Yes | |
| msg | string | Yes | |
| type | string | Yes | |
| input | No | ||
| ctx | object | No |
restmesh accepts Apprise via the JSON schema. To be able to use it you need to install it first. See also the Repology page to see the available packages for Apprise.
Note
The title parameter is ignored! Write your full text in the body.
Simple example using channel 0:
apprise -b 'My message here' "json://localhost:8000/api/v1/integrations/apprise/channels/0/messages"With parameters:
apprise -b 'Hello world!' "json://localhost:8000/api/v1/integrations/apprise/channels/0/messages?:wantAck=false&:portNum=1"Send a message to the node with hex id !0a1b2c3d. Alternatively you can use
the decimal integer representation of the node id, without prepending the
! character:
apprise -b 'My message here' "json://localhost:8000/api/v1/integrations/apprise/nodes/!0a1b2c3d/messages"
apprise -b 'My message here' "json://localhost:8000/api/v1/integrations/apprise/nodes/169552957/messages"With parameters:
apprise -b 'Hello world!' "json://localhost:8000/api/v1/integrations/apprise/nodes/!0a1b2c3d/messages?:wantResponse=true&wantAck=false&:portNum=1"
apprise -b 'Hello world!' "json://localhost:8000/api/v1/integrations/apprise/nodes/169552957/messages?:wantResponse=true&wantAck=false&:portNum=1"See Contributing.
In some places, such as Europe, the non-ham LoRa band has limited air time use.
Please don't use restmesh for mass spamming, and never broadcast automated
messages on public channels such as MediumFast or LongFast. Setup private
channels instead.
restmesh has a basic message FIFO queue also to mitigate the air time problem.
If you need help or custom endpoints and integrations, I'm available for contract-based freelance consulting and custom Python development:
- Email: solvecomputersciencecollabs+restmesh@gmail.com
- Freelancing: https://blog.franco.net.eu.org/jobs/
Copyright (C) 2026 Franco Masotti
restmesh is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
restmesh is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with restmesh. If not, see http://www.gnu.org/licenses/.
You can check the authenticity of new releases using my public key.
Changelogs, instructions, sources and keys can be found at blog.franco.net.eu.org/software/#restmesh.
| URL | Type |
|---|---|
| https://codeberg.org/frnmst/restmesh | RW |
| https://framagit.org/frnmst/restmesh | RW |
| https://repos.franco.net.eu.org/frnmst/restmesh | RW |
| https://github.com/frnmst/restmesh | RO (push mirror only) |
- Buy Me a Coffee
- Liberapay
- Bitcoin:
bc1qnkflazapw3hjupawj0lm39dh9xt88s7zal5mwu - Monero:
84KHWDTd9hbPyGwikk33Qp5GW7o7zRwPb8kJ6u93zs4sNMpDSnM5ZTWVnUp2cudRYNT6rNqctnMQ9NbUewbj7MzCBUcrQEY - Dogecoin:
DMB5h2GhHiTNW7EcmDnqkYpKs6Da2wK3zP - Vertcoin:
vtc1qd8n3jvkd2vwrr6cpejkd9wavp4ld6xfu9hkhh0