Async Python SDK for the Airbeld backend
Project description
Airbeld Python SDK
Async Python SDK for the Airbeld API, providing access to air quality devices and telemetry data.
If you want to contribute, read CONTRIBUTING and DEVELOPER.
Installation
# Using pip
pip install airbeld-api-sdk
# Using uv (recommended)
uv add airbeld-api-sdk
Examples
Running Examples
All examples require setting environment variables before running:
# Export required environment variables
export AIRBELD_API_BASE="https://api.airbeld.com"
export AIRBELD_API_TOKEN="your-jwt-token-here"
# Run an example
python examples/quickstart.py
Environment Variables:
AIRBELD_API_BASE: API base URL (default:https://api.airbeld.com)AIRBELD_API_TOKEN: JWT access token for authentication
Quickstart Example
import asyncio
import os
from airbeld import AirbeldClient
async def main():
# Initialize client (JWT token obtained outside SDK)
base_url = os.environ.get("AIRBELD_API_BASE", "https://api.airbeld.com")
token = os.environ["AIRBELD_API_TOKEN"]
async with AirbeldClient(token=token, base_url=base_url) as client:
# Get all devices
devices = await client.async_get_devices()
print(f"Found {len(devices)} devices")
if devices:
device = devices[0]
print(f"Device: {device.name} ({device.status})")
# Get latest telemetry readings (no date range)
readings = await client.async_get_readings_by_date(
device_id=device.id,
sensor="temperature" # Optional: filter to single sensor
)
# Print latest temperature reading
if "temperature" in readings.sensors:
latest_temp = readings.get_latest_value("temperature")
print(f"Latest temperature: {latest_temp}°C")
if __name__ == "__main__":
asyncio.run(main())
Note: JWT token acquisition happens outside the SDK. The SDK only handles API requests with a ready token.
Getting Latest Readings
You can fetch the latest sensor readings without specifying a date range:
import asyncio
import os
from airbeld import AirbeldClient
async def main():
base_url = os.environ.get("AIRBELD_API_BASE", "https://api.airbeld.com")
token = os.environ["AIRBELD_API_TOKEN"]
async with AirbeldClient(token=token, base_url=base_url) as client:
devices = await client.async_get_devices()
if devices:
device = devices[0]
# Get latest readings without specifying start_date/end_date
readings = await client.async_get_readings_by_date(device_id=device.id)
# Display latest values
for sensor_name, metric in readings.sensors.items():
latest = readings.get_latest_value(sensor_name)
print(f"{metric.display_name}: {latest} {metric.unit}")
if __name__ == "__main__":
asyncio.run(main())
Getting Historical Readings
Fetch sensor readings for a specific date range with hourly or daily aggregation:
import asyncio
import os
from airbeld import AirbeldClient
async def main():
base_url = os.environ.get("AIRBELD_API_BASE", "https://api.airbeld.com")
token = os.environ["AIRBELD_API_TOKEN"]
async with AirbeldClient(token=token, base_url=base_url) as client:
devices = await client.async_get_devices()
if devices:
device = devices[0]
# Get hourly readings for a specific date range
readings = await client.async_get_readings_by_date(
device_id=device.id,
start_date="2025-09-19", # Format: YYYY-MM-DD or 'today'
end_date="2025-09-19", # Same day = 24 hours of data
period="hour", # Aggregation: 'hour' or 'day'
sensor="temperature" # Optional: single sensor filter
)
# Access all temperature values
if "temperature" in readings.sensors:
temp_metric = readings.sensors["temperature"]
print(f"Temperature readings: {len(temp_metric.values)} values")
for reading in temp_metric.values:
print(f" {reading.timestamp}: {reading.value}°C")
if __name__ == "__main__":
asyncio.run(main())
API Reference
Client Methods
AirbeldClient(token, base_url, timeout)
Initialize the Airbeld API client.
Parameters:
token(str, required): JWT access token for authenticationbase_url(str, optional): API base URL. Default:"https://api.airbeld.com"timeout(float, optional): Request timeout in seconds. Default:10.0
Usage:
async with AirbeldClient(token=token, base_url=base_url) as client:
# Use client...
async_get_devices()
Get list of all devices.
Parameters: None
Returns: list[DeviceSummary] - List of device objects
Raises:
AuthError: Authentication failed (401/403)ApiError: Other API errors (4xx/5xx)NetworkError: Network connectivity issues
Example:
devices = await client.async_get_devices()
for device in devices:
print(f"{device.name} - {device.status}")
async_get_readings_by_date(device_id, start_date, end_date, sensor, period)
Get telemetry readings for a device.
Parameters:
device_id(int, required): Device IDstart_date(str, optional): Start date inYYYY-MM-DDformat or'today'. If omitted, returns latest data.end_date(str, optional): End date inYYYY-MM-DDformat or'today'. If omitted, returns latest data.sensor(str, optional): Single sensor name to filter (e.g.,"temperature","pm2p5"). If omitted, returns all sensors.period(str, optional): Data aggregation period. Values:"hour"or"day".
Returns: Readings - Object containing sensor readings
Raises:
AuthError: Authentication failed (401/403)ApiError: API errors including 404 (device not found), 413 (range too large)RateLimitError: Rate limit exceeded (429)NetworkError: Network connectivity issues
Examples:
# Get latest readings (no date range)
readings = await client.async_get_readings_by_date(device_id=123)
# Get historical readings with date range
readings = await client.async_get_readings_by_date(
device_id=123,
start_date="2025-09-19",
end_date="2025-09-19",
period="hour"
)
# Filter to single sensor
readings = await client.async_get_readings_by_date(
device_id=123,
sensor="temperature"
)
async_get_all_readings_by_date(start_date, end_date, sensor, period)
Get telemetry readings for all user devices.
Parameters:
start_date(str, optional): Start date inYYYY-MM-DDformat or'today'. If omitted, returns latest data.end_date(str, optional): End date inYYYY-MM-DDformat or'today'. If omitted, returns latest data.sensor(str, optional): Single sensor name to filter (e.g.,"temperature","pm2p5"). If omitted, returns all sensors.period(str, optional): Data aggregation period. Values:"hour"or"day".
Returns: list[DeviceReadings] - List of device objects with metadata and sensor readings
Raises:
AuthError: Authentication failed (401/403)ApiError: API errors including 413 (range too large)RateLimitError: Rate limit exceeded (429)NetworkError: Network connectivity issues
Examples:
# Get latest readings for all devices
devices = await client.async_get_all_readings_by_date()
for device in devices:
print(f"Device: {device.display_name or device.name}")
temp = device.get_latest_value("temperature")
print(f" Temperature: {temp}°C")
# Get historical readings with date range
devices = await client.async_get_all_readings_by_date(
start_date="2025-10-14",
end_date="2025-10-14",
period="hour"
)
# Filter to single sensor
devices = await client.async_get_all_readings_by_date(
sensor="pm2p5"
)
set_token(new_token)
Update the authorization token at runtime.
Parameters:
new_token(str, required): New JWT access token
Returns: None
Example:
client.set_token(refreshed_token)
Authentication Functions
async_login(base_url, email, password, timeout)
Authenticate with email and password to obtain JWT tokens.
Parameters:
base_url(str, required): API base URLemail(str, required): User email addresspassword(str, required): User passwordtimeout(float, optional): Request timeout in seconds. Default:10.0
Returns: TokenSet - Object containing access token, refresh token, expires_in, and token_type
Raises:
AuthError: Invalid credentials (401)RateLimitError: Rate limit exceeded (429)ApiError: Other API errors (4xx/5xx)NetworkError: Network connectivity issues
Example:
from airbeld import async_login
token_set = await async_login(
base_url="https://api.airbeld.com",
email="user@example.com",
password="password"
)
print(f"Access token: {token_set.access_token}")
print(f"Expires in: {token_set.expires_in} seconds")
Data Models
DeviceSummary
Device information object.
Attributes:
uid(str): Unique device identifierid(int): Device IDname(str): Device namedisplay_name(str | None): Custom display namedescription(str): Device descriptiontype(str | None): Device typeis_locked(bool): Whether device is lockedstatus(str): Device status -"online"or"offline"sector(str | None): Sector namesector_id(int | None): Sector IDlocation(str | None): Location namelocation_id(int | None): Location IDtimezone(str): IANA timezone
DeviceReadings
Device with telemetry readings (returned by async_get_all_readings_by_date).
Attributes:
id(int): Device IDuid(str): Unique device identifiername(str): Device namedisplay_name(str | None): Custom display namelocation(str | None): Location namesector(str | None): Sector nametimezone(str): IANA timezonesensors(dict[str, TelemetryMetric]): Dictionary of sensor metrics by sensor name
Methods:
get_latest_value(metric_name: str) -> float | None: Get the latest value for a specific sensorpm2_5(property): Shortcut to access PM 2.5 metric (returnsTelemetryMetric | None)
Readings
Container for sensor readings.
Attributes:
sensors(dict[str, TelemetryMetric]): Dictionary of sensor metrics by sensor name
Methods:
get_latest_value(metric_name: str) -> float | None: Get the latest value for a specific sensorpm2_5(property): Shortcut to access PM 2.5 metric (returnsTelemetryMetric | None)
TelemetryMetric
Individual sensor metric with metadata and values.
Attributes:
name(str): Sensor namedisplay_name(str | None): Display nameunit(str): Measurement unitdescription(str | None): Sensor descriptionvalues(list[TelemetryValue]): List of readings
TelemetryValue
Single sensor reading.
Attributes:
timestamp(datetime): Reading timestampvalue(float | None): Measured value
Authentication Options
The SDK supports two authentication paths depending on your use case:
Home Assistant Integration
For Home Assistant users, authentication is handled by the integration:
- Home Assistant performs OAuth2 flow automatically
- SDK receives a ready JWT token from the integration
- See docs/home-assistant-auth.md for details
Standalone Applications (CLI, Scripts, etc.)
For standalone applications, use the built-in authentication:
import asyncio
import os
from airbeld import async_login, AirbeldClient
async def main():
# Authenticate with email/password
token_set = await async_login(
base_url="https://api.airbeld.com",
email=os.environ["AIRBELD_USER_EMAIL"],
password=os.environ["AIRBELD_USER_PASSWORD"]
)
# Create client with access token
async with AirbeldClient(token=token_set.access_token) as client:
# List devices
devices = await client.async_get_devices()
print(f"Found {len(devices)} devices:")
for device in devices:
print(f" - {device.name} ({device.status})")
if __name__ == "__main__":
asyncio.run(main())
⚠️ Security Warning: Never commit real credentials or tokens to version control. Use environment variables or secure secret storage systems.
Token Management
For applications requiring token refresh or runtime token updates:
# Update token at runtime
client.set_token(new_token)
# Or use refresh token (if implemented)
new_token_set = await async_login(...)
client.set_token(new_token_set.access_token)
Development
Installation
Using uv (recommended):
# Clone the repository
git clone https://github.com/Embio-Diagnostics/airbeld-api-sdk.git
cd airbeld-api-sdk
# Create virtual environment and install dependencies
uv sync
Using pip:
# Clone the repository
git clone https://github.com/Embio-Diagnostics/airbeld-api-sdk.git
cd airbeld-api-sdk
# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install package with dev dependencies
pip install -e ".[dev]"
Running Tests
# Using uv
uv run pytest
# Or with pip
pytest
Linting and Formatting
# Check code style
uv run ruff check .
# Format code
uv run ruff format .
# Or with pip
ruff check .
ruff format .
Type Checking
# Using uv
uv run mypy src
# Or with pip
mypy src
Building the Package
# Using uv
uv build
# Or with pip
python -m build
Resources
- Contributing: See CONTRIBUTING.md for contribution guidelines
- Changelog: See CHANGELOG.md for version history
- License: This project is licensed under the MIT License - see LICENSE file for details
Project 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 airbeld_api_sdk-0.4.0.tar.gz.
File metadata
- Download URL: airbeld_api_sdk-0.4.0.tar.gz
- Upload date:
- Size: 48.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3b4b73d2af08e532f3687005e833c5ec5f80e742dac0ee74a34c6aff73a9982
|
|
| MD5 |
788b9956532297ccafc52c5f3c35c971
|
|
| BLAKE2b-256 |
868020c3802b0d8e1535285939bf1c6b5a46ab528577d8814bb324ff031c21df
|
File details
Details for the file airbeld_api_sdk-0.4.0-py3-none-any.whl.
File metadata
- Download URL: airbeld_api_sdk-0.4.0-py3-none-any.whl
- Upload date:
- Size: 12.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17808f8933686599c1d87d04491d66f47df4966b679b60fd7b92b73675bf8765
|
|
| MD5 |
42e42b3af0be11e7710101f33122e99d
|
|
| BLAKE2b-256 |
4dbf784c0dcb447b43d358df94f5c396158525be0fcd6072ac3d3dcc2911ea8c
|