Async Python client for the Aranet Cloud REST API.
Wraps the public Aranet Cloud OpenAPI 3.0 spec — 25 of its 27 read-only
GET endpoints (all but the two attachment-metadata endpoints) — and
returns typed dataclass models. Designed primarily as the backing library
for the
aranet-cloud-homeassistant
HACS integration, but usable as a standalone Python client.
Status: Alpha (0.2.x). The OpenAPI mapping is stable; the public Python surface may still evolve as the HA integration drives requirements. Pin to a minor version in production.
pip install aranet-cloudPython 3.11+ required. Single runtime dependency: aiohttp.
import asyncio
from aranet_cloud import AranetCloudClient
async def main() -> None:
async with AranetCloudClient(api_key="...") as client:
# List every sensor on your account
sensors = await client.get_sensors()
for s in sensors:
print(f" {s.serial} {s.name:<30s} type={s.type}")
# Latest reading per (sensor × metric), with name resolution
readings, links = await client.get_measurements_last()
for r in readings:
metric = links.name("metric", r.metric) or r.metric
unit = links.name("unit", r.unit) or r.unit
print(f" {r.sensor:>10s} {metric:>22s}: {r.value} {unit}")
asyncio.run(main())Output against a typical home/garden account:
A0005 Bedroom type=S4V1
A0001 Kitchen type=S4V1
...
4000005 Temperature: 72.5 °F
4000005 Humidity: 30 %
4000005 CO₂: 757 ppm
4000005 Atmospheric Pressure: 697.9 mmHg
...
The Aranet Cloud API uses a single header — ApiKey: <your-key>. No
OAuth, no token refresh. Generate a key from your Aranet Cloud dashboard
under Account → API.
AranetCloudClient(api_key="vku...")25 of the 27 GET endpoints in the public OpenAPI spec (all but the two attachment-metadata endpoints — the file/thumbnail downloads are wrapped):
| Domain | Methods |
|---|---|
| Sensors | get_sensors, get_sensor, get_sensor_types, get_sensor_type |
| Measurements | get_measurements_last, iter_measurements_history (paginated) |
| Telemetry | get_telemetry_last, iter_telemetry_history (paginated) |
| Bases | get_bases, get_base |
| Alarms | get_alarms_actual, get_alarms_history, get_alarm_rules, get_alarm_rule |
| Assets | get_assets, get_asset |
| Tags | get_tags, get_tag |
| Catalog | get_metrics, get_metric, get_unit |
| Attachments | download_sensor_attachment, download_asset_attachment |
See docs/architecture.md for the full API
reference, edge cases discovered during live probing, and design notes.
History endpoints are paginated by the server. The library hides the mechanics via async iterators:
async for reading in client.iter_measurements_history(sensor="4000005", hours=24):
print(reading.time, reading.value)The iterator follows the next token transparently until the server
returns no more data. Mind the time windows — /measurements/history
without a sensor filter caps at 7 days; with a sensor filter it caps at
6 months.
AranetError ← base; catch this for a blanket handler
├── AranetConnectionError ← network, timeout, TLS, DNS
├── AranetAuthError ← 401 (key wrong/missing/revoked) - NOT transient
├── AranetValidationError ← 400 (carries correlation_id from API)
├── AranetRateLimitError ← 429 (with retry_after if present)
├── AranetServerError ← 5xx after exhausted retries
└── AranetNotFoundError ← 404 (rare; API often returns 200 {} instead)
Auth errors deserve special handling — the Aranet API returns 401 as
plain text (not JSON), so AranetAuthError doesn't carry a
correlation ID. Validation errors are JSON with error[].id correlation
tokens, preserved on AranetValidationError.correlation_id for support
escalations.
- Async-first (
aiohttp). Use as an async context manager (auto session) or inject an existingClientSession— the HA-friendly pattern. - Typed: every response shape modelled as a dataclass with
from_dictthat ignores unknown fields, forward-compatible with new server fields. - Pagination hidden:
iter_*_history()async generators follownexttokens transparently. - Retry/backoff on 5xx, 429, and transient network failures.
Exponential backoff
1s, 2s, 4s, …capped at 30 s; 3 retries by default (so 1s, 2s, 4s). A serverRetry-Afteron 429 is honoured — also clamped to the 30 s cap, so a hostile/misconfigured value can't stall the caller. - Polite-spacing floor (250 ms) between successive requests. The Aranet API has no documented rate limit, but we don't hammer.
- Never logs the API key. Debug logs cover request method, path, status, and body size; the key is in headers only.
The library has no HA dependencies and is fine to use in standalone Python scripts, FastAPI services, data-ingestion pipelines, etc.:
async with AranetCloudClient(api_key="...") as client:
# Pull 24 hours of CO₂ readings for a single sensor
readings = [
r async for r in client.iter_measurements_history(
sensor="4000005", metric="3", hours=24,
)
]
print(f"got {len(readings)} CO₂ samples")git clone https://github.com/jasonjhofmann/aranet-cloud
cd aranet-cloud
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest # 50 tests
ruff check . # lint
mypy src # type-check (strict)
uv build # build wheel + sdist (what CI publishes)Apache 2.0