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.
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 pymenderio-0.1.1.tar.gz.
File metadata
- Download URL: pymenderio-0.1.1.tar.gz
- Upload date:
- Size: 42.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea005c53f7b60b31debc4311ce1f133fae83f2d2e65c57164361459151be8d09
|
|
| MD5 |
b6997b9437f324cc91b268ed41b3d80a
|
|
| BLAKE2b-256 |
4c3b34fdb2e9f184515e6475487ba607917bb04198f69c9f9a7d8357512f6799
|
Provenance
The following attestation bundles were made for pymenderio-0.1.1.tar.gz:
Publisher:
release.yml on boeboe/pymenderio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymenderio-0.1.1.tar.gz -
Subject digest:
ea005c53f7b60b31debc4311ce1f133fae83f2d2e65c57164361459151be8d09 - Sigstore transparency entry: 2312693585
- Sigstore integration time:
-
Permalink:
boeboe/pymenderio@55e637bbd103e2db1cd9835853a60687a558e5d1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/boeboe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@55e637bbd103e2db1cd9835853a60687a558e5d1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pymenderio-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pymenderio-0.1.1-py3-none-any.whl
- Upload date:
- Size: 50.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b73c706aec5daea65aedbcc4261e8f01c0ad77dd44de3373e687b7f1a7e789d7
|
|
| MD5 |
fe1f3c338eca8b1e42a92cb5392fd14c
|
|
| BLAKE2b-256 |
d9f03efa9d406b8f74b226a62d3661cee387553dd9679ce28675cd6214533655
|
Provenance
The following attestation bundles were made for pymenderio-0.1.1-py3-none-any.whl:
Publisher:
release.yml on boeboe/pymenderio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymenderio-0.1.1-py3-none-any.whl -
Subject digest:
b73c706aec5daea65aedbcc4261e8f01c0ad77dd44de3373e687b7f1a7e789d7 - Sigstore transparency entry: 2312693623
- Sigstore integration time:
-
Permalink:
boeboe/pymenderio@55e637bbd103e2db1cd9835853a60687a558e5d1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/boeboe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@55e637bbd103e2db1cd9835853a60687a558e5d1 -
Trigger Event:
push
-
Statement type: