Docs

REST API

The realtime REST API for publishing, history, presence, and token minting from any language.

Everything that doesn’t need a live connection is a plain HTTPS call to https://realtime.foony.io. Use the REST API from serverless functions, cron jobs, or any language without an SDK. For live subscriptions you need the WebSocket, see Protocol.

Authentication

Every endpoint except /time accepts either scheme:

  • Basic: username is the key name (myapp.kid_a1b2c3), password is the key secret. Key-authenticated callers are trusted backends and may publish as any clientId.
  • Bearer: Authorization: Bearer <token> with a client JWT. Token callers may only publish as their own clientId.

Requests without an Authorization header fail with 40101.

Publish

POST /channels/{channel}/messages

The body is one message object, or an array to publish an atomic batch:

{ "name": "chat", "data": { "text": "hello" } }
Field Type Meaning
name string Message name subscribers filter on
data any JSON The payload
clientId string? Who the message is from. Key auth may set any value
id string? Idempotency id, up to 64 characters. Single-message publishes only
ephemeral boolean? Skip storage, deliver live only

Responds 201 with { "channel", "messageId", "serial" }. serial is absent for ephemeral and retained-last publishes, which are unsequenced. An array must share one clientId and one ephemeral value across its members, and subscribers receive the members as individual messages. The request body is capped at 2 MB, and each message at your plan’s message size limit.

History

GET /channels/{channel}/messages?limit=100&direction=backwards

Requires the history capability. Returns a JSON array of messages, each with id, name, data, timestamp, clientId, encoding, and serial.

Param Default Meaning
limit 100 Page size, up to 10,000
direction backwards backwards is newest-first, forwards oldest-first
before Exclusive serial cursor, returns messages with a lower serial

When older messages remain, the response carries a Link: <url>; rel="next" header with the next page’s URL, so paging is a loop of following Link until it disappears. Whether a channel has history at all depends on its channel rule.

Presence

GET /channels/{channel}/presence?clientId=user-123

Requires the subscribe capability. Returns the current members as a JSON array of { "clientId", "connectionId", "action": "present", "data", "timestamp" }. Filter with clientId, connectionId, and limit.

Mint a token

POST /keys/{keyName}/requestToken

Basic auth with the named key is required. The body takes clientId (required), ttl in milliseconds, and a capability object:

{ "clientId": "user-123", "ttl": 3600000, "capability": { "chat:*": ["subscribe", "publish"] } }

Responds 200 with token details: { "token", "keyName", "issued", "expires", "clientId", "capability" }. The requested capability is intersected with the key’s, as described in Authentication and capabilities.

POST /auth/token is the same minting with a smaller answer: it responds with the bare signed JWT as text/plain, which is convenient when your backend just relays the token to a browser.

Server time

GET /time

No auth. Responds [1720500000000], the server time in milliseconds, for clock-skew checks before minting tokens locally.

Errors and CORS

Errors share the envelope and code table in Errors. CORS allows any origin with the Authorization header, so the REST API is callable from browsers too. Remember that anything shipped to a browser should authenticate with a token, never a key.