Reference

Changelog

Changes you can see from a client, newest first. Each entry that needs something from you says what. Internal work, and anything not yet running in production, is not listed.

/v1 only grows: new fields and endpoints are added, nothing is removed without 90 days' notice through Deprecation and Sunset headers. Build clients that ignore fields they do not know. Labels: Added Changed (behaviour you may need to handle) Fixed Security.

#

  • Fixed
    The published OpenAPI document now matches the traffic it describes. The server did not change; the spec was wrong in these places:
    • Completing an upload returns status uploaded (or ready); the spec listed ready, pending, rejected.
    • Completing a multipart upload returns the same two fields as a single-part completion, not a full attachment.
    • A key created on the account plane has no tenant_id in its response; only the operator plane returns one.
    • Rooms carry e2ee; registered devices carry a nullable app_id; downloads include poster_url; attachment init accepts poster_of.
    • DELETE /v1/push/subscriptions returns {"removed": …}, not unsubscribed.
    • GET /v1/push/vapid-public-key needs no credential.
    • Minting a token declares the 400 (unknown role) and 403 (role above the member's).
    • PATCH /internal/v1/rooms/{room_id} declares its body (status, expires_at).
    What to do: Regenerate any client built from an earlier openapi.json. A hand-written client should accept uploaded as an upload status and must not require tenant_id on an account-plane key.

#

  • Security
    A minted session token can no longer carry more rights than the member has. The requested role must be owner, member or viewer (400 otherwise) and at or below the member's stored role (403 otherwise). Before, a higher role was granted.
    What to do: If you mint with an explicit role, check it never exceeds the member's.
  • Changed
    Both WebSockets re-check membership every 30 seconds. A socket of a principal removed from the room closes with 4403.
    What to do: Treat 4403 as final: do not reconnect.
  • Changed
    A Web Push endpoint must be https with a public host. Cleartext, and loopback, private or link-local IP addresses, are refused with 400.
  • Changed
    FCM credentials must use Google's own token_uri; anything else is refused with 400 when you store them.
  • Security
    Two concurrent verifies of the same OTP can no longer both succeed: exactly one wins.
  • Fixed
    One slow Web Push service no longer delays every other push: each delivery has a 10-second deadline.

#

  • Added
    POST /v1/directory/logout revokes the presented directory session, or every session of that user with {"all_devices": true}.
    What to do: Call it on sign-out and for "log out everywhere".
  • Changed
    Rate-limited responses (429) carry Retry-After, and the message names which limit was hit. otp/request has four: per app, per phone number, per IP and a resend cooldown.
    What to do: Back off by Retry-After rather than a fixed delay.
  • Changed
    POST /v1/directory/contacts/lookup is limited to 20 calls per session per minute.
  • Fixed
    Erasing a principal now also removes their directory identity: phone number, display name, sessions, devices and OTP rows. Erasing a tenant also removes its account.
  • Changed
    On Android, an incoming-call push is data-only, so your app can show a full-screen call. A message push still carries a notification.
    What to do: Implement onMessageReceived, or calls show nothing on Android.
  • Changed
    A ring expires after 45 seconds, so a device that was offline does not ring late, and a cancel replaces its ring.
  • Fixed
    A call ending no longer reaches Web Push as new_message. Web Push always carries type, room_id and, for calls, call_id.

#

  • Added
    Native push: the backend sends FCM and APNs notifications itself, with your own credentials, set once per directory app (PUT /v1/account/directory-apps/{app_id}/push).
  • Changed
    Pushes identify themselves, in FCM data and beside APNs aps: type and room_id always; call_id on the two call types; sender except on a message in an E2EE room. APNs pushes now play a sound, and E2EE rooms set mutable-content so a Notification Service Extension can decrypt.
    What to do: Route on type and room_id instead of parsing titles, and treat call_id and sender as optional.
  • Changed
    A ring is deduplicated by call_id, so a call is no longer swallowed by a message sent just before it. When the caller hangs up, a silent call.ended push goes to the members who were rung.
    What to do: Cancel the notification for that call_id when call.ended arrives.
  • Added
    Every operation has a recorded request and response in api-examples.json.

#

  • Added
    The turnkey directory: phone-OTP sign-up, profile, contact lookup, direct and group conversations, room tokens and push devices, under /v1/directory/*. Directory apps are created on the account plane and deliver codes through your webhook or your own Twilio, Plivo or MSG91 account.
  • Added
    GET /v1/directory/inbox: the chat list in one call, with names, last-message previews and unread counts.
  • Added
    call.started and call.ended events on the chat socket, to webhooks and as push, so an offline member can be rung.

#

  • Added
    Self-serve accounts: /v1/account/signup, login and logout, and API-key management (create, rotate, delete).
  • Changed
    A test key now works in its own data namespace and never sees live data. Anything a test key created before this is no longer visible to it. :: is reserved in tenant ids.
    What to do: Recreate test fixtures with your test key.
  • Added
    The /v1 stability policy: changes are additive, and a removal is announced 90 days ahead with Deprecation and Sunset headers.
  • Added
    Optional scheduled retention per plan: messages older than the plan's window are deleted, together with their attachments and stored files. Off unless set for your plan.

#

  • Added
    Erasure: DELETE /v1/server/rooms/{room_id} (scope rooms:delete) and POST /v1/server/principals/{principal_id}/erase (scope data:erase) delete rows and files.
  • Added
    Plans: per-key rate tiers, and optional monthly quotas on messages sent and attachment bytes. Over a quota the call fails with 402 quota_exceeded.
    What to do: Do not retry a 402 blindly: it lasts until the quota resets at the start of the month or the plan changes. Retry after either.
  • Changed
    Push, key-package and /v1/e2ee/* endpoints re-check that the caller is still a room member: a removed member gets 403 at once, not at token expiry. Push unsubscribe stays open to them.
  • Security
    Push subscriptions belong to the principal that created them; another principal can no longer overwrite or delete them.

#

  • Added
    Usage metering: GET /v1/server/usage (scope usage:read) sums message.sent and attachment.bytes over a window.

#

  • Added
    The customer server API, /v1/server/*, with tenant-pinned, scoped ak_ keys.
  • Added
    Webhooks: /v1/server/webhooks with signed delivery, retries, a delivery log and replay.