API

Overview

The API layer starts as the boundary between runtime, room state, and media services. M0 contracts are intentionally narrow and versioned through manifest schema.

Endpoints

GET /health

Returns service liveness for local and staging smoke checks.

Response:

{
  "status": "ok",
  "service": "api",
  "port": 4000
}

GET /api/rooms/:roomId/manifest

Returns the room manifest used by runtime boot.

Response shape:

{
  "schemaVersion": 1,
  "tenantId": "demo-tenant",
  "roomId": "demo-room",
  "template": "meeting-room-basic",
  "features": {
    "voice": true,
    "spatialAudio": true,
    "screenShare": false
  },
  "quality": {
    "default": "desktop-standard",
    "mobile": "mobile-lite",
    "xr": "xr"
  },
  "access": {
    "joinMode": "link",
    "guestAllowed": true
  }
}

POST /api/tokens/state

Returns a short-lived signed room session token used by runtime, apps/room-state, and media token issuing.

Request:

{
  "roomId": "demo-room",
  "participantId": "p1",
  "displayName": "Guest",
  "requestedRole": "guest"
}

Response:

{
  "token": "base64url-payload.hmac-signature",
  "expiresInSeconds": 900,
  "sessionId": "session-id",
  "role": "guest",
  "permissions": ["room.join", "audio.join", "surface.view"]
}

Signed payload claims are tenantId, roomId, participantId, displayName, role, optional roleSource, permissions, sessionId, iat, exp, and jti. The server normalizes permissions from the signed role when validating the token.

POST /api/tokens/media

Returns a media-plane join token payload for LiveKit.

Requires Authorization: Bearer <room-session-token>. The session token must match the requested room and participant. The API derives publish grants from signed room permissions instead of trusting caller-provided role claims.

Request:

{
  "roomId": "demo-room",
  "participantId": "p1",
  "canPublishAudio": true,
  "canPublishVideo": false
}

Runtime-mutating endpoints also require the same session token:

Response:

{
  "token": "base64url-encoded-payload",
  "expiresInSeconds": 900,
  "livekitUrl": "ws://localhost:7880"
}

Control-plane AuthN/AuthZ

Control-plane protected actions are deny-by-default. They require either x-vrata-admin-token: <CONTROL_PLANE_ADMIN_TOKEN> or Authorization: Bearer <room-session-token>.

Only the CONTROL_PLANE_ADMIN_TOKEN header is an operator/admin identity. Signed room-session tokens can authorize only explicitly allowed room-scoped actions; a room-session role named admin is not treated as a control-plane admin.

Host-owned control-plane actions require a trusted room-session role source. Tokens minted through dev-role query mode are valid runtime tokens but are denied for control-plane host-owned actions.

Missing identity returns 401 with reason: "missing_identity". A valid identity without the declared permission returns 403 with reason: "permission_denied".

The current permission matrix is documented in docs/security/permissions.md. The protected endpoint groups are:

Every protected authorization decision writes a control-plane audit entry with requestId, actor, action, object, permission, and result. Operators can inspect the bounded in-memory log with GET /api/audit/control-plane using an admin identity.

Room documents and presentation media

The presenter, host, and operator admin roles have document.present. Members and guests remain view-only for presentation state. Images are limited to 16,384 pixels per dimension and 67 million pixels; videos are limited to four hours. Configure the shared PDF/image/video upload size with DOCUMENT_UPLOAD_MAX_BYTES and page count with PDF_PRESENTATION_MAX_PAGES. Video delivery intentionally uses the bounded upload blob; adaptive streaming and transcoding are not provided.

Notes

Control Plane Permissions

Identity Sources

Control-plane protected actions accept one of two identities:

Missing identity returns 401. Invalid tokens also return 401. A valid identity without the required permission returns 403.

Room-session host permissions require a trusted role source in the signed token. Tokens minted through dev-role query mode (?role=host / requestedRole=host) are valid runtime tokens but are not trusted for control-plane host-owned actions.

Permission Matrix

Permission Endpoint group Admin token Host room session Member Guest
tenant.write POST/PATCH/DELETE /api/tenants yes no no no
room.create POST /api/rooms yes no no no
room.update PATCH /api/rooms/:roomId yes no no no
room.bind-scene-bundle POST /api/rooms/:roomId/bind-scene-bundle yes own room only no no
room.delete DELETE /api/rooms/:roomId yes no no no
asset.write POST/PATCH/DELETE /api/assets yes no no no
scene-bundle.write scene-bundle create/version/status/current endpoints yes no no no
xr-telemetry.read GET /api/rooms/:roomId/xr-telemetry yes own room only no no
audit.read GET /api/audit/control-plane yes no no no

Signed room-session roles are not operator identities. A signed room-session token with role admin is still denied for control-plane admin permissions unless the request uses the CONTROL_PLANE_ADMIN_TOKEN header.

Invite-link lifecycle endpoints are not part of the current API surface. VRATA-FEAT-014 owns private room access and invite create/revoke/use behavior; when those endpoints are added, they must declare explicit permissions and write control-plane audit entries.

Public Runtime Endpoints

Runtime boot/read endpoints stay public by design and must not mutate tenant or room state:

Audit Log

Every protected control-plane authorization decision writes a structured audit event to stdout and to a bounded in-memory inspection log.

Audit entries include:

Use x-request-id to correlate client requests with audit entries.

Source: docs/api-contracts.md, docs/security/permissions.md in vrata-labs/platform at revision 380b2e7ff1dd, license Apache-2.0. GitHub