Mesh things python package
mesh-things is the Python package for the mesh-things API. It lets you read your Meshtastic meshes, their devices and their readings, and manage them, without writing any HTTP code. It also contains the vocabulary and Meshtastic protocol helpers that the API itself is built on, so your scripts and the server always speak the same language.
Installation
The package works with Python 3.10 or newer. The base install includes the API client. Optional extras add support for pandas, the Parquet archive and the Meshtastic protobufs:
pip install mesh-things # the API client and the shared vocabulary
pip install mesh-things[pandas] # readings and exports as pandas DataFrames
pip install mesh-things[archive] # the Parquet archive, read straight from S3 (adds pyarrow)
pip install mesh-things[protobuf] # the telemetry catalog and portnum names, from the Meshtastic protobufs
pip install mesh-things[codec] # MQTT envelope parsing and channel decryption
Getting started
To connect, you need the address of your instance and, for anything that is not public, an API key. You receive the key when you sign up (see registry_create_owner.py in the examples). You can pass both directly, or set MESH_THINGS_URL and MESH_THINGS_API_KEY once in your environment:
from mesh_things.client import MeshThings
api = MeshThings("https://api.example.org", api_key="...")
me = api.owners.me_get()
print(f"Hello, {me['name']}")
for mesh in api.meshes.list():
print(mesh["id"], mesh["label"])
Every operation of the API is available as a method. Methods are grouped by topic, such as meshes, devices or readings, and named after the operation they call: for example, GET /meshes/{mesh_id}/traceroutes becomes api.meshes.traceroutes_list(mesh_id). Responses are plain Python dictionaries and lists. Calling help() on a method shows the API's own documentation for it, including its arguments.
Reading your data
Most of the time you will want to read data. The calls below cover the most common cases:
# The devices on a mesh, and the latest readings of each one
for device in api.meshes.devices_list(mesh_id):
latest = api.readings.devices_latest_get(device["uuid"])
for reading in latest["readings"]:
print(device["name"], reading["channel"], reading["value"], reading["unit"])
# One channel over the last day, averaged every hour
temperature = api.readings.devices_get(
device_uuid, channel="environment_metrics.temperature", start="-1d", window="1h", agg="mean",
)
# Data completeness, radio traffic and mesh statistics
quality = api.readings.devices_quality_get(device_uuid, start="-7d")
traffic = api.readings.devices_traffic_get(device_uuid, range="7d")
stats = api.readings.meshes_stats_get(mesh_id, range="24h")
# Radio links between the nodes of a mesh
links = api.meshes.links_list(mesh_id, since="7d")
# The measurements of one kind of telemetry, with their units
measurements = api.measurements.list(payload_kind="telemetry:environment_metrics")
A node can be given either as its number or in its Meshtastic form, such as "!19ba4462". Dates and times can be given as date or datetime objects, which are sent in UTC. A public mesh can be read without an API key.
The examples include several short scripts that only read data, all starting with get_. They are a good place to see these calls in context.
Making changes
Calls that create or change something take their content as body. Path parameters are given in order, and query parameters by name:
mesh = api.meshes.create(body={"channel_id": "LongFast", "psk_b64": "..."})
api.meshes.update(mesh["id"], body={"label": "Rooftop mesh"})
The client checks your arguments before sending anything. An unknown or missing argument raises TypeError. A value the API would reject raises ValueError, for example a value outside a list of allowed options, a malformed start, stop or window, or a channel that is not written as <measurement>.<field>.
Helpers
Some tasks take more than one call, so the client takes care of them for you:
from mesh_things.client import terms_fields
# Accept the terms an instance asks for when creating a mesh, if any
terms = api.get_terms("meshes")
mesh = api.meshes.create(body={"channel_id": "LongFast", "psk_b64": "...", **terms_fields(terms)})
# Request an export, wait until it is ready and download it
export = api.readings.meshes_exports_create(mesh["id"], body={"format": "csv", "start": "2026-10-01", "stop": "2026-10-07"})
export = api.wait_for_export(mesh["id"], export["id"], timeout=600)
api.download(export["url"], "readings.zip")
# Download one hour of the Parquet archive
api.download_partition(mesh["id"], "2026-10-24", 13, "13.parquet")
# Go through every traceroute, page after page
for traceroute in api.traceroutes(mesh["id"], node_id="!19ba4462"):
print(traceroute["heard_at"])
Download links are signed on their own, so your API key is never sent with them.
Working with pandas
With the [pandas] extra, you can turn readings, exports and archive rows into DataFrames:
from mesh_things.pandas import channel_units, read_export, to_dataframe
readings = api.readings.devices_get(device_uuid, payload_kind="telemetry:environment_metrics", start="-1d")
df = to_dataframe(readings, units=channel_units(api.measurements.list()))
frames = read_export(api.download(export["url"]))
By default, a DataFrame has one row per time, in UTC, and one column per channel, such as environment_metrics.temperature. If you prefer one row per reading, pass long=True. The DataFrame's attrs keep the context: the mesh, the node, the kind of payload, its origin and, when you pass them, the units. Latest readings and quality results become one row per channel.
The Parquet archive
With the [archive] extra, an operator can read a mesh's whole archive straight from S3, using temporary credentials that the API issues:
from mesh_things import archive
from mesh_things.pandas import archive_frame
rows = archive.open_dataset(api, mesh_id).to_table().to_pandas()
df = archive_frame(rows) # one column per channel, indexed by node and time
Meshtastic messages
With the [protobuf] extra, mesh_things.protobuf turns readings back into Meshtastic protobuf messages. latest_messages() builds one Telemetry message per kind of telemetry from a device's latest readings, series_messages() builds one message per time from raw readings, and payload_message() converts an archived payload of any kind. Positions and node details are stored in the API's own format, so they stay as dictionaries.
Async
AsyncMeshThings offers exactly the same methods as coroutines, for use with asyncio:
from mesh_things.client import AsyncMeshThings
async with AsyncMeshThings("https://api.example.org", api_key="...") as api:
meshes = await api.meshes.list()
Errors
When the API answers with an error, the client raises one of the following exceptions:
| Exception | When |
|---|---|
ValidationFailed |
The request was rejected (400). errors lists the messages for each field. |
Unauthorized |
The API key is missing or not valid (401). |
Forbidden |
You do not have access to this resource (403). |
NotFound |
The resource does not exist, or you cannot see it (404). |
Conflict |
The change clashes with existing data (409). |
Throttled |
You are sending requests too quickly (429). retry_after gives the waiting time in seconds, and retries= makes the client wait and retry for you. |
ExportInProgress |
One of your exports is already running. |
ServerError |
Something went wrong on the server (5xx). |
InsecureTransport |
Your API key, or archive credentials, would travel over plain HTTP to another computer. |
All of them are subclasses of ApiError. wait_for_export also raises ExportFailed when an export cannot be completed, and TimeoutError when it takes longer than you allowed.
Note To protect your credentials, the client only sends an API key over plain
http://to your own machine (localhost,127.0.0.1or::1). For any other address, please usehttps://. If you work on a network you trust, you can allow it withallow_insecure_http=Trueor by settingMESH_THINGS_ALLOW_HTTP=1.
Versions
Each release of mesh-things matches a version of the API, and the API reports its version on every response. If you see a MeshThingsVersionWarning, the API has changed since your release was published. Upgrading mesh-things gives you its current set of methods.
Examples
The examples cover every part of the API, from signing up to reading the archive. They are included in the source distribution, and each one can be run on its own.
Metadata
Release files for mesh-things 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mesh_things-0.1.2.tar.gz | 133.7 kB | With provenance | Details for mesh_things-0.1.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mesh_things-0.1.2-py3-none-any.whl | Python 3 | none | any | Details for mesh_things-0.1.2-py3-none-any.whl |
Total release size: 238.6 kB
Release files / mesh_things-0.1.2.tar.gz
| Download URL | mesh_things-0.1.2.tar.gz |
|---|---|
| Size | 133.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
19fb91980f76d50d731878c0096506efa107caefcb491c943ad73f14b2f80ecd
|
|
BLAKE2b-256 checksum How to use checksums |
08a6f4a16e3124def0d8151c6a053f574d9105aa0fa1cc57e0c095c2ada5f8d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / mesh_things-0.1.2-py3-none-any.whl
| Download URL | mesh_things-0.1.2-py3-none-any.whl |
|---|---|
| Size | 104.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3d288371db185e0fbdf3a37befd3cb9848bdf308f09773eafb74125a38128cd
|
|
BLAKE2b-256 checksum How to use checksums |
937f7fd7ce8f75d233b4f9c66d847dc45fd33bdeb6375d593536fd83a86b469e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logSecurity for mesh-things 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for mesh_things-0.1.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.