Reference

API reference

The complete shipped surface — 114 HTTP operations across five planes, plus two WebSocket channels documented separately. Grouped by the credential each plane takes, because that is the thing most likely to trip you up. Every path below links to a worked example.

The examples are recordings, not illustrations. All 114 were captured by driving a throwaway tenant through the real API, then redacting the result — so a shape you copy from here is a shape the server actually returned. 112 came from the production deployment; the remaining 2 are marked where they differ.
ak_ server key

Customer server API

23 operations

room-scoped session token

Client API

30 operations

du_ directory session — or none for OTP

Directory (end-user) API

35 operations

as_ account session

Account API

11 operations

HMAC-signed

Operator control plane

12 operations

none / internal

Operational

3 operations

Customer server API #

Credential: ak_ server key

Your backend calls these. Tenant comes from the key — a tenant_id in a body is rejected. Scope-gated.

Rooms & members

POST /v1/server/rooms Provision a room (idempotent on external_ref)
GET /v1/server/rooms/{room_id} Fetch a room
DELETE /v1/server/rooms/{room_id} Erase a room — rows and blobs
POST /v1/server/rooms/{room_id}/members Add or restore a member (409 once the room holds 5,000)
DELETE /v1/server/rooms/{room_id}/members/{principal_id} Remove a member
PUT /v1/server/rooms/{room_id}/members/{principal_id}/mute Silence the room's push for a member — 8h, 1w or always (members:write)
DELETE /v1/server/rooms/{room_id}/members/{principal_id}/mute Unmute (idempotent)
PUT /v1/server/rooms/{room_id}/members/{principal_id}/preferences Pin which simulcast layer of a publisher's track the member receives — low, medium, high or auto (members:write)
GET /v1/server/rooms/{room_id}/members/{principal_id}/preferences What is pinned for the member, and whether they are in the call (rooms:read)
PUT /v1/server/rooms/{room_id}/members/{principal_id}/rtc-policy Restrict what the member may publish and receive, which peers it sees, how many devices it may bring, and its chat and attachment rights — revisioned, live within about a second (members:write)
GET /v1/server/rooms/{room_id}/members/{principal_id}/rtc-policy The member's stored policy and how far it has reached their live connections, per component (rooms:read)
DELETE /v1/server/rooms/{room_id}/members/{principal_id}/rtc-policy Back to role-only rights — a write in the same revision sequence, ?revision= required (members:write)
GET /v1/server/rooms/{room_id}/rtc/forwarding Who receives whose media in the call right now, with the simulcast layer (rooms:read)

Tokens, principals, messages

POST /v1/server/session-tokens Mint a room-scoped client token
POST /v1/server/principals Register a principal (bot/service identity)
POST /v1/server/principals/{principal_id}/erase GDPR erase a principal
POST /v1/server/rooms/{room_id}/messages Send as a principal (server-side)

Usage & webhooks

GET /v1/server/usage Metered usage for a window
POST /v1/server/webhooks Create an endpoint subscription
GET /v1/server/webhooks List endpoints
DELETE /v1/server/webhooks/{webhook_id} Delete an endpoint
GET /v1/server/webhooks/{webhook_id}/deliveries Delivery audit log
POST /v1/server/webhooks/{webhook_id}/deliveries/{delivery_id}/replay Replay a delivery

Client API #

Credential: room-scoped session token

What your app calls directly. Every path is authorised against the token’s room and principal.

Messages

POST /v1/rooms/{room_id}/messages Send
GET /v1/rooms/{room_id}/messages History (after_seq / before_seq / limit)
PATCH /v1/rooms/{room_id}/messages/{message_id} Edit (author only)
DELETE /v1/rooms/{room_id}/messages/{message_id} Soft delete
GET /v1/rooms/{room_id}/messages/search Full-text search within the room
POST /v1/rooms/{room_id}/messages/{message_id}/reactions Add a reaction
DELETE /v1/rooms/{room_id}/messages/{message_id}/reactions Remove a reaction

