Skip to main content

Channels REST API

Two endpoints — read peers in a channel, or publish to a channel from your backend.

GET /v1/channels/:id/peers

List peers currently subscribed to a channel.

GET https://rms.metered.ca/v1/channels/app_abc/room-1/peers
Authorization: Bearer sk_live_xxx...

The channel name goes in the URL path. URL-encode it if it contains / or other reserved characters — app_abc/room-1app_abc%2Froom-1.

Required permissions

The sk_ key must:

  • Have presence in its actions
  • Have the channel match its channelPatterns

Response — 200 OK

{
"channel": "app_abc/room-1",
"peers": [
{ "peerId": "u_alice_123", "metadata": { "userId": "u_alice_123", "username": "Alice" } },
{ "peerId": "u_bob_456", "metadata": { "userId": "u_bob_456", "username": "Bob" } },
{ "peerId": "u_carol_789" }
]
}
FieldTypeNotes
channelstringEchoes the requested channel.
peersPresencePeer[]Each entry: { peerId, metadata? }. Same shape as presence.joined entries. metadata is omitted for peers whose JWT didn't set peerMetadata. May be empty if no one's in the channel.

Errors

HTTPerrorWhen
400invalid_requestChannel name empty or malformed
400channel_reservedChannel uses a _metered/ / _internal/ / _system/ prefix
403action_not_permittedsk_ lacks presence action
403channel_not_authorizedChannel is outside sk_'s channelPatterns

Example

curl -G "https://rms.metered.ca/v1/channels/app_abc%2Froom-1/peers" \
-H "Authorization: Bearer sk_live_xxx..."

POST /v1/channels/:id/publish

Publish to a channel from your backend, without opening a WebSocket connection.

POST https://rms.metered.ca/v1/channels/app_abc/room-1/publish
Authorization: Bearer sk_live_xxx...
Content-Type: application/json

Required permissions

  • publish in the sk_'s actions
  • Channel must match the sk_'s channelPatterns

Request body

{
"data": { "anything": "json-serializable" },
"from": "system",
"peerMetadata": {
"kind": "moderation-bot",
"displayName": "System"
}
}
FieldRequiredTypeNotes
dataanyAny JSON value. Capped at 32 KB encoded (tighter than the WS layer's 64 KB — REST commands shouldn't carry kilobytes).
fromstringOrigin string surfaced to subscribers as the from field. Defaults to "server". Same charset/length rules as a peerId (printable ASCII, ≤128 chars).
peerMetadataobjectIdentity-bag stamped onto opt-in subscribers' messages under fromMetadata. Same semantics as a real peer's peerMetadata JWT claim, but supplied per-request because REST publishes have no JWT. Cap: 4 KB serialized.

Response — 200 OK

{
"channel": "app_abc/room-1",
"delivered": 3
}

delivered is the count of subscribers that received the message on this instance.

What subscribers see

Each subscriber receives a normal message event:

{
"type": "message",
"channel": "app_abc/room-1",
"from": "system",
"data": { "anything": "json-serializable" }
}

Subscribers who subscribed with includeSenderMetadata: true get fromMetadata only if the REST caller included a peerMetadata body field. If you want the system / orchestrator's identity stamped on the message (e.g., to render a "🤖 System" avatar in chat), pass peerMetadata in the request body.

Errors

HTTPerrorWhen
400invalid_requestMissing data field, malformed from
400channel_reservedReserved prefix
403action_not_permittedsk_ lacks publish action
403channel_not_authorizedChannel outside sk_'s patterns
413invalid_requestdata serializes to > 32 KB

Use cases for server-side publish

PatternExample
AI agent fan-outA workflow service emits a status update to all agents on workflows/abc/status
Chat moderation broadcast"An admin has joined" / "this room is being recorded"
IoT command-and-controlBackend issues { cmd: "reboot" } to fleets/abc/commands; every device subscribed to that fleet receives it
Webhook → realtime relayYour Stripe webhook handler publishes a payment-succeeded event to org-xyz/billing-events

Example — curl

curl -X POST "https://rms.metered.ca/v1/channels/app_abc%2Froom-1/publish" \
-H "Authorization: Bearer sk_live_xxx..." \
-H "Content-Type: application/json" \
-d '{
"data": { "text": "System: recording started" },
"from": "system"
}'

Example — Node.js

moderation.js
async function broadcastModerationMessage(channel, text) {
const resp = await fetch(
`https://rms.metered.ca/v1/channels/${encodeURIComponent(channel)}/publish`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.METERED_REALTIME_SK}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
data: { kind: "moderation-notice", text },
from: "moderator-bot",
}),
},
);
const result = await resp.json();
console.log(`delivered to ${result.delivered} subscribers`);
}