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-1 → app_abc%2Froom-1.
Required permissions
The sk_ key must:
- Have
presencein itsactions - 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" }
]
}
| Field | Type | Notes |
|---|---|---|
channel | string | Echoes the requested channel. |
peers | PresencePeer[] | 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
| HTTP | error | When |
|---|---|---|
| 400 | invalid_request | Channel name empty or malformed |
| 400 | channel_reserved | Channel uses a _metered/ / _internal/ / _system/ prefix |
| 403 | action_not_permitted | sk_ lacks presence action |
| 403 | channel_not_authorized | Channel 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
publishin the sk_'sactions- Channel must match the sk_'s
channelPatterns
Request body
{
"data": { "anything": "json-serializable" },
"from": "system",
"peerMetadata": {
"kind": "moderation-bot",
"displayName": "System"
}
}
| Field | Required | Type | Notes |
|---|---|---|---|
data | ✅ | any | Any JSON value. Capped at 32 KB encoded (tighter than the WS layer's 64 KB — REST commands shouldn't carry kilobytes). |
from | string | Origin string surfaced to subscribers as the from field. Defaults to "server". Same charset/length rules as a peerId (printable ASCII, ≤128 chars). | |
peerMetadata | object | Identity-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
| HTTP | error | When |
|---|---|---|
| 400 | invalid_request | Missing data field, malformed from |
| 400 | channel_reserved | Reserved prefix |
| 403 | action_not_permitted | sk_ lacks publish action |
| 403 | channel_not_authorized | Channel outside sk_'s patterns |
| 413 | invalid_request | data serializes to > 32 KB |
Use cases for server-side publish
| Pattern | Example |
|---|---|
| AI agent fan-out | A 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-control | Backend issues { cmd: "reboot" } to fleets/abc/commands; every device subscribed to that fleet receives it |
| Webhook → realtime relay | Your 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
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`);
}