Receipts & presence

POST /v1/rooms/{room_id}/messages/{message_id}/delivered Delivery receipt
POST /v1/rooms/{room_id}/messages/{message_id}/read Read receipt (advances cursor)
GET /v1/rooms/{room_id}/unread Unread count and cursors
GET /v1/rooms/{room_id}/participants Room participants

Attachments

Push

POST /v1/push/subscriptions Register a Web Push subscription
DELETE /v1/push/subscriptions Unregister one
GET /v1/push/vapid-public-key VAPID public key

End-to-end encryption

POST /v1/e2ee/keys/upload Publish device + one-time keys
POST /v1/e2ee/keys/query Query devices for principals
POST /v1/e2ee/keys/claim Claim a one-time key (atomic)
POST /v1/e2ee/todevice Send a to-device message
GET /v1/e2ee/todevice Fetch queued to-device messages
POST /v1/keypackages Upload MLS key packages
GET /v1/keypackages/count Remaining key-package count
GET /v1/rooms/{room_id}/keypackages/{principal_id} Consume one key package
PUT /v1/rooms/{room_id}/mls/group-info Publish the MLS GroupInfo + ratchet tree
GET /v1/rooms/{room_id}/mls/group-info Latest GroupInfo + ratchet tree

Deprecated

GET /v1/rooms/{room_id}/recordings Sunset 2027-01-05 — camera feeds were withdrawn; always an empty list, with Deprecation and Sunset headers

Directory (end-user) API #

Credential: du_ directory session — or none for OTP

The turnkey identity plane. Your client calls these directly.

Sign-in & profile

POST /v1/directory/otp/request Request a one-time code (unauthenticated)
POST /v1/directory/otp/verify Verify and receive a du_ session
POST /v1/directory/logout Revoke this session (or every device)
GET /v1/directory/me Current user: id, phone, display_name, about, avatar_version
PATCH /v1/directory/me Update display name and/or about line
POST /v1/directory/me/avatar/init Start a profile-photo upload (JPEG/PNG/WebP/GIF, ≤ 2 MiB)
POST /v1/directory/me/avatar/complete Make the uploaded bytes your photo — verified before it answers
DELETE /v1/directory/me/avatar Remove your profile photo
GET /v1/directory/users/{user_id}/avatar A user's photo: 302 to the bytes, ETag = avatar_version

Contacts & conversations

POST /v1/directory/contacts/lookup Which phone numbers are registered (≤500)
POST /v1/directory/conversations/direct Open a 1:1 (idempotent; blocked across a block)
POST /v1/directory/conversations/group Create a group
GET /v1/directory/conversations List conversations
GET /v1/directory/conversations/{room_id}/token Mint a room token
GET /v1/directory/inbox Chat list: peer, last message, unread, muted_until

Group management

GET /v1/directory/conversations/{room_id} A conversation as a member sees it: name, description, icon, size
PATCH /v1/directory/conversations/{room_id} Owner: rename a group, set its description or icon
GET /v1/directory/conversations/{room_id}/members Members, owners first
POST /v1/directory/conversations/{room_id}/members Owner: add up to 256 members (409 once the room holds 5,000)
PUT /v1/directory/conversations/{room_id}/members/{user_id} Owner: make a member an owner, or an owner a member
DELETE /v1/directory/conversations/{room_id}/members/{user_id} Owner: remove a member
POST /v1/directory/conversations/{room_id}/leave Leave a group; a lone owner's seat passes to the longest-standing member

Invite links

POST /v1/directory/conversations/{room_id}/invites Owner: create a link — the token is shown once
GET /v1/directory/conversations/{room_id}/invites Owner: live links, without tokens
DELETE /v1/directory/conversations/{room_id}/invites/{invite_id} Owner: revoke a link
POST /v1/directory/invites/preview What a link leads to, before joining (token in the body)
POST /v1/directory/invites/join Join a group through a link

Mute & block

PUT /v1/directory/conversations/{room_id}/mute Silence this conversation's push for yourself — 8h, 1w or always
DELETE /v1/directory/conversations/{room_id}/mute Unmute
GET /v1/directory/blocks Your block list
PUT /v1/directory/blocks/{user_id} Block a user — the direct chat freezes for both
DELETE /v1/directory/blocks/{user_id} Unblock

Devices

POST /v1/directory/devices Register a push token
GET /v1/directory/devices List devices
DELETE /v1/directory/devices Unregister a push token

Account API #

Credential: as_ account session

Self-serve control surface. Dashboard/admin credential — never ship it in a product build.

Account

POST /v1/account/signup Create an account and its tenant
POST /v1/account/login Log in
POST /v1/account/logout Invalidate the session

API keys

GET /v1/account/api-keys List keys
POST /v1/account/api-keys Create a key (secret shown once)
POST /v1/account/api-keys/{key_id}/rotate Rotate atomically
DELETE /v1/account/api-keys/{key_id} Revoke

Directory apps

POST /v1/account/directory-apps Provision an app (webhook or SMS provider)
GET /v1/account/directory-apps List apps and which push transports are configured
DELETE /v1/account/directory-apps/{app_id} Delete an app
PUT /v1/account/directory-apps/{app_id}/push Set FCM/APNs credentials (write-only)

Operator control plane #

Credential: HMAC-signed

Deployment-wide, for whoever runs the infrastructure. As a customer you should never need these.

Rooms, members, tokens

POST /internal/v1/rooms Create a room
GET /internal/v1/rooms Resolve by external_ref
PATCH /internal/v1/rooms/{room_id} Patch status / expiry
GET /internal/v1/rooms/{room_id}/status Members, presence, live call roster
POST /internal/v1/rooms/{room_id}/members Add a member
DELETE /internal/v1/rooms/{room_id}/members/{principal_id} Remove a member
POST /internal/v1/session-tokens Mint a token
GET /internal/v1/principals/{principal_id}/unread Per-room unread for a principal

Provisioning & lifecycle

POST /internal/v1/api-keys Issue a customer key
DELETE /internal/v1/api-keys/{key_id} Revoke a key
PUT /internal/v1/plans/{name} Create/update a rate + quota plan
DELETE /internal/v1/tenants/{tenant_id} Offboard a tenant — revokes the account and its logins, keys and directory at once, then erases rooms, blobs, webhooks and usage

Operational #

Credential: none / internal

Health and telemetry. Metrics are not publicly exposed.

Health

GET /health/live Liveness
GET /health/ready Readiness (dependencies)
GET /metrics Prometheus metrics — internal only

Conventions #

  • All bodies are JSON; all timestamps are RFC 3339 UTC.
  • Errors carry code and message — see Errors & rate limits.
  • /v1 is additive-only, with a 90-day deprecation notice signalled by Deprecation and Sunset headers.
  • Writes that can be retried take an idempotency key (client_message_id for messages, external_ref for rooms).
  • Unknown fields are rejected rather than ignored — sending tenant_id in a body on the server plane is a 422, because the tenant comes from the key.
  • E2EE endpoints require a token minted with an explicit device_id. Without one they refuse, which is easy to misread as a permissions problem. See End-to-end encryption.
The machine-readable OpenAPI document is the source of truth and is kept in lockstep with the router by a parity check that fails the build — an endpoint cannot ship undocumented, and a documented endpoint cannot vanish. Download it at /openapi.json and generate a typed client. See WebSocket protocol for the socket frames.
Try it in Postman. Import /ollacore.postman_collection.json (File → Import → Link). It holds every customer-facing operation with its recorded example, plus a Quickstart folder that runs top to bottom — sign up, create a test key, provision a room, mint a token, send. Details →