Devices
Device operations live under the Devices tag in the canonical OpenAPI bundle (operationId values below). Responses are DeviceResponse (or lists/pagination wrappers) unless noted — see schemas in the API reference.
Organization scope, X-Org, output_options, and pagination are required on several routes exactly as documented in OpenAPI. Start with Organization context & pagination before calling org-scoped device APIs.
Operations vs wire routes
Section titled “Operations vs wire routes”| Concern | HTTP | operationId | Notes |
|---|---|---|---|
| List | GET /devices | list_devices | Requires X-Org, output_options, pagination; optional filters (integration_id, device_kind, q, …). |
| Create | POST /devices | create_device | Body matches CreateDeviceRequest (see bundle). X-Org header required. |
| Get | GET /devices/{id} | get_device | Requires X-Org and output_options query. |
| Update | PUT /devices/{id} | update_device | |
| Soft delete | DELETE /devices/{id} | delete_device | |
| Restore | POST /devices/{id}/restore | restore_device | |
| Hard delete | DELETE /devices/{id}/purge | hard_delete_device | |
| Door restrictions | GET /devices/{id}/door-restrictions | get_door_restrictions | 400 if the device is not a virtual_access_portal; 404 if missing. |
| Metadata schema | GET /devices/{id}/metadata-definition | get_device_metadata_definition | Requires X-Org. |
SDK coverage
Section titled “SDK coverage”| Capability | Python | Rust (openapp_sdk) | Go | TypeScript (AsyncClient) |
|---|---|---|---|---|
| List / CRUD + purge / restore | client.devices.* | client.devices() thin JSON helpers | Full generated DevicesAPI | listDevices facade only (GET /devices); no typed CRUD helpers yet |
| Filters & pagination on list | list(..., org_id=, integration_id=, …) | High-level list() sends bare GET /devices — use transport() + RequestSpec for full OpenAPI query/header parity | ListDevices builder | Parameter reserved; wire call does not send X-Org — prefer Python or Go for full control |
| Images | upload_image, upload_image_from_url | Use transport or REST | Generated operations | Not exposed on façade |
For Python-only image uploads and scripting patterns, see Python — Resources.
Typical errors
Section titled “Typical errors”In addition to 401 (bad or missing API key), device routes commonly return 403 (role/org mismatch), 404 (unknown id), or 400 (validation / wrong device kind, e.g. door restrictions on non-portals). Shapes follow ApiErrorResponse — see Errors & retries.
Examples
Section titled “Examples”List devices
Section titled “List devices”devices.list forwards query params to list_devices. Pass org_id so the bridge can align org context with the wire contract where your deployment expects it.
page = await client.devices.list( org_id="01HORG00000000000000000000", integration_id="01HINT00000000000000000000", limit=50,)ListDevices requires XOrg, OutputOptions, and Pagination.
import ( openapiclient "github.com/tomers/openapp-sdk/go")
resp, httpResp, err := client.DevicesAPI.ListDevices(ctx). XOrg("01HORG00000000000000000000"). OutputOptions(*openapiclient.NewMultiResourceOutputOptionsQuery(false, false, false)). Pagination(openapiclient.PaginationQuery{ Limit: openapiclient.PtrInt32(50), Offset: openapiclient.PtrInt32(0), }). Execute()if err != nil { return err}defer httpResp.Body.Close()Quick path: high-level DevicesClient::list (no filters). For list_devices parity (X-Org, output_options, pagination), use Transport with a RequestSpec (same pattern as Organization context & pagination).
use openapp_sdk::Client;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let devices = client.devices().list().await?;import { AsyncClient } from "@tomers/openapp-sdk";
const client = new AsyncClient("https://api.openapp.house/api/v1_openapp_YOUR_SECRET");const raw = await client.listDevices("01HORG00000000000000000000");The façade calls GET /devices only; it does not attach X-Org. Use another SDK or raw HTTP with headers from Authentication when the server requires org context on this route.
Create device
Section titled “Create device”CreateDeviceRequest requires org_id, integration_id, and name (localized map). X-Org must match the org you are creating in.
device = await client.devices.create( org_id="01HORG00000000000000000000", integration_id="01HINT00000000000000000000", name={"en": "LAN controller"},)Ensure the bridge sends X-Org for this route (same org_id as in the body is typical).
import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
body := *openapiclient.NewCreateDeviceRequest( "01HINT00000000000000000000", *openapiclient.NewLocalizedString(map[string]string{"en": "LAN controller"}), "01HORG00000000000000000000",)dev, httpResp, err := client.DevicesAPI.CreateDevice(ctx). XOrg("01HORG00000000000000000000"). CreateDeviceRequest(body). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = devHigh-level devices().create omits X-Org on the wire — use transport + RequestSpec when your deployment requires header parity with OpenAPI.
use openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::json;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let body = json!({ "org_id": "01HORG00000000000000000000", "integration_id": "01HINT00000000000000000000", "name": { "en": "LAN controller" },});let created: serde_json::Value = client .transport() .request_json(RequestSpec { method: Method::POST, path: "/devices", body: Some(&body), extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;const orgId = "01HORG00000000000000000000";const device = await fetch("https://api.openapp.house/api/v1/devices", { method: "POST", headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId, }, body: JSON.stringify({ org_id: orgId, integration_id: "01HINT00000000000000000000", name: { en: "LAN controller" }, }),}).then((r) => r.json());Body matches CreateDeviceRequest — keep org_id and X-Org aligned. Replace with AsyncClient.createDevice once the Node façade exposes it.
Get and update
Section titled “Get and update”device = await client.devices.get("01HDEV00000000000000000000")updated = await client.devices.update( "01HDEV00000000000000000000", name={"en": "Front door"},)GetDevice requires XOrg and OutputOptions. Use the builder pattern from the pkg.go.dev listing.
dev, httpResp, err := client.DevicesAPI.GetDevice(ctx, "01HDEV00000000000000000000"). XOrg("01HORG00000000000000000000"). OutputOptions(*openapiclient.NewSingleResourceOutputOptionsQuery(false, false)). Execute()Quick path: devices().get / devices().update issue bare GET|PUT /devices/{id} without X-Org or output_options. For get_device / update_device parity (X-Org; get_device also needs include_deleted / include_metadata per SingleResourceOutputOptionsQuery), use transport() + RequestSpec:
use openapp_sdk::Client;use serde_json::json;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let device = client.devices().get("01HDEV00000000000000000000").await?;let _updated = client .devices() .update("01HDEV00000000000000000000", &json!({ "name": { "en": "Front door" } })) .await?;use openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::json;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let dev_id = "01HDEV00000000000000000000";let path = format!("/devices/{dev_id}");
let device: serde_json::Value = client .transport() .request_json(RequestSpec { method: Method::GET, path: &path, query: &[ ("include_deleted", Some("false".into())), ("include_metadata", Some("false".into())), ], extra_headers: &[("X-Org", org_id.clone())], ..Default::default() }) .await?;
let body = json!({ "name": { "en": "Front door" } });let _updated: serde_json::Value = client .transport() .request_json(RequestSpec { method: Method::PUT, path: &path, body: Some(&body), extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;There is no patch_device in the published OpenAPI bundle — device rows are replaced with update_device (PUT) only.
const orgId = "01HORG00000000000000000000";const deviceId = "01HDEV00000000000000000000";
const getUrl = new URL(`https://api.openapp.house/api/v1/devices/${deviceId}`);getUrl.searchParams.set("include_metadata", "false");
const device = await fetch(getUrl, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, },}).then((r) => r.json());
const updated = await fetch(`https://api.openapp.house/api/v1/devices/${deviceId}`, { method: "PUT", headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId, }, body: JSON.stringify({ name: { en: "Front door" } }),}).then((r) => r.json());GET /devices/{id} carries the same flat output_options keys (include_metadata, include_deleted) as the list route. Replace with AsyncClient.getDevice / AsyncClient.updateDevice once the Node façade exposes them.
Soft delete, restore, purge
Section titled “Soft delete, restore, purge”await client.devices.delete("01HDEV00000000000000000000")restored = await client.devices.restore("01HDEV00000000000000000000")await client.devices.purge("01HDEV00000000000000000000")Each call requires XOrg on the builder (same as soft-delete / restore / purge in the generated client).
import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
orgID := "01HORG00000000000000000000"devID := "01HDEV00000000000000000000"
soft, httpResp, err := client.DevicesAPI.DeleteDevice(ctx, devID).XOrg(orgID).Execute()if err != nil { return err}defer httpResp.Body.Close()_ = soft
restored, httpResp, err := client.DevicesAPI.RestoreDevice(ctx, devID).XOrg(orgID).Execute()if err != nil { return err}defer httpResp.Body.Close()_ = restored
purged, httpResp, err := client.DevicesAPI.HardDeleteDevice(ctx, devID).XOrg(orgID).Execute()if err != nil { return err}defer httpResp.Body.Close()_ = purgedclient.devices().delete("01HDEV00000000000000000000").await?;let _ = client.devices().restore("01HDEV00000000000000000000").await?;client.devices().purge("01HDEV00000000000000000000").await?;const orgId = "01HORG00000000000000000000";const deviceId = "01HDEV00000000000000000000";const headers = { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId,};
await fetch(`https://api.openapp.house/api/v1/devices/${deviceId}`, { method: "DELETE", headers,});
const restored = await fetch( `https://api.openapp.house/api/v1/devices/${deviceId}/restore`, { method: "POST", headers },).then((r) => r.json());
await fetch(`https://api.openapp.house/api/v1/devices/${deviceId}/purge`, { method: "DELETE", headers,});All three lifecycle routes require X-Org on the wire — Go’s builder enforces it, and Python/Rust high-level helpers attach it via interceptors. Replace each fetch with AsyncClient lifecycle helpers once the Node façade exposes them.
Door restrictions and metadata definition
Section titled “Door restrictions and metadata definition”rules = await client.devices.door_restrictions("01HDEV00000000000000000000")await client.devices.set_door_restrictions( "01HDEV00000000000000000000", apartment_entity_ids=["01JENT00000000000000000000"],)schema = await client.devices.metadata_definition("01HDEV00000000000000000000")import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
devID := "01HDEV00000000000000000000"orgID := "01HORG00000000000000000000"
rules, httpResp, err := client.DevicesAPI.GetDoorRestrictions(ctx, devID).Execute()if err != nil { return err}defer httpResp.Body.Close()_ = rules
schema, httpResp, err := client.DevicesAPI.GetDeviceMetadataDefinition(ctx, devID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = schemalet rules = client.devices().door_restrictions("01HDEV00000000000000000000").await?;let schema = client.devices().metadata_definition("01HDEV00000000000000000000").await?;get_device_metadata_definition requires X-Org on the wire; if the bare metadata_definition() helper fails, repeat the transport + RequestSpec + extra_headers pattern from list devices.
Updating door restrictions via PUT is not wrapped in the high-level Rust client yet — use transport() or REST if your deployment exposes it.
const orgId = "01HORG00000000000000000000";const deviceId = "01HDEV00000000000000000000";
const rules = await fetch( `https://api.openapp.house/api/v1/devices/${deviceId}/door-restrictions`, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET" } },).then((r) => r.json());
const schema = await fetch( `https://api.openapp.house/api/v1/devices/${deviceId}/metadata-definition`, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, }, },).then((r) => r.json());get_door_restrictions rejects with 400 when the device is not a virtual_access_portal, 404 when missing — branch on res.status if your UI distinguishes these. get_device_metadata_definition requires X-Org. Replace each fetch with AsyncClient helpers once the Node façade exposes them.
Related
Section titled “Related”- Organization context & pagination
- Authentication
- Errors & retries
- Devices reference — product-oriented device concepts in the docs tree.