Resources
The SDK exposes the full OpenApp API surface through per-tag sub-clients.
Each is available as an attribute on both Client and AsyncClient.
| Attribute | OpenAPI tag | Typical methods |
|---|---|---|
client.api_keys | API Keys | list, create, delete |
client.users | Users | list, get, create, update, delete, invite, upload_image, upload_image_from_url |
client.orgs | Orgs | list, get, create, update, delete, upload_image, upload_image_from_url |
client.devices | Devices | list, get, update, delete, commands, upload_image, upload_image_from_url |
client.entities | Entities | list, get, by_id, action, open, close, on, off, upload_image, upload_image_from_url |
client.integrations | Integrations | list, get, create, update, delete, upload_image, upload_image_from_url |
client.zones | Zones | list, get, create, update, delete |
client.lan_agent | LAN agent | status, agents |
client.scripting | Scripting | execute |
client.apartment_residents | Apartment Residents | list, create, update, delete |
client.public_access | Public Access | list, create, update, delete |
client.auth | Auth | whoami |
client.me | Me | get, update |
client.eula | EULA | current, accept |
client.status | Status | get |
The authoritative list of methods per sub-client is the
API reference (same openapi.json the SDK is generated
from).
Payload conventions
Section titled “Payload conventions”The current SDK exposes request and response payloads as plain Python
dictionaries (dict[str, Any]). This keeps the surface stable while the typed
Pydantic models are still under active generation. If you want type safety
today, see Typing & Pydantic.
Examples
Section titled “Examples”Paginate through users
Section titled “Paginate through users”page = client.users.list(limit=100)while page["items"]: for user in page["items"]: print(user["email"]) if not page.get("next_cursor"): break page = client.users.list(limit=100, cursor=page["next_cursor"])Invoke an entity action
Section titled “Invoke an entity action”entity = client.entities.by_id("01J00000000000000000000000")result = entity.open(reason="visitor buzzed in", duration_s=5)print(result["state"])
# Generic fallback for any action id:custom = entity.action("switchable.open", reason="visitor buzzed in", duration_s=5)print(custom["state"])Upload org, user, integration, device, or entity images
Section titled “Upload org, user, integration, device, or entity images”The API exposes the same multipart file upload for organizations, users, integrations, devices, and entities: POST /{resource}/{id}/image with image/jpeg, image/png, or image/webp (matching the dashboard).
from pathlib import Path
logo = Path("logo.png").read_bytes()client.orgs.upload_image("01HORG...", data=logo, content_type="image/png", filename="logo.png")
avatar = Path("avatar.jpg").read_bytes()client.users.upload_image("01HUSR...", data=avatar, content_type="image/jpeg", filename="avatar.jpg")
icon = Path("integration.jpg").read_bytes()client.integrations.upload_image( "01HZZZ...", data=icon, content_type="image/jpeg", filename="integration.jpg")
door_jpeg = Path("door.jpg").read_bytes()client.devices.upload_image( "01HXXX...", data=door_jpeg, content_type="image/jpeg", filename="door.jpg",)
face_png = Path("face.png").read_bytes()client.entities.upload_image( "01HYYY...", data=face_png, content_type="image/png", filename="face.png",)When the image is already on the web, use upload_image_from_url (HTTP or HTTPS only):
client.users.upload_image_from_url("01HUSR...", url="https://example.com/avatar.jpg")client.devices.upload_image_from_url("01HXXX...", url="https://example.com/door.jpg")Run an OpenApp Scripting program
Section titled “Run an OpenApp Scripting program”Use the client.scripting sub-client when you need OpenApp Scripting beyond individual REST calls:
result = client.scripting.execute( script='entity_action("01J00000000000000000000000", "switchable.open", #{});')print(result)You can also keep the script in a .openapp file and pass its filename:
result = client.scripting.execute_file("open-door.openapp")print(result)Query device state
Section titled “Query device state”device = client.devices.get("dev_abc")for cmd in client.devices.commands(device_id=device["id"], limit=5)["items"]: print(cmd["issued_at"], cmd["command"], cmd["result"])Query strings and path params
Section titled “Query strings and path params”- Path parameters are accepted as positional or keyword arguments, matching the
method signature (
client.orgs.get("org_123")). - Query strings are keyword arguments;
Nonevalues are omitted. - List query params are passed as Python lists and serialized with
?k=a&k=b.
Request and response hooks
Section titled “Request and response hooks”Use interceptors if you need to add custom headers, log requests, or modify request/response bodies across all calls.