Skip to content
OAOpenAppPhysical Security as a Service
Login

Public Access

Public Access routes drive visitor experiences: invite URLs, building portal pages, and short-lived sessions (door open, lights, call/video signaling). Authorization is carried by opaque ids in the path (inviteToken, publicPortalId, sessionId), not org X-Org / entity RBAC. Response shapes include PublicInviteResponse, PublicPortalResponse, PublicSessionResponse, etc. — see the API reference.

You still point the SDK at the same API base URL; the transport may attach your API key for routing, but these routes do not represent an authenticated org user. For admin-side portal configuration, see Integrations.

AreaHTTPoperationIdNotes
InvitesGET /public/access/invites/{inviteToken}get_public_inviteDashboard payload for an invite link.
POST .../claimpost_public_invite_claimClaim / associate the invite (body per bundle when required).
POST .../executepost_public_invite_executeBody PublicInviteExecuteRequestPublicInviteExecuteResponse.
POST .../sessionpost_public_invite_sessionStart a guest session from an invite.
PortalsGET /public/access/portals/{publicPortalId}get_public_portalPortal config + mode.
POST .../lights, POST .../openpost_public_portal_lights, post_public_portal_openControl paths on the public portal id.
GET .../reachableget_public_portal_reachableHealth / connectivity probe.
POST .../sessionspost_public_portal_sessionsCreate session from portal (PublicPortalCreateSessionRequest).
GET .../targetsget_public_portal_targetsCallable targets for the portal UI.
SessionsGET /public/access/sessions/{sessionId}get_public_sessionPoll guest session state.
POST .../cancel, POST .../declinepost_public_session_cancel, post_public_session_declineEnd or reject.
POST .../lights, POST .../open, POST .../notify-messagepost_public_session_lights, post_public_session_open, post_public_session_notify_messageIn-session actions.
GET .../streamsget_public_session_streamsWebRTC / streaming metadata (PublicSessionStreamsResponse).

Path segments in OpenAPI use inviteToken, publicPortalId, sessionId — SDK helpers take plain strings.

SurfacePythonRust (openapp_sdk)GoTypeScript (AsyncClient)
Full table aboveclient.public_accessclient.public_access() — methods mirror path names (get_invite, claim_invite, portal_open, session_streams, …)PublicAccessAPIServicegetPublicInvite, claimPublicInvite only — extend via transport / other SDKs for portals and sessions

400 on bad tokens or payloads.401 / 403 on claim/execute when policy blocks the action.404 for expired or unknown tokens.403 on post_public_invite_execute when forbidden. See Errors & retries.

invite = await client.public_access.get_invite(token)
await client.public_access.claim_invite(token, **{})
result = await client.public_access.portal_open(portal_id, reason="visitor")

Execute an invite (POST .../invites/{token}/execute)

Section titled “Execute an invite (POST .../invites/{token}/execute)”

Body PublicInviteExecuteRequest is a single required field, grant_id (the grant published in get_public_invite’s payload). Response PublicInviteExecuteResponse includes ok, optional message, door_auto_close_duration, and lights_auto_off_duration.

out = await client.public_access.execute_invite(token, grant_id="grant_abc123")

post_public_portal_sessions opens a guest WebRTC session on the portal. Body PublicPortalCreateSessionRequest requires target_entity_id and mode (e.g. "call", "video"). The response carries session_id, caller_token, expires_at, and PeerJS routing details.

session = await client.public_access.portal_start_session(
portal_id,
target_entity_id=entity_id,
mode="call",
)

Drive a guest session (poll / cancel / decline / open)

Section titled “Drive a guest session (poll / cancel / decline / open)”

Once a session exists, get_public_session polls state, post_public_session_cancel ends it from the caller side, post_public_session_decline rejects from the callee side, and post_public_session_open triggers an entry-side action while the call is live.

state = await client.public_access.get_session(session_id)
await client.public_access.session_open(session_id)
await client.public_access.cancel_session(session_id)
await client.public_access.decline_session(session_id)

Portal targets, reachability, and session streams

Section titled “Portal targets, reachability, and session streams”

get_public_portal_targets lists callable entities for the portal UI; get_public_portal_reachable is a connectivity probe; get_public_session_streams returns WebRTC routing (PeerJS host/port/path, ICE config) needed by the visitor client.

targets = await client.public_access.portal_targets(portal_id)
reachable = await client.public_access.portal_reachable(portal_id)
streams = await client.public_access.session_streams(session_id)

Start a guest session from an invite (POST .../invites/{token}/session)

Section titled “Start a guest session from an invite (POST .../invites/{token}/session)”

post_public_invite_session is body-less — the bundle reads everything it needs from inviteToken. Useful for invites that already encode the target entity / mode and just need the visitor to “begin.” Pair it with get_public_session for polling and get_public_session_streams for WebRTC routing.

session = await client.public_access.start_invite_session(token)
session_id = session["session_id"]

Lights and notify-message (portal_lights, session_lights, session_notify_message)

Section titled “Lights and notify-message (portal_lights, session_lights, session_notify_message)”

Three POSTs that drive side effects rather than carrying a meaningful response: portal-scoped lights, in-session lights, and an in-session text notification (Web Push to apartment residents). NotifyPortalMessageBody is { "text": string } (only text is required). The Go session-scoped variants take an optional Token(token) query param if your bundle issued a per-session caller token (see caller_token on PublicPortalCreateSessionRequest responses).

await client.public_access.portal_lights(portal_id)
await client.public_access.session_lights(session_id)
await client.public_access.session_notify_message(session_id, text="Hi, I'm at the door")

Sessions are short-lived. The visitor side typically polls get_public_session at 1–2 s intervals while the call is pending / ringing / active, exits when status becomes terminal (accepted + door_opened, declined, cancelled, expired), and surfaces failures to the user. The pattern is the same in every language — use your runtime’s sleep primitive and a bounded retry budget. get_public_session_streams is fetched once after the session reaches active, not on every poll.

import asyncio
TERMINAL = {"accepted", "declined", "cancelled", "expired", "failed"}
async def wait_for_session(client, session_id: str, timeout_s: float = 60.0) -> dict:
deadline = asyncio.get_event_loop().time() + timeout_s
while True:
state = await client.public_access.get_session(session_id)
if state.get("status") in TERMINAL:
return state
if asyncio.get_event_loop().time() >= deadline:
raise TimeoutError(f"session {session_id} did not reach terminal state")
await asyncio.sleep(1.0)
final = await wait_for_session(client, session_id)