push.tt / Enterprise
Partly shipping

One HTTPS API, one WebSocket, and a crypto spec you can implement

Everything push.tt does is reachable from the same API our own console and Android client are built on. This page is the technical shape of it — auth, the endpoints, the voice wire format and the encryption — so you can judge feasibility before writing anything.

Partly shipping. The API is running in production today. There is no published SDK, no webhook delivery, and no hosted API reference yet — what is below is the accurate shape, and access is by asking us. Everything marked unbuilt is unbuilt.

Authentication

Username and password to a bearer token. Tokens are per device, so revoking one handset does not sign out the rest.

  • POST /api/v1/auth/register — create an account and enrol a device key
  • POST /api/v1/auth/login — returns a bearer token bound to a device
  • GET /api/v1/me — the signed-in user, their org and role
  • GET /api/v1/devices · DELETE /api/v1/devices/:id — list and revoke device keys
  • Every other call sends Authorization: Bearer <token>

The REST surface

Versioned under /api/v1. This is the same surface the console uses, which is the only durable guarantee that it keeps working.

  • ChannelsGET/POST /channels, /channels/join, /channels/:id/members, /channels/:id/grant-key
  • MessagesPOST /messages/text, /messages/media, /messages/read, GET /messages/history, DELETE /messages/:id
  • Organisation/org/users, /org/stats, /org/audit, /org/settings, /org/vault/*
  • Store/store/catalogue, /store/licences, /store/esims, /store/wallet
  • EmergencyGET /emergency/active plus raise, acknowledge and clear

Settings that change who can read what answer HTTP 428 until an admin sends an explicit acknowledgement. That is not a validation error to retry past — it is a consent gate, and your integration has to surface the warning rather than resending with a flag flipped.

The WebSocket

One socket at /ws carries both JSON control messages and binary audio. Presence, occupancy, floor control, emergencies and live voice all share it.

  • hello to authenticate the socket, then ready comes back with channels, unread counts and any active emergency
  • channel/connect and channel/disconnect — occupancy, the "N connected" count
  • talk/start → the floor is granted or talk/busy names the current speaker; talk/end closes the stream
  • status, typing, read — presence and conversation state
  • ping/pong carry timestamps, which is what the clients use to grade link quality

The voice wire format

Documented because an integration that carries audio has to get these exactly right, and they are the parts people most often assume.

  • 16 kHz mono, 20 ms frames. Capture and playback are always PCM16 — 640 bytes a frame
  • Frame header is 7 bytes, little-endian: [type u8 | streamId u32le | seq u16le]
  • The codec is negotiated per stream, not per deployment. opus/16000 is preferred and pcm16/16000 is the fallback — never assume a stream matches your own device's capability, read it from stream/start
  • Variable-length codecs are length-prefixed by the sender, u16le per frame. The server concatenates payloads and records no frame boundaries, so without that prefix a stored Opus blob is unparseable
  • The server relays frames verbatim and never transcodes an encrypted stream

The crypto spec

If you are writing a client rather than calling the API, this is the part that has to match byte for byte. Three implementations already agree — TypeScript, Kotlin and Swift — and a round-trip test is what keeps them that way.

  • X25519, HKDF-SHA256, AES-256-GCM. That is the entire permitted set, chosen because all three are native on every platform we target
  • Sealed box layout is ephPub(32) ‖ nonce(12) ‖ ciphertext‖tag. Note CryptoKit's .combined includes the nonce and is not this layout
  • Per-frame nonce is an 8-byte random stream prefix ‖ u32le seq, with the prefix travelling in talk/start. Never reuse a (key, nonce) pair — the one GCM rule that does not bend
  • Channel keys are sealed per device, so a user on a phone and a console has two independent grants

What does not exist yet

Listed so you can plan around it rather than discover it.

  • No published SDK. The Android voice pipeline is already a separate module from the UI, so extraction is the work — but it has not been done
  • No webhooks. Poll the API or hold the socket open
  • No hosted API reference. The types are the contract — packages/protocol is shared TypeScript the clients build from, and we will send it
  • No sandbox environment. We provision a real test organisation instead
  • No rate-limit documentation beyond "there are rate limits"; ask and we will tell you the current numbers rather than publish ones that change

Tell us what you are building

The question that decides most of the design: does your integration need message content, or only the fact of a message? Content means holding a key and decrypting client-side, because no server-side call will ever return plaintext for an encrypted channel. Everything else is straightforward.