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.
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
| POST | /v1/rooms/{room_id}/attachments/init | Begin an upload |
| POST | /v1/rooms/{room_id}/attachments/{attachment_id}/complete | Finish and verify |
| POST | /v1/rooms/{room_id}/attachments/init-multipart | Begin a multipart upload |
| POST | /v1/rooms/{room_id}/attachments/{attachment_id}/complete-multipart | Finish multipart |
| GET | /v1/rooms/{room_id}/attachments/{attachment_id}/download | Presigned download |
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
codeandmessage— see Errors & rate limits. /v1is additive-only, with a 90-day deprecation notice signalled byDeprecationandSunsetheaders.- Writes that can be retried take an idempotency key (
client_message_idfor messages,external_reffor rooms). - Unknown fields are rejected rather than ignored — sending
tenant_idin a body on the server plane is a422, 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.
/openapi.json and
generate a typed client. See
WebSocket protocol for the socket frames.
/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 →