pymenderio
Python client library for the Mender API.
Table of Contents
- pymenderio
- Table of Contents
- Features
- Installation
- Quick Start
- API Coverage
- Major Functionality Blocks
- Devices (
client.devices) - Deployments (
client.deployments) - Artifacts (
client.artifacts) - Releases (
client.releases) - Inventory (
client.inventory) - Inventory Filters v2 (
client.filters) - User Administration (
client.useradm) - Tenant Administration (
client.tenantadm) - Device Connect (
client.deviceconnect) - Device Configure (
client.deviceconfigure) - Device Monitor (
client.devicemonitor) - IoT Manager (
client.iot)
- Devices (
- Authentication
- Examples
- API Reference
- Error Handling
- Development
- Release
- License
Features
- Type-safe: Full type hints and Pydantic models for all API objects
- Async/Sync: Both synchronous and asynchronous clients
- Transparent pagination: All list operations return complete results
- Clean API: Pythonic interface mirroring the Mender CLI
Installation
pip install pymenderio
Or with Poetry:
poetry add pymenderio
Quick Start
Synchronous Usage
from pymenderio import MenderClient
# Using a token file (default: ~/.cache/mender/authtoken)
with MenderClient.from_token_file() as client:
# List all accepted devices
devices = client.devices.list(status="accepted")
for device in devices:
print(f"{device.id}: {device.identity}")
# Create a deployment
deployment_id = client.deployments.create(
name="Production rollout",
artifact_name="myapp-v2.0",
devices=["device-id-1", "device-id-2"],
)
print(f"Created deployment: {deployment_id}")
# Using a token directly
with MenderClient(server="https://hosted.mender.io", token="...") as client:
releases = client.releases.list()
Asynchronous Usage
import asyncio
from pymenderio import AsyncMenderClient
async def main():
async with AsyncMenderClient.from_token_file() as client:
# List devices
devices = await client.devices.list(status="accepted")
# Get deployment stats
stats = await client.deployments.stats("deployment-id")
print(f"Success: {stats.success}, Pending: {stats.pending}")
asyncio.run(main())
API Coverage
Every API method documented below is available in both variants:
- Sync:
MenderClientmethod call - Async: same method name on
AsyncMenderClient, awaited
Example pattern:
# sync
from pymenderio import MenderClient
with MenderClient.from_mender_cli() as client:
devices = client.devices.list(status="accepted")
# async
from pymenderio import AsyncMenderClient
async with AsyncMenderClient.from_mender_cli() as client:
devices = await client.devices.list(status="accepted")
Function parity reference by API family:
- Devices methods (
list,search,get,count,preauthorize,accept,reject,set_auth_status,get_auth_status,remove_auth_set,decommission,revoke_token,limits,license,auto_auth)- Sync form:
client.devices.<method>(...) - Async form:
await client.devices.<method>(...)
- Sync form:
- Deployments methods (
list,get,create,stats,stats_list,devices,log,try_log,abort,device_history,abort_device)- Sync form:
client.deployments.<method>(...) - Async form:
await client.deployments.<method>(...)
- Sync form:
- Artifacts methods (
list,get,upload,download,download_to_file,delete)- Sync form:
client.artifacts.<method>(...) - Async form:
await client.artifacts.<method>(...)
- Sync form:
- Releases methods (
list,get,delete,update,set_tags,list_tags,list_update_types,list_delta_jobs,get_delta_job)- Sync form:
client.releases.<method>(...) - Async form:
await client.releases.<method>(...)
- Sync form:
- Inventory methods (
list,get,count,get_device_group,set_device_group,clear_device_group)- Sync form:
client.inventory.<method>(...) - Async form:
await client.inventory.<method>(...)
- Sync form:
- Inventory groups methods (
list,devices,add_devices,remove_devices,delete)- Sync form:
client.inventory.groups.<method>(...) - Async form:
await client.inventory.groups.<method>(...)
- Sync form:
- Inventory tags methods (
list,set,delete)- Sync form:
client.inventory.tags.<method>(...) - Async form:
await client.inventory.tags.<method>(...)
- Sync form:
- Inventory filters v2 methods (
attributes,search,list,create,get,update,delete,execute,statistics)- Sync form:
client.filters.<method>(...) - Async form:
await client.filters.<method>(...)
- Sync form:
- User administration methods (
list_users,create_user,user_exists,get_user,update_user,delete_user,me,update_me,enable_2fa,disable_2fa,settings,set_settings,my_settings,set_my_settings,list_personal_access_tokens,create_personal_access_token,revoke_personal_access_token,list_roles,create_role,get_role,update_role,delete_role,list_permission_sets,create_permission_set,get_permission_set,update_permission_set,delete_permission_set)- Sync form:
client.useradm.<method>(...) - Async form:
await client.useradm.<method>(...)
- Sync form:
- Tenant administration methods (
list_tenants,create_tenant,me,delete_inactive_tenant,cancel_tenant,update_child_tenant,update_plan,init_tenant_removal,set_tenant_status,billing_products,billing_info,init_card_update,confirm_card_update,register_billing_profile,billing_profile,update_billing_profile,change_subscription,subscription,preview_invoice,stripe_secret,contact_support)- Sync form:
client.tenantadm.<method>(...) - Async form:
await client.tenantadm.<method>(...)
- Sync form:
- Device connect methods (
get_device,connect,check_update,send_inventory,playback,download,upload)- Sync form:
client.deviceconnect.<method>(...) - Async form:
await client.deviceconnect.<method>(...)
- Sync form:
- Device configure methods (
get,set,deploy)- Sync form:
client.deviceconfigure.<method>(...) - Async form:
await client.deviceconfigure.<method>(...)
- Sync form:
- Device monitor methods (
alerts,latest_alerts,config,set_alert_channel_status)- Sync form:
client.devicemonitor.<method>(...) - Async form:
await client.devicemonitor.<method>(...)
- Sync form:
- IoT manager methods (
list_integrations,register_integration,remove_integration,set_integration_credentials,unregister_device_integrations,device_states,device_state,set_device_state,events)- Sync form:
client.iot.<method>(...) - Async form:
await client.iot.<method>(...)
- Sync form:
Authentication helper parity:
- Sync:
login(...),login_and_save_session(...) - Async:
await async_login(...),await async_login_and_save_session(...)
Major Functionality Blocks
- Device Identity and Lifecycle Management:
- Device listing/search, auth-set lifecycle, preauthorization, token revocation, auto-auth, decommission, and limits/license helpers.
- OTA and Release Operations:
- Deployments lifecycle, stats and logs, release metadata/tag management, artifact operations, and server-side delta generation visibility.
- Inventory and Fleet Querying:
- Inventory list/get/count, groups/tags management, and inventory v2 filters/search/statistics with saved filter workflows.
- Administrative and Tenant Operations:
- User administration (users, PATs, settings, RBAC) and tenant administration (tenant lifecycle, billing/profile/subscription, support).
- Device Service Operations:
- Device Connect, Device Configure, Device Monitor, and IoT Manager families for operational actions beyond OTA.
- Typed Ergonomics and Compatibility:
- Pydantic models for typed payloads/queries, sync+async parity, and compatibility fallbacks for evolving backend query parameters.
Devices (client.devices)
| Method | Description |
|---|---|
list(status=...) |
List all devices |
search(status=..., ids=...) |
Search devices by status and IDs |
get(device_id) |
Get a single device |
count(status=...) |
Count devices |
preauthorize(preauth) |
Submit a preauthorized device identity |
accept(device_id, auth_set_id) |
Accept a device |
reject(device_id, auth_set_id) |
Reject a device |
set_auth_status(device_id, auth_set_id, status) |
Set auth set status |
get_auth_status(device_id, auth_set_id) |
Get auth set status |
remove_auth_set(device_id, auth_set_id) |
Remove auth set |
decommission(device_id) |
Decommission a device |
revoke_token(token_id) |
Revoke device API token |
limits() |
Get accepted device limits per tier |
license() |
Get device license data (CSV) |
auto_auth(request, signature=...) |
Automatically authenticate a device |
Deployments (client.deployments)
| Method | Description |
|---|---|
list(status=..., type=...) |
List all deployments |
get(deployment_id) |
Get a single deployment |
create(name=..., artifact_name=..., devices=...) |
Create a deployment |
stats(deployment_id) |
Get deployment statistics |
stats_list(deployment_ids) |
Get statistics for multiple deployments |
devices(deployment_id, status=...) |
List devices in a deployment |
log(deployment_id, device_id) |
Get device deployment log |
try_log(deployment_id, device_id) |
Get deployment log or None if unavailable |
abort(deployment_id) |
Abort a deployment |
device_history(device_id, status=...) |
List deployment history for a device |
abort_device(device_id) |
Abort active/pending deployments for a device |
Artifacts (client.artifacts)
| Method | Description |
|---|---|
list(name=..., device_type=...) |
List all artifacts |
get(artifact_id) |
Get a single artifact |
upload(file, description=...) |
Upload an artifact |
download(artifact_id) |
Download an artifact |
download_to_file(artifact_id, path) |
Download to a file |
delete(artifact_id) |
Delete an artifact |
Releases (client.releases)
| Method | Description |
|---|---|
list(name=..., tag=...) |
List all releases |
get(name) |
Get a single release |
delete(name or [name,...]) |
Delete one or more releases |
update(name, update) |
Update release fields (for example notes) |
set_tags(name, tags) |
Replace tags for a release |
list_tags() |
List all release tags |
list_update_types() |
List all release update types |
list_delta_jobs(sort=...) |
List server-side delta generation jobs |
get_delta_job(job_id) |
Get delta generation job details |
Inventory (client.inventory)
| Method | Description |
|---|---|
list(group=..., filters=...) |
List all inventory devices |
get(device_id) |
Get device inventory |
count(group=..., filters=...) |
Count devices |
get_device_group(device_id) |
Get device's group |
set_device_group(device_id, group) |
Set device's group |
clear_device_group(device_id) |
Remove from group |
Groups (client.inventory.groups)
| Method | Description |
|---|---|
list() |
List all group names |
devices(name) |
List device IDs in a group |
add_devices(name, device_ids) |
Add devices to a group |
remove_devices(name, device_ids) |
Remove devices from a group |
delete(name) |
Delete a group |
Tags (client.inventory.tags)
| Method | Description |
|---|---|
list(device_id) |
Get device tags |
set(device_id, name, value) |
Set a tag |
delete(device_id, name) |
Delete a tag |
Inventory Filters v2 (client.filters)
| Method | Description |
|---|---|
attributes() |
List filterable inventory attributes |
search(params=...) |
Search devices with filter predicates |
list() |
List saved filters |
create(definition) |
Create a saved filter |
get(filter_id) |
Get a saved filter definition |
update(filter_id, definition) |
Update a saved filter |
delete(filter_id) |
Delete a saved filter |
execute(filter_id) |
Search devices using a saved filter |
statistics() |
Get inventory statistics |
User Administration (client.useradm)
| Method | Description |
|---|---|
list_users() |
List users in the current tenant |
create_user(user) |
Create a user |
user_exists(email) |
Check whether a user exists |
get_user(user_id) |
Get user by ID |
update_user(user_id, user) |
Update user by ID |
delete_user(user_id) |
Delete user by ID |
me() |
Get current user information |
update_me(user) |
Update current user information |
enable_2fa(user_id="me") |
Enable 2FA for a user |
disable_2fa(user_id="me") |
Disable 2FA for a user |
settings() |
Get global settings and ETag |
set_settings(settings, if_match=...) |
Set global settings |
my_settings() |
Get current user settings and ETag |
set_my_settings(settings, if_match=...) |
Set current user settings |
list_personal_access_tokens() |
List personal access tokens |
create_personal_access_token(request) |
Create a personal access token |
revoke_personal_access_token(token_id) |
Revoke a personal access token |
list_roles() |
List RBAC roles (v2) |
create_role(role) |
Create RBAC role (v2) |
get_role(role_id) |
Get RBAC role (v2) |
update_role(role_id, role) |
Update RBAC role (v2) |
delete_role(role_id) |
Delete RBAC role (v2) |
list_permission_sets() |
List permission sets (v2) |
create_permission_set(permission_set) |
Create permission set (v2) |
get_permission_set(permission_set_id) |
Get permission set (v2) |
update_permission_set(permission_set_id, permission_set) |
Update permission set (v2) |
delete_permission_set(permission_set_id) |
Delete permission set (v2) |
Tenant Administration (client.tenantadm)
| Method | Description |
|---|---|
list_tenants() |
List child tenants |
create_tenant(tenant) |
Create a child tenant |
me() |
Get current tenant |
delete_inactive_tenant(tenant_id) |
Remove inactive tenant |
cancel_tenant(tenant_id, request) |
Request tenant cancellation |
update_child_tenant(tenant_id, tenant) |
Update child tenant |
update_plan(tenant_id, request) |
Request plan/add-on change |
init_tenant_removal(tenant_id) |
Start asynchronous tenant removal |
set_tenant_status(tenant_id, status) |
Set tenant status |
billing_products() |
Get billing product info |
billing_info() |
Get billing info summary |
init_card_update() |
Initialize card update flow |
confirm_card_update(intent_id) |
Confirm card update |
register_billing_profile(profile) |
Register billing profile |
billing_profile() |
Get billing profile |
update_billing_profile(profile) |
Update billing profile |
change_subscription(request) |
Request subscription change |
subscription() |
Get current subscription |
preview_invoice(request) |
Preview upcoming invoice |
stripe_secret() |
Get Stripe client secret |
contact_support(request) |
Send message to support |
Device Connect (client.deviceconnect)
| Method | Description |
|---|---|
get_device(device_id) |
Get current device connection state |
connect(device_id, headers=...) |
Initiate websocket upgrade handshake for interactive session |
check_update(device_id) |
Trigger check-update on device |
send_inventory(device_id) |
Trigger send-inventory on device |
playback(session_id, sleep_ms=..., headers=...) |
Initiate websocket upgrade handshake for session playback |
download(device_id, path=...) |
Download file content and metadata from device |
upload(device_id, path=..., fileobj=...) |
Upload file to device |
Typed helper support:
upload(..., request=UploadFileRequest(...))for structured upload metadata and optional inline bytes content.
Device Configure (client.deviceconfigure)
| Method | Description |
|---|---|
get(device_id) |
Get device configuration |
set(device_id, configuration) |
Replace device configuration |
deploy(device_id, request=...) |
Trigger configuration deployment |
Device Monitor (client.devicemonitor)
| Method | Description |
|---|---|
alerts(device_id, ...) |
List alerts for a device |
latest_alerts(device_id, ...) |
List latest alerts for a device |
config(device_id) |
List monitor check configuration for a device |
set_alert_channel_status(name, enabled=...) |
Enable/disable a global alert channel |
Typed helper support:
alerts(..., query=AlertsQuery(...))latest_alerts(..., query=LatestAlertsQuery(...))
IoT Manager (client.iot)
| Method | Description |
|---|---|
list_integrations() |
List configured cloud integrations |
register_integration(integration) |
Register a new integration |
remove_integration(integration_id) |
Remove a configured integration |
set_integration_credentials(integration_id, credentials) |
Replace integration credentials |
unregister_device_integrations(device_id) |
Remove all integrations from a device |
device_states(device_id) |
Get states for all integrations for a device |
device_state(device_id, integration_id) |
Get state for one integration |
set_device_state(device_id, integration_id, state) |
Replace desired state for one integration |
events(integration_id=...) |
List integration events |
Typed helper support:
events(..., query=EventsQuery(...))for typedintegration_id,page, andper_pagelist_integrations(query=IntegrationsQuery(...))for typedpageandper_page
IntegrationsQuery(...) also supports additive client-side filters:
providerscopedescription_contains
Compatibility fallback strategy for future backend query params:
- Use
future_params={...}to pass through potential future server query params. - If backend rejects unknown params with HTTP 400/422, pymenderio retries with stable params when
allow_unsupported_params_fallback=True(default), then applies client-side typed filters.
Credential variants for client.iot.register_integration(...) and client.iot.set_integration_credentials(...):
- HTTP webhook credentials (
type="http") - Azure IoT Hub shared access secret (
type="sas") - AWS credentials (
type="aws")
Typed helper support for integration registration:
register_integration(IntegrationCreateRequest(provider=..., credentials=..., scopes=...))
IoT event payload parsing for client.iot.events(...):
device-provisioned,device-decommissioned,device-status-changedmapevent.datatoDeviceAuthEventdevice-inventory-changedmapsevent.datatoDeviceInventoryEvent
Typed helper support for state updates:
set_device_state(..., DeviceStateUpdate(desired=...))
Authentication
Token File
By default, pymenderio reads the JWT token from the same cache path used by mender-cli:
~/.cache/mender/authtoken.
client = MenderClient.from_token_file()
# or specify a custom token file path
client = MenderClient.from_token_file("/path/to/authtoken")
mender-cli Compatible Config
pymenderio can also read mender-cli configuration from ~/.mender-clirc
(JSON containing server and username) and combine it with the default token path:
client = MenderClient.from_mender_cli()
Direct Token
client = MenderClient(
server="https://hosted.mender.io",
token="your-jwt-token",
)
Login
from pymenderio.auth import login, write_token_file
token = login(
server="https://hosted.mender.io",
email="user@example.com",
password="password",
totp_code="123456", # Optional 2FA code
)
# Optionally save for future use
write_token_file(token)
To persist server and username in mender-cli config format:
from pymenderio.auth import write_config_file
write_config_file(
server="https://eu.hosted.mender.io",
username="user@example.com",
)
For a single call that logs in and saves both config and token in mender-cli compatible locations:
from pymenderio.auth import login_and_save_session
token = login_and_save_session(
server="https://eu.hosted.mender.io",
email="user@example.com",
password="password",
)
Examples
The repository includes runnable sync/async script pairs under examples.
Parity script pairs:
- Device list: examples/sync_list_devices.py and examples/async_list_devices.py
- Inventory list: examples/sync_list_inventory.py and examples/async_list_inventory.py
- Artifacts list: examples/sync_list_artifacts.py and examples/async_list_artifacts.py
- Releases list: examples/sync_list_releases.py and examples/async_list_releases.py
- Deployments list: examples/sync_list_deployments.py and examples/async_list_deployments.py
- Get resource by kind: examples/sync_get_resource.py and examples/async_get_resource.py
- Deployment details: examples/sync_deployment_details.py and examples/async_deployment_details.py
- Inventory filters v2: examples/sync_inventory_v2_filters.py and examples/async_inventory_v2_filters.py
- Deployment delta jobs: examples/sync_deployment_delta_jobs.py and examples/async_deployment_delta_jobs.py
- Device auth search/preauthorize: examples/sync_devauth_search_and_preauth.py and examples/async_devauth_search_and_preauth.py
- User administration basics: examples/sync_user_admin_basics.py and examples/async_user_admin_basics.py
- Tenant administration basics: examples/sync_tenant_admin_basics.py and examples/async_tenant_admin_basics.py
- Device services overview: examples/sync_device_services_overview.py and examples/async_device_services_overview.py
- Login and save session: examples/sync_login_and_save_session.py and examples/async_login_and_save_session.py
- Real API smoke (combined sync + async probe): examples/real_api_smoke.py
See EXAMPLES.md for:
- script-by-script usage
- live API validation results
- model compatibility notes
See API.md for the full public SDK reference:
- all public methods
- all public classes
- all public errors
API Reference
For a complete surface inventory, see API.md.
Error Handling
from pymenderio import MenderClient
from pymenderio.exceptions import (
AuthenticationError,
NotFoundError,
ValidationError,
)
with MenderClient.from_token_file() as client:
try:
device = client.devices.get("nonexistent-id")
except NotFoundError:
print("Device not found")
except AuthenticationError:
print("Invalid or expired token")
Development
# Show available targets
make help
# Create local virtual environment and install dependencies
make venv
# Run tests in Docker
make test
# Lint and type-check in Docker
make lint
make typecheck
# Run all checks (lint, typecheck, test)
make check
# Remove local virtual environment
make venv-clean
If you prefer Poetry directly:
# Install dependencies
poetry install
# Run tests
poetry run pytest
# Type checking
poetry run mypy src/
# Linting
poetry run ruff check src/
Release
GitHub Actions workflows in .github/workflows/release-testpypi.yml and .github/workflows/release-pypi.yml handle package publishing.
One-time setup:
- Configure a Trusted Publisher in TestPyPI for .github/workflows/release-testpypi.yml.
- Configure a Trusted Publisher in PyPI for .github/workflows/release-pypi.yml.
Release flow:
- Publish a prerelease to TestPyPI with a prerelease tag, for example:
git tag v0.1.1-rc1
git push origin v0.1.1-rc1
- Publish a final release to PyPI with a stable tag:
git tag v0.1.1
git push origin v0.1.1
The PyPI workflow validates that the tag version matches the package version in pyproject.toml.
License
MIT License - see LICENSE for details.
Metadata
Release files for pymenderio 0.1.1
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 |
|---|---|---|---|---|
| pymenderio-0.1.1.tar.gz | 42.4 kB | With provenance | Details for pymenderio-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pymenderio-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for pymenderio-0.1.1-py3-none-any.whl |
Total release size: 92.8 kB
Release files / pymenderio-0.1.1.tar.gz
| Download URL | pymenderio-0.1.1.tar.gz |
|---|---|
| Size | 42.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ea005c53f7b60b31debc4311ce1f133fae83f2d2e65c57164361459151be8d09
|
|
BLAKE2b-256 checksum How to use checksums |
4c3b34fdb2e9f184515e6475487ba607917bb04198f69c9f9a7d8357512f6799
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 1, 2026.
Transparency logRelease files / pymenderio-0.1.1-py3-none-any.whl
| Download URL | pymenderio-0.1.1-py3-none-any.whl |
|---|---|
| Size | 50.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b73c706aec5daea65aedbcc4261e8f01c0ad77dd44de3373e687b7f1a7e789d7
|
|
BLAKE2b-256 checksum How to use checksums |
d9f03efa9d406b8f74b226a62d3661cee387553dd9679ce28675cd6214533655
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 1, 2026.
Transparency logSecurity for pymenderio 0.1.1
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 pymenderio-0.1.1.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 Aug 1, 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.