Integrations
Integrations bind external systems (provider types) into an organization: devices, entities, and portal flows hang off an integration id. Almost everything requires X-Org — see Organization context & pagination. Shapes follow IntegrationResponse, CreateIntegrationRequest, UpdateIntegrationRequest, and related schemas in the API reference.
Related: per-entity automation lives under Entities (POST /entities/{id}/actions/{action_id}). GET /integrations/{id}/entities (list_integration_entities) lists entities owned by devices on that integration — same pagination/output rules as org GET /entities.
Some URLs under /integrations/... are tagged elsewhere in OpenAPI: .../building-users (Apartment Residents), .../lan-agent/... (LAN agent), .../zones (Zones — see Zones).
Operations vs wire routes (summary)
Section titled “Operations vs wire routes (summary)”| Area | Representative HTTP | operationId | Notes |
|---|---|---|---|
| Catalog | GET / POST /integrations | list_integrations, create_integration | List requires output_options + pagination, optional provider_type, q. |
| Provider catalog | GET /integrations/provider-types, GET .../definition | list_integration_provider_types, get_integration_provider_definition | Static discovery for dashboards and codegen. |
| Instance + lifecycle | GET/PUT /integrations/{id}, DELETE .../purge, POST .../restore | get_integration, update_integration, hard_delete_integration, restore_integration | Purge is destructive removal per bundle rules. |
| Device metadata schema | GET .../device-metadata-schema | get_integration_device_metadata_schema | JsonForms/schema hints for device_metadata on devices under this integration. |
| Discovery | GET .../discovered-devices | get_integration_discovered_devices | Provider-specific provisioning candidates. |
| Entities (integration scope) | GET .../entities | list_integration_entities | Requires output_options + pagination; optional entity_type. |
| Ops | GET .../ops, POST .../ops/{op_id} | list_integration_ops, execute_integration_op | Provider maintenance / sync jobs — JSON body often {}. |
| Access portals | GET/POST .../access-portals, GET /integrations/access-portals/{portal_id}, PUT/DELETE .../access-portals/{portal_id} | list_integration_access_portals, create_integration_access_portal, get_access_portal_by_id, update_integration_access_portal, delete_integration_access_portal | Building entry portals for virtual access flows. |
| Access invites | PUT/DELETE .../access-invites/{invite_link_id}, POST .../restore | update_integration_access_invite, delete_integration_access_invite, restore_integration_access_invite |
Multipart POST /integrations/{id}/image may exist where deployments expose it — Python includes upload_image / upload_image_from_url (see Python — Resources).
SDK coverage
Section titled “SDK coverage”| Capability | Python | Rust (openapp_sdk) | Go | TypeScript (AsyncClient) |
|---|---|---|---|---|
| Surface above | client.integrations — matches methods on IntegrationsClient | client.integrations() — thin JSON helpers for the same paths | IntegrationsAPIService | Not on façade |
GET /integrations: Rust list() is a bare GET /integrations — OpenAPI requires output_options + pagination; use transport() + RequestSpec for parity (same story as Devices). integrations.entities(id) in Python/Rust is a bare GET .../entities — the bundle expects output_options + pagination; use Go or explicit RequestSpec for full wire fidelity.
Typical errors
Section titled “Typical errors”401 / 403 on org or integration access.404 for unknown ids.400 / 422 on validation.426 can appear on LAN-agent compatibility routes (see LAN agent docs when shipped). See Errors & retries.
Examples
Section titled “Examples”List integrations
Section titled “List integrations”page = await client.integrations.list(org_id="org_123")Wire parity for output_options / pagination may require raw requests — compare list_integrations in the bundle.
oo := openapiclient.NewMultiResourceOutputOptionsQueryWithDefaults()pg := openapiclient.NewPaginationQuery()resp, httpResp, err := client.IntegrationsAPI.ListIntegrations(ctx). XOrg(orgID). OutputOptions(*oo). Pagination(*pg). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = respuse openapp_sdk::Client;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let rows = client.integrations().list().await?;Prefer transport-level RequestSpec when the server rejects calls without output_options / pagination.
const url = new URL("https://api.openapp.house/api/v1/integrations");url.searchParams.set("limit", "50");url.searchParams.set("offset", "0");url.searchParams.set("include_metadata", "false");const page = await fetch(url, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, },}).then((r) => r.json());Add provider_type or q as additional query params for the same filters Go’s builder offers. Replace the fetch call with AsyncClient.listIntegrations once the Node façade exposes it.
Execute a provider op
Section titled “Execute a provider op”result = await client.integrations.run_op(integration_id, "some_op_id")httpResp, err := client.IntegrationsAPI.ExecuteIntegrationOp(ctx, integrationID, "some_op_id"). XOrg(orgID). Body(map[string]interface{}{}). Execute()if err != nil { return err}defer httpResp.Body.Close()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 result = client .integrations() .run_op(integration_id, "some_op_id", &json!({})) .await?;const result = await fetch( `https://api.openapp.house/api/v1/integrations/${integrationId}/ops/some_op_id`, { method: "POST", headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId, }, body: JSON.stringify({}), },).then((r) => r.json());Most provider ops accept an empty JSON body; pass the op-specific payload when the bundle requires one. Swap to AsyncClient.executeIntegrationOp once the Node façade exposes it.
Provider catalog (GET .../provider-types, GET .../definition)
Section titled “Provider catalog (GET .../provider-types, GET .../definition)”Use this before create_integration to pick a provider_type and load ProviderDefinition (capabilities, schema hooks) for dashboards or CLIs.
types = await client.integrations.provider_types()definition = await client.integrations.provider_definition("virtual_demo")ListIntegrationProviderTypes returns a typed map; GetIntegrationProviderDefinition returns a raw http.Response (decode JSON from Body).
import ( "context" "encoding/json" "io"
openapiclient "github.com/tomers/openapp-sdk/go")
types, httpResp, err := client.IntegrationsAPI.ListIntegrationProviderTypes(context.Background()).Execute()if err != nil { return err}defer httpResp.Body.Close()_ = types
defnHTTP, err := client.IntegrationsAPI.GetIntegrationProviderDefinition(context.Background(), "virtual_demo").Execute()if err != nil { return err}defer defnHTTP.Body.Close()body, err := io.ReadAll(defnHTTP.Body)if err != nil { return err}var defn map[string]anyif err := json.Unmarshal(body, &defn); err != nil { return err}_ = defnuse openapp_sdk::Client;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let types = client.integrations().provider_types().await?;let definition = client.integrations().provider_definition("virtual_demo").await?;const headers = { authorization: "Bearer v1_openapp_YOUR_SECRET" };
const types = await fetch( "https://api.openapp.house/api/v1/integrations/provider-types", { headers },).then((r) => r.json());
const definition = await fetch( "https://api.openapp.house/api/v1/integrations/provider-types/virtual_demo/definition", { headers },).then((r) => r.json());Both routes are static catalog reads — no X-Org required. Replace with AsyncClient helpers once the Node façade ships them.
Get integration (GET /integrations/{id})
Section titled “Get integration (GET /integrations/{id})”OpenAPI marks X-Org as required on this route — the Go client models it explicitly. Python/Rust high-level helpers assume your gateway or interceptors supply org context when needed.
row = await client.integrations.get(integration_id)If your stack requires an explicit org header for this path, wire X-Org through client configuration (see Python resources overview) or a RequestSpec interceptor — match what get_integration expects in the bundle.
import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
integration, httpResp, err := client.IntegrationsAPI.GetIntegration(context.Background(), integrationID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = integrationuse openapp_sdk::Client;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let row = client.integrations().get(integration_id).await?;Prefer transport() + RequestSpec with an X-Org header if the server rejects this without org context.
const integration = await fetch( `https://api.openapp.house/api/v1/integrations/${integrationId}`, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, }, },).then((r) => r.json());The wire requires X-Org — Node’s fetch lower-cases it for you, but include it on every org-scoped request. Replace with AsyncClient.getIntegration once the Node façade exposes it.
Create integration (POST /integrations)
Section titled “Create integration (POST /integrations)”Body CreateIntegrationRequest — org_id and provider_type are required; optional name, config, secrets, enabled (see schemas in the API reference).
created = await client.integrations.create( org_id=org_id, provider_type="virtual_demo", name="Demo bridge",)import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
req := openapiclient.CreateIntegrationRequest{ OrgId: orgID, ProviderType: "virtual_demo",}created, httpResp, err := client.IntegrationsAPI.CreateIntegration(context.Background()). XOrg(orgID). CreateIntegrationRequest(req). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = createduse openapp_sdk::Client;use serde_json::json;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000";let body = json!({ "org_id": org_id, "provider_type": "virtual_demo", "name": "Demo bridge",});let created = client.integrations().create(&body).await?;const created = await fetch("https://api.openapp.house/api/v1/integrations", { method: "POST", headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId, }, body: JSON.stringify({ org_id: orgId, provider_type: "virtual_demo", name: "Demo bridge", }),}).then((r) => r.json());Body matches CreateIntegrationRequest (required org_id + provider_type; optional name, config, secrets, enabled). Switch to AsyncClient.createIntegration once the Node façade exposes it.
Update, restore, purge
Section titled “Update, restore, purge”OpenAPI requires X-Org on update_integration, restore_integration, and hard_delete_integration — the Go builder enforces it; Python/Rust high-level helpers omit the header on the wire, so use transport + RequestSpec when your gateway rejects the request.
updated = await client.integrations.update( integration_id, name="Renamed bridge", enabled=False,)restored = await client.integrations.restore(integration_id)await client.integrations.purge(integration_id)import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
patch := openapiclient.UpdateIntegrationRequest{ Name: openapiclient.PtrString("Renamed bridge"),}updated, httpResp, err := client.IntegrationsAPI.UpdateIntegration(ctx, integrationID). XOrg(orgID). UpdateIntegrationRequest(patch). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = updated
restored, httpResp, err := client.IntegrationsAPI.RestoreIntegration(ctx, integrationID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = restored
purged, httpResp, err := client.IntegrationsAPI.HardDeleteIntegration(ctx, integrationID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = purgeduse 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 path = format!("/integrations/{integration_id}");let patch = json!({ "name": "Renamed bridge", "enabled": false });let _updated: serde_json::Value = client .transport() .request_json(RequestSpec { method: Method::PUT, path: &path, body: Some(&patch), extra_headers: &[("X-Org", org_id.clone())], ..Default::default() }) .await?;
let _restored = client.integrations().restore(integration_id).await?;client.integrations().purge(integration_id).await?;const headers = { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId,};
const updated = await fetch( `https://api.openapp.house/api/v1/integrations/${integrationId}`, { method: "PUT", headers, body: JSON.stringify({ name: "Renamed bridge", enabled: false }), },).then((r) => r.json());
const restored = await fetch( `https://api.openapp.house/api/v1/integrations/${integrationId}/restore`, { method: "POST", headers },).then((r) => r.json());
await fetch( `https://api.openapp.house/api/v1/integrations/${integrationId}/purge`, { method: "DELETE", headers },);Body for PUT is UpdateIntegrationRequest — every field is optional. Replace each fetch with AsyncClient lifecycle helpers once the Node façade ships them.
Discovered devices (GET /integrations/{id}/discovered-devices)
Section titled “Discovered devices (GET /integrations/{id}/discovered-devices)”Provider-specific provisioning candidates. X-Org is required; the Go return type is interface{} (the body shape varies by provider — decode into your own struct or map[string]any).
candidates = await client.integrations.discovered_devices(integration_id)import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
raw, httpResp, err := client.IntegrationsAPI.GetIntegrationDiscoveredDevices(ctx, integrationID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = rawuse openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::Value;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let path = format!("/integrations/{integration_id}/discovered-devices");let candidates: Value = client .transport() .request_json(RequestSpec { method: Method::GET, path: &path, extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;The bare integrations().discovered_devices(...) helper omits X-Org on the wire.
const candidates = await fetch( `https://api.openapp.house/api/v1/integrations/${integrationId}/discovered-devices`, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, }, },).then((r) => r.json());Body shape varies by provider — treat candidates as unknown and validate against your own type at the call site. Replace with AsyncClient.getIntegrationDiscoveredDevices once the Node façade exposes it.
Access portals (list and create)
Section titled “Access portals (list and create)”list_integration_access_portals returns ListIntegrationAccessPortalsResponse; create_integration_access_portal takes a CreateAccessPortalRequest (required name — accepts a string or LocalizedString map; optional device_external_id to bind the portal to a virtual_access_portal device) and returns CreateAccessPortalResponse (id, public_id, name). Both require X-Org.
portals = await client.integrations.list_access_portals(integration_id)created = await client.integrations.create_access_portal( integration_id, name={"en": "Lobby"}, device_external_id="lobby-gate",)public_id = created["public_id"]import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
list, httpResp, err := client.IntegrationsAPI.ListIntegrationAccessPortals(ctx, integrationID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = list
body := *openapiclient.NewCreateAccessPortalRequest( *openapiclient.NewLocalizedString(map[string]string{"en": "Lobby"}),)body.SetDeviceExternalId("lobby-gate")created, httpResp2, err := client.IntegrationsAPI.CreateIntegrationAccessPortal(ctx, integrationID). XOrg(orgID). CreateAccessPortalRequest(body). Execute()if err != nil { return err}defer httpResp2.Body.Close()_ = created.GetPublicId()use openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::{json, Value};
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let path = format!("/integrations/{integration_id}/access-portals");
let _portals: Value = client .transport() .request_json(RequestSpec { method: Method::GET, path: &path, extra_headers: &[("X-Org", org_id.clone())], ..Default::default() }) .await?;
let body = json!({ "name": { "en": "Lobby" }, "device_external_id": "lobby-gate",});let created: Value = client .transport() .request_json(RequestSpec { method: Method::POST, path: &path, body: Some(&body), extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;const portalsUrl = `https://api.openapp.house/api/v1/integrations/${integrationId}/access-portals`;
const portals = await fetch(portalsUrl, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, },}).then((r) => r.json());
const created = await fetch(portalsUrl, { method: "POST", headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId, }, body: JSON.stringify({ name: { en: "Lobby" }, device_external_id: "lobby-gate", }),}).then((r) => r.json() as Promise<{ id: string; public_id: string; name: unknown }>);
const publicId = created.public_id;name accepts a plain string or a LocalizedString map; device_external_id is optional and binds the portal to a virtual_access_portal device. Replace with AsyncClient portal helpers once the Node façade exposes them.
Update or delete a portal
Section titled “Update or delete a portal”UpdateAccessPortalRequest has no required fields — set whichever of name (localized) or device_external_id you want to change (null unlinks). Both routes require X-Org.
updated = await client.integrations.update_access_portal( integration_id, portal_id, name={"en": "Lobby (renamed)"},)await client.integrations.delete_access_portal(integration_id, portal_id)import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
patch := openapiclient.NewUpdateAccessPortalRequest()patch.SetName(*openapiclient.NewLocalizedString(map[string]string{"en": "Lobby (renamed)"}))updated, httpResp, err := client.IntegrationsAPI.UpdateIntegrationAccessPortal(ctx, integrationID, portalID). XOrg(orgID). UpdateAccessPortalRequest(*patch). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = updated
delResp, err := client.IntegrationsAPI.DeleteIntegrationAccessPortal(ctx, integrationID, portalID). XOrg(orgID). Execute()if err != nil { return err}defer delResp.Body.Close()use openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::{json, Value};
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let path = format!("/integrations/{integration_id}/access-portals/{portal_id}");
let patch = json!({ "name": { "en": "Lobby (renamed)" } });let _updated: Value = client .transport() .request_json(RequestSpec { method: Method::PUT, path: &path, body: Some(&patch), extra_headers: &[("X-Org", org_id.clone())], ..Default::default() }) .await?;
client .transport() .request_json::<(), ()>(RequestSpec { method: Method::DELETE, path: &path, extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;The bare integrations().update_access_portal(...) / delete_access_portal(...) helpers omit X-Org on the wire.
const portalUrl = `https://api.openapp.house/api/v1/integrations/${integrationId}/access-portals/${portalId}`;const headers = { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId,};
const updated = await fetch(portalUrl, { method: "PUT", headers, body: JSON.stringify({ name: { en: "Lobby (renamed)" } }),}).then((r) => r.json());
await fetch(portalUrl, { method: "DELETE", headers });UpdateAccessPortalRequest has no required fields — set name (string or LocalizedString) and/or device_external_id (pass null to unlink). Move to AsyncClient once the Node façade ships portal helpers.
Update, delete, restore an access invite
Section titled “Update, delete, restore an access invite”UpdateAccessInviteRequest is fully optional — common fields are is_enabled, name, expires_in, valid_from / valid_to, max_uses, portal_ids, schedules (replaces all entries when set), and disabled_justification. delete_integration_access_invite is a hard delete; restore_integration_access_invite undoes a soft revoke. All three require X-Org.
edited = await client.integrations.update_access_invite( integration_id, invite_link_id, is_enabled=False, disabled_justification="lost token",)await client.integrations.delete_access_invite(integration_id, invite_link_id)restored = await client.integrations.restore_access_invite(integration_id, invite_link_id)import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
patch := openapiclient.NewUpdateAccessInviteRequest()patch.SetIsEnabled(false)patch.SetDisabledJustification("lost token")edited, httpResp, err := client.IntegrationsAPI.UpdateIntegrationAccessInvite(ctx, integrationID, inviteLinkID). XOrg(orgID). UpdateAccessInviteRequest(*patch). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = edited
delResp, err := client.IntegrationsAPI.DeleteIntegrationAccessInvite(ctx, integrationID, inviteLinkID). XOrg(orgID). Execute()if err != nil { return err}defer delResp.Body.Close()
restored, httpResp2, err := client.IntegrationsAPI.RestoreIntegrationAccessInvite(ctx, integrationID, inviteLinkID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp2.Body.Close()_ = restoreduse openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::{json, Value};
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let invite_path = format!("/integrations/{integration_id}/access-invites/{invite_link_id}");let restore_path = format!("{invite_path}/restore");
let patch = json!({ "is_enabled": false, "disabled_justification": "lost token",});let _edited: Value = client .transport() .request_json(RequestSpec { method: Method::PUT, path: &invite_path, body: Some(&patch), extra_headers: &[("X-Org", org_id.clone())], ..Default::default() }) .await?;
client .transport() .request_json::<(), ()>(RequestSpec { method: Method::DELETE, path: &invite_path, extra_headers: &[("X-Org", org_id.clone())], ..Default::default() }) .await?;
let _restored: Value = client .transport() .request_json(RequestSpec { method: Method::POST, path: &restore_path, extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;The bare integrations().update_access_invite(...) / delete_access_invite(...) / restore_access_invite(...) helpers omit X-Org on the wire.
const inviteUrl = `https://api.openapp.house/api/v1/integrations/${integrationId}/access-invites/${inviteLinkId}`;const headers = { authorization: "Bearer v1_openapp_YOUR_SECRET", "content-type": "application/json", "x-org": orgId,};
const edited = await fetch(inviteUrl, { method: "PUT", headers, body: JSON.stringify({ is_enabled: false, disabled_justification: "lost token", }),}).then((r) => r.json());
await fetch(inviteUrl, { method: "DELETE", headers });
const restored = await fetch(`${inviteUrl}/restore`, { method: "POST", headers,}).then((r) => r.json());UpdateAccessInviteRequest is fully optional — common fields are is_enabled, name, expires_in, valid_from / valid_to, max_uses, portal_ids, schedules (replaces all entries), and disabled_justification. Replace with AsyncClient invite helpers once the Node façade exposes them.
Look up a portal by id (GET /integrations/access-portals/{portal_id})
Section titled “Look up a portal by id (GET /integrations/access-portals/{portal_id})”Resolves the portal’s owning integration so dashboards can build deep links. The path is integration-less (uses portal_id only) but X-Org is still required on the wire (the Go builder enforces it).
portal = await client.integrations.get_access_portal(portal_id)integration_id = portal["integration_id"]import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
portal, httpResp, err := client.IntegrationsAPI.GetAccessPortalById(ctx, portalID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = portaluse openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::Value;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let path = format!("/integrations/access-portals/{portal_id}");let portal: Value = client .transport() .request_json(RequestSpec { method: Method::GET, path: &path, extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;The bare integrations().get_access_portal(...) helper omits X-Org on the wire.
const portal = await fetch( `https://api.openapp.house/api/v1/integrations/access-portals/${portalId}`, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, }, },).then((r) => r.json() as Promise<{ integration_id: string }>);
const integrationId = portal.integration_id;The path is integration-less but X-Org is still required on the wire. Replace with AsyncClient.getAccessPortalById once the Node façade exposes it.
Device metadata schema (GET /integrations/{id}/device-metadata-schema)
Section titled “Device metadata schema (GET /integrations/{id}/device-metadata-schema)”Returns IntegrationDeviceMetadataSchemaResponse — provider JSON Schema dashboards use to render generic labels for device_metadata fields. Different from per-device get_device_metadata_definition (Devices). X-Org required.
schema = await client.integrations.device_metadata_schema(integration_id)schema, httpResp, err := client.IntegrationsAPI.GetIntegrationDeviceMetadataSchema(ctx, integrationID). XOrg(orgID). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = schemause openapp_sdk::{Client, transport::RequestSpec};use reqwest::Method;use serde_json::Value;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let org_id = "01HORG00000000000000000000".to_string();let path = format!("/integrations/{integration_id}/device-metadata-schema");let schema: Value = client .transport() .request_json(RequestSpec { method: Method::GET, path: &path, extra_headers: &[("X-Org", org_id)], ..Default::default() }) .await?;const schema = await fetch( `https://api.openapp.house/api/v1/integrations/${integrationId}/device-metadata-schema`, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, }, },).then((r) => r.json());This is the integration-wide JsonForms schema for device_metadata — distinct from the per-device get_device_metadata_definition route. Replace with AsyncClient.getIntegrationDeviceMetadataSchema once the Node façade exposes it.
List integration entities (GET /integrations/{id}/entities)
Section titled “List integration entities (GET /integrations/{id}/entities)”Returns PaginatedResponseEntityResponse. The Go builder requires XOrg, OutputOptions, and Pagination; optional EntityType filter. Python and Rust ship bare helpers — same pattern as list_entities where parity needs transport + RequestSpec.
rows = await client.integrations.entities(integration_id)import ( "context"
openapiclient "github.com/tomers/openapp-sdk/go")
page, httpResp, err := client.IntegrationsAPI.ListIntegrationEntities(ctx, integrationID). XOrg(orgID). OutputOptions(*openapiclient.NewMultiResourceOutputOptionsQuery(false, false, false)). Pagination(openapiclient.PaginationQuery{ Limit: openapiclient.PtrInt32(50), Offset: openapiclient.PtrInt32(0), }). EntityType("door"). Execute()if err != nil { return err}defer httpResp.Body.Close()_ = pageDrop EntityType(...) to list every entity type under the integration.
use openapp_sdk::Client;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
let rows = client.integrations().entities(integration_id).await?;The bare helper omits X-Org, output_options, and pagination. For full parity, switch to transport() + RequestSpec with X-Org in extra_headers and the same query encoding the Go builder uses.
const url = new URL( `https://api.openapp.house/api/v1/integrations/${integrationId}/entities`,);url.searchParams.set("limit", "50");url.searchParams.set("offset", "0");url.searchParams.set("include_metadata", "false");url.searchParams.set("entity_type", "door");
const page = await fetch(url, { headers: { authorization: "Bearer v1_openapp_YOUR_SECRET", "x-org": orgId, },}).then((r) => r.json());The Axum handler flattens output_options and pagination — pass each field (limit, offset, include_deleted, include_metadata) as its own query param, the same wire shape as list_device_entities. Drop entity_type to list every type. Replace with AsyncClient.listIntegrationEntities once the Node façade exposes it.