Build an agent that controls physical access
An access-control agent (or integration bot) calls OpenApp on behalf of an operator: list doors, open a gate when a parcel arrives, or revoke a guest invite at checkout. This guide covers the minimum wiring — credentials, org scope, and executing switchable.open safely.
For sector-specific workflows (private home, STR, hotel, campus, locker), pair this with Integrate OpenApp with your existing software and Model by sector.
Prerequisites
Section titled “Prerequisites”- An OpenApp account with permission to manage the target organization.
- An API key for server-to-server use.
- The organization id (ULID) and at least one entity id for a door or gate (from the dashboard or
GET /entities).
Store secrets in environment variables — never embed keys in prompts, llms.txt, or public repos.
export OPENAPP_API_BASE='https://api.openapp.house/api/v1'export OPENAPP_API_KEY='v1_openapp_YOUR_SECRET'export OPENAPP_ORG_ID='01HORG00000000000000000000'export ENTITY_ID='01HENTITY000000000000000000'Organization context (X-Org)
Section titled “Organization context (X-Org)”Many routes are scoped to an organization. Pass the org id in the X-Org header when the API reference or Organization context docs say it is required (for example GET /devices, integration admin routes).
SDK
devices = await client.devices.list()import { AsyncClient } from "@tomers/openapp-sdk";
const client = new AsyncClient(apiKey, { org: orgId });const devices = await client.listDevices(orgId);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?;page, httpResp, err := client.DevicesAPI.ListDevices(ctx). XOrg(orgID). 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()_ = pagePass org context via client headers where required — see Organization context.
HTTP API (curl)
curl -sS "${OPENAPP_API_BASE}/devices" \ -H "Authorization: Bearer ${OPENAPP_API_KEY}" \ -H "X-Org: ${OPENAPP_ORG_ID}"Execute a door action
Section titled “Execute a door action”Doors and relays use entity actions. The common unlock action is switchable.open — the same call as Open the door with the HTTP API.
SDK
await client.entities.by_id(entity_id).open()const res = await fetch( `${apiBase}/entities/${entityId}/actions/switchable.open`, { method: "POST", headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json", "x-org": orgId, }, body: JSON.stringify({}), },);if (!res.ok) throw new Error(`open failed: ${res.status}`);use openapp_sdk::Client;use serde_json::json;
let client = Client::builder() .api_key("https://api.openapp.house/api/v1_openapp_YOUR_SECRET") .build()?;
client .entities() .invoke_action(entity_id, "open", &json!({})) .await?;httpResp, err := client.EntitiesAPI.ExecuteEntityAction(ctx, entityID, "open"). Body(map[string]interface{}{}). Execute()if err != nil { return err}defer httpResp.Body.Close()Pass org context on list calls via headers where required — see Entities and Organization context.
HTTP API (curl)
curl -sS -X POST \ -H "Authorization: Bearer ${OPENAPP_API_KEY}" \ -H "Content-Type: application/json" \ -H "X-Org: ${OPENAPP_ORG_ID}" \ -d '{}' \ "${OPENAPP_API_BASE}/entities/${ENTITY_ID}/actions/switchable.open"Discover entities before acting
Section titled “Discover entities before acting”Agents should list or resolve entities instead of hard-coding ids when possible:
GET /integrations→ pick the Virtual Access or hardware integration.GET /integrations/{id}/entitiesorGET /entitieswith pagination.- Match
entity_typeand metadata (door name, zone, external id). - Call
POST /entities/{id}/actions/{action_id}only on the resolved id.
See Agent-relevant API index for operation groupings.
Safety checklist
Section titled “Safety checklist”| Risk | Mitigation |
|---|---|
| Wrong door opened | Resolve entity by id + human-readable label; confirm in UI for first deploy. |
| Stale or stolen API key | Rotate keys in the dashboard; scope automation to dedicated keys. |
| Unattended unlock | Gate agent tools behind confirmation; document org policy. |
| Missing audit trail | Surface API errors to operators; use dashboard audit where available. |
OpenApp orchestrates access; life-safety and compliance remain the operator’s responsibility — see Access control architecture.
Next steps
Section titled “Next steps”- Time-bound guest invitations for STR and hotel flows.
- Virtual intercom flow for directory + call + unlock.
- SDK overview to replace raw HTTP in production code.