restmesh
A stateless thread-safe REST API for Meshtastic.
- restmesh
- Description and features
- Examples
- Quickstart
- Integrations
- Contributing
- Responsible usage policy
- Consulting and custom integrations
- License
- Changelog and trusted source
- Git forge mirrors
- Support this project
- REST API reference
- [POST] /api/v1/channels/{channel_index}/messages
- [POST] /api/v1/nodes/{node_target}/messages
- [POST] /api/v1/integrations/apprise/channels/{channel_index}/messages
- [POST] /api/v1/integrations/apprise/nodes/{node_target}/messages
- Schemas
- AppriseJsonChannelBroadcastPayload Schema
- AppriseJsonNodeDirectPayload Schema
- ChannelBroadcastPayload Schema
- HTTPValidationError Schema
- MeshActionResponse Schema
- MeshPacketDetails Schema
- NodeDirectPayload Schema
- QueueErrorResponse Schema
- RadioErrorResponse Schema
- RegexSubst Schema
- ValidationError Schema
Description and features
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
Examples
Error reporting: is Internet down?
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 how I described in Automatic alerts and news on Meshtastic - part 1
RSS/Atom feeds to mesh: weather warnings
You can also resyndicate RSS/Atom feeds to Meshtastic using a third party program called feed2exec. You can add as many feeds as you like and schedule the feed fetching via some cron. See the Automatic alerts and news on Meshtastic - part 2 post.
Quickstart
One minute setup
-
install pipx
-
install restmesh
pipx install restmesh
-
your user must have access to the modem. For example on Debian you have to add your user to the
dialoutgroupsudo usermod -aG dialout ${USER}
-
run restmesh
restmesh
-
connect to the /docs page using a browser
-
create a Systemd service
[Unit] Requires=network-online.target After=network-online.target [Service] User=meshtastic Group=meshtastic Type=simple ExecStart=/bin/restmesh Restart=on-failure [Install] WantedBy=multi-user.target
[!IMPORTANT] The modem device may be different that the default one,
/dev/ttyUSB0. Check new devices withdmesg. In some cases it might be/dev/ttyACM0instead. Naming depdends from different loaded kernel modules.
CLI help
usage: restmesh [-h] [--version] [--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
--version show program's version number 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)
Running
Defaults
restmesh --host 127.0.0.1 --port 8000 --radio-serial-path /dev/ttyUSB0
Connect to http://127.0.0.1:8000/docs for the Swagger page to test the endpoints, or use Apprise directly.
Globally
restmesh --host 0.0.0.0 --port 8000 --radio-serial-path /dev/ttyUSB0
[!WARNING] At the moment no kind of authentication is implemented!
Use the Core API
Send to channel 0 (the primary channel):
curl -X 'POST' \
'http://127.0.0.1:8000/api/v1/channels/0/messages' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"text": "This is a message for the mesh on channel 0!",
"want_ack": false,
"port_num": 1
}'
Send to node !0a1b2c3d:
curl -X 'POST' \
'http://127.0.0.1:8000/api/v1/nodes/%210a1b2c3d/messages' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"text": "This is a message for node !0a1b2c3d",
"want_ack": false,
"want_response": true,
"port_num": 1
}'
Integrations
Apprise
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.
Channel
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"
Node
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"
Contributing
See Contributing.
Responsible usage policy
Meshtastic
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.
Consulting and custom integrations
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/
License
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/.
Changelog and trusted source
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.
Git forge mirrors
| URL | Type | Notes |
|---|---|---|
| https://github.com/frnmst/restmesh | RW | Official home |
| https://codeberg.org/frnmst/restmesh | RW | Mirror |
| https://framagit.org/frnmst/restmesh | RW | Mirror |
| https://repos.franco.net.eu.org/frnmst/restmesh | RW | Mirror |
Support this project
REST API reference
This endpoint documentation is automatically generated from FastAPI OpenAPI's generator and swagger-markdown.
[POST] /api/v1/channels/{channel_index}/messages
Send a message to a channel
Broadcast a text message to a specific mesh channel.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| channel_index | path | The channel index (0 to 7) | Yes | integer |
Request Body
| Required | Schema |
|---|---|
| Yes | application/json: ChannelBroadcastPayload |
Responses
| 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 |
[POST] /api/v1/nodes/{node_target}/messages
Send Text To Node
Send a DM text to a node via nodeId string or nodeNum integer.
Parameters
| 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 |
Request Body
| Required | Schema |
|---|---|
| Yes | application/json: NodeDirectPayload |
Responses
| 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 |
[POST] /api/v1/integrations/apprise/channels/{channel_index}/messages
Apprise Gateway Adapter Send Text Channel
Adapter gateway.
Parameters
| Name | Located in | Description | Required | Schema |
|---|---|---|---|---|
| channel_index | path | The channel index (0 to 7) | Yes | integer |
Request Body
| Required | Schema |
|---|---|
| Yes | application/json: AppriseJsonChannelBroadcastPayload |
Responses
| 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 |
[POST] /api/v1/integrations/apprise/nodes/{node_target}/messages
Apprise Gateway Adapter Send Text Node
Adapter gateway.
Parameters
| 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 |
Request Body
| Required | Schema |
|---|---|
| Yes | application/json: AppriseJsonNodeDirectPayload |
Responses
| 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 |
Schemas
AppriseJsonChannelBroadcastPayload Schema
| Name | Type | Description | Required |
|---|---|---|---|
| regex_subst | RegexSubst or null | No | |
| version | string | Apprise JSON schema version | Yes |
| title | string or null | Unused parameter | No |
| message | string | UTF-8 text to the mesh. This API limits it to 200 bytes for safety, although the default Meshtastic MTU is 237 bytes | Yes |
| type | string, Available values: "info", "warning", "success", "failure" or null |
Unused parameter | No |
| attachment | [ ], Default: |
Unused parameter | No |
| want_ack | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery). See this also |
No |
| port_num | integer, Default: 1 |
Protobuf application port number | No |
AppriseJsonNodeDirectPayload Schema
| Name | Type | Description | Required |
|---|---|---|---|
| regex_subst | RegexSubst or null | No | |
| version | string | Apprise JSON schema version | Yes |
| title | string or null | Unused parameter | No |
| message | string | UTF-8 text to the mesh. This API limits it to 200 bytes for safety, although the default Meshtastic MTU is 237 bytes | Yes |
| type | string, Available values: "info", "warning", "success", "failure" or null |
Unused parameter | No |
| attachment | [ ], Default: |
Unused parameter | No |
| want_ack | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery). See this also |
No |
| port_num | integer, Default: 1 |
Protobuf application port number | No |
| want_response | boolean, Default: true |
true if you want the service on the other side to send an application layer response. See this also |
No |
ChannelBroadcastPayload Schema
| Name | Type | Description | Required |
|---|---|---|---|
| regex_subst | RegexSubst or null | No | |
| text | string | UTF-8 text to the mesh. This API limits it to 200 bytes for safety, although the default Meshtastic MTU is 237 bytes | Yes |
| want_ack | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery). See this also |
No |
| port_num | integer, Default: 1 |
Protobuf application port number | No |
HTTPValidationError Schema
| Name | Type | Description | Required |
|---|---|---|---|
| detail | [ ValidationError ] | No |
MeshActionResponse Schema
| Name | Type | Description | Required |
|---|---|---|---|
| status | string | Always return "success" | Yes |
| routing_mode | string, Available values: "broadcast", "direct" |
Message routing type Enum: "broadcast", "direct" |
Yes |
| packet | MeshPacketDetails | Packet data from radios | Yes |
| on_response_callback_payload | object or null | No | |
| truncated | boolean | Message was truncated to Meshtastic MTU before being sent | No |
MeshPacketDetails Schema
| 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 |
| port_num | integer | Protobuf application port number | Yes |
| text | string | The message sent to the mesh | Yes |
| want_ack | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery). See this also |
No |
| want_response | boolean, Default: true |
true if you want the service on the other side to send an application layer response. See this also |
No |
NodeDirectPayload Schema
| Name | Type | Description | Required |
|---|---|---|---|
| regex_subst | RegexSubst or null | No | |
| text | string | UTF-8 text to the mesh. This API limits it to 200 bytes for safety, although the default Meshtastic MTU is 237 bytes | Yes |
| want_ack | boolean | true if you want the message sent in a reliable manner (with retries and ack/nak provided for delivery). See this also |
No |
| port_num | integer, Default: 1 |
Protobuf application port number | No |
| want_response | boolean, Default: true |
true if you want the service on the other side to send an application layer response. See this also |
No |
QueueErrorResponse Schema
| Name | Type | Description | Required |
|---|---|---|---|
| detail | string, Default: Unable to handle more requests, queue full. |
No |
RadioErrorResponse Schema
| Name | Type | Description | Required |
|---|---|---|---|
| detail | string, Default: Meshtastic radio problem |
No |
RegexSubst Schema
| Name | Type | Description | Required |
|---|---|---|---|
| pattern | string | A regex pattern to be matched against | No |
| subst | string | What to replace the regex pattern with | No |
ValidationError Schema
| Name | Type | Description | Required |
|---|---|---|---|
| loc | [ string or integer ] | Yes | |
| msg | string | Yes | |
| type | string | Yes | |
| input | No | ||
| ctx | object | No |
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file restmesh-0.4.0.tar.gz.
File metadata
- Download URL: restmesh-0.4.0.tar.gz
- Upload date:
- Size: 34.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bfe31755c6a14fcdd4f6dd9c3db521cd27ea90f2996582233ce6f9dc5e63f5c
|
|
| MD5 |
50856728aa40fbc4593a3715bde7b06e
|
|
| BLAKE2b-256 |
8d659cb878005e35901249e54399afa3633310da9667746d9e1ab42a04cac7cd
|
File details
Details for the file restmesh-0.4.0-py3-none-any.whl.
File metadata
- Download URL: restmesh-0.4.0-py3-none-any.whl
- Upload date:
- Size: 28.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04a5d4665d81cd7923fa409b8571749d6996e83138215e020cc7dfb6029763ba
|
|
| MD5 |
921f2f86ffd944355b1c6f12e2e112c7
|
|
| BLAKE2b-256 |
29f43bd7a8dbe24d0dd432600002eee96339bd7f7a7744988da1b61b0df9cfac
|