Skip to content
OpenAppPhysical access, fully managed
Login

Virtual intercom — directory, call, unlock

A virtual intercom replaces or augments a lobby panel: visitors scan a QR code or open a public portal URL, browse the building’s directory listings, call or message a listing, and the resident who answers can unlock the door. OpenApp implements this through Virtual Access integrations, access portals, and public session APIs — no rip-and-replace hardware required when you already have relays or cloud gate APIs.

This guide targets building operators and integrators automating apartment and office entry. For end-user portal concepts, see Access Portals and Virtual Access integration.

Visitor scans portal QR (/p/{public_id})
│
├─ Directory mode → pick listing → POST portal/sessions (voice / video / message)
│ │ caller gets caller_token
│ └─ Resident is notified with callee_token
│ └─ Resident answers → POST session/open?token=<callee_token>
│
└─ Guest with invite → quick open (invite execute)
  1. Add a Virtual Access integration and link a door device to a portal (portals user guide).
  2. Note publicPortalId from the portal’s public URL, and the target_entity_id of the directory listing the visitor calls (list them with GET /public/access/portals/{publicPortalId}/targets).
  1. Optional: go2rtc for live camera on video calls.

If the physical intercom or mailbox bank already lists unit numbers and resident names but listing display names in OpenApp are empty, use Enrich (Virtual intercom → Enrich): upload panel photos, Analyze, review, and Commit. This fills the visitor directory labels visitors see when browsing the portal.

See Virtual Access — automatic directory filling from photos for privacy, API key setup, and manual JSON fallback.

Start a session from the public portal. Body PublicPortalCreateSessionRequest requires target_entity_id (the listing) and mode: "voice", "video", or "message". Any other mode returns 400. Signed-in minors can’t place visitor calls (403).

SDK

session = await client.public_access.portal_start_session(
portal_id,
target_entity_id=entity_id,
mode="voice", # voice | video | message
)
session_id = session["session_id"]
caller_token = session["caller_token"]

The response includes session_id, caller_token, expires_at, PeerJS routing, and callees_notified. Poll GET /public/access/sessions/{sessionId} with the caller token until the callee answers. In message mode no call is rung; residents are notified when a message is sent.

HTTP API (curl)

Terminal window
export OPENAPP_API_BASE='https://openapp.house/api/v1'
export PUBLIC_PORTAL_ID='01HPUBLICPORTAL000000000000'
export TARGET_ENTITY_ID='01HENTITY000000000000000000'
curl -sS -X POST \
-H "Content-Type: application/json" \
-d '{
"target_entity_id": "'"${TARGET_ENTITY_ID}"'",
"mode": "voice"
}' \
"${OPENAPP_API_BASE}/public/access/portals/${PUBLIC_PORTAL_ID}/sessions"

Only the callee opens the door. OpenApp never returns the callee token to the caller; it goes to the listing’s residents in the incoming-call notification (web push and the native app). The resident’s client sends it as Authorization: Bearer <callee_token> or ?token=<callee_token>. A caller token, or no token, is refused (403 / 401).

SDK

# session_open() can't send the callee token; call the route through the client transport.
await client._request(
"POST",
f"/public/access/sessions/{session_id}/open",
query=[("token", callee_token)],
)

HTTP API (curl)

Terminal window
export CALLEE_TOKEN='token-from-the-incoming-call-notification'
curl -sS -X POST \
-H "Authorization: Bearer ${CALLEE_TOKEN}" \
"${OPENAPP_API_BASE}/public/access/sessions/${SESSION_ID}/open"

Residents receive calls through Web Push and the native app; configure subscriptions via Me — push. Session opens count against the door_opens quota and go through the same life-safety gate as any open.

Visitors with a valid invite link skip the directory and use invite execute. Pair with Time-bound guest invitation for STR and hotel stays.

StepAPI surface
List portalsGET /integrations/{id}/access-portals
Probe reachabilityGET /public/access/portals/{id}/reachable
Callable targetsGET /public/access/portals/{id}/targets
Who openedPortal opens are audited as access_portal.opened — see Query access and audit activity

Full operation index: Agent-relevant API.


← Time-bound guest invitation · Agents overview