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:
PUT /api/rooms/:roomId/presence/:participantIdDELETE /api/rooms/:roomId/presence/:participantIdPOST /api/rooms/:roomId/diagnosticsPUT /api/rooms/:roomId/xr-telemetry/:participantIdPOST /api/tokens/remote-browser-frame
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:
- tenant writes
- room create/update/delete
- host-own-room scene-bundle binding
- asset writes
- scene-bundle writes
- XR telemetry admin/host-own-room reads
- control-plane audit reads
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
GET /api/rooms/:roomId/documentsrequiresdocument.view.POST /api/rooms/:roomId/documentsrequiresdocument.upload. PDF, PNG, JPEG, WebP, MP4, and WebM uploads are validated before storage. Images use server-read dimensions; video containers are sniffed and browser-read dimensions/duration are bounded by the server.GET /api/rooms/:roomId/documents/:documentId/downloadrequiresdocument.downloadand returns an attachment.POST /api/rooms/:roomId/documents/:documentId/surfacerequiresdocument.present; only validated PDF, image, and video documents can be bound.GET /api/rooms/:roomId/documents/:documentId/presentationrequiressurface.view, returns an inline PDF only while the document is bound, and is used by in-room viewers including guests.GET /api/rooms/:roomId/documents/:documentId/contentrequiressurface.viewand returns bounded image/video bytes only while the document is bound.DELETE /api/rooms/:roomId/documents/:documentIdrequiresdocument.delete, removes matching active PDF/image/video objects through the internal room-state API, deletes the blob, and soft-deletes metadata.
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
- Room session tokens are signed with
STATE_TOKEN_SECRETand expire through theexpclaim. - Dev role escalation through
requestedRoleis allowed only when the dev-role query mode is enabled; production-safe configs disable it. schemaVersionmust remain explicit in every manifest.- Runtime should degrade gracefully if media or XR features are unavailable.
Control Plane Permissions
Identity Sources
Control-plane protected actions accept one of two identities:
x-vrata-admin-token: <CONTROL_PLANE_ADMIN_TOKEN>for self-host/operator administration.Authorization: Bearer <room-session-token>for signed room-session actors issued byPOST /api/tokens/state.
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:
GET /health,GET /health/live,GET /health/ready,GET /metrics- runtime and control-plane static assets
GET /api/templatesGET /api/assetsGET /api/tenantsGET /api/roomsGET /api/rooms/:roomIdGET /api/rooms/:roomId/manifestGET /api/rooms/:roomId/spacesGET /api/rooms/:roomId/presenceGET /api/rooms/:roomId/diagnostics- scene-bundle list/read endpoints
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:
requestIdactionpermissionobject.typeand optionalobject.idresult(allowedordenied)reasonfor denied requestsactorwithout raw tokens or secrets
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