Reference · API reference
HTTP and stream protocol
Build another client against the room API and multiplexed WebSocket protocol version 2.
Use the shared SDK for a TypeScript client. This page is for another language, a diagnostic tool, or an integration that needs an HTTP detail the SDK does not expose. The HTTP server and multiplexer are the current contract.
Authenticate HTTP requests
Room APIs use Authorization: Bearer <room-token>. JSON bodies use Content-Type: application/json. POST /v1/rooms instead checks the configured admin token; if the standalone server has no admin token configured, room creation is unguarded. Configure it when you expose that operation.
Create a room:
POST /v1/rooms
Authorization: Bearer <server-admin-token>
Content-Type: application/json
{"scenario_id":"support","owner_role":"owner"}
The 201 response contains room, participant, and the newly issued token. Invite a scoped participant before giving a browser access. IDs in paths must be URL-encoded. The endpoint table below uses {room} and other braces as placeholders, not literal URL segments.
Core room endpoints
All paths below begin with /v1/rooms/{room}. Permissions and response data are filtered for the authenticated participant.
| Method and suffix | Body / behavior |
|---|---|
GET room root |
Authorized snapshot |
DELETE room root |
Close room and initiate native provider cleanup |
GET /events?after_seq=42 |
One authorized history page; read X-RMC-Cursor |
POST /events |
Publication: client_event_id, type, payload, optional channel_id, causation_id, correlation_id, visibility |
POST /channels |
Channel definition: optional id, required type, direction, optional media_binding |
POST /participants |
{role, grants}; returns participant and token |
DELETE /participants/{participant} |
Revoke participant and initiate native provider cleanup |
PATCH /state/{key} |
{patch, expected_version?, visibility?, client_event_id} |
POST /artifacts |
{id, type, content, status, expected_version?, visibility?, client_event_id} |
POST /delegations |
Snake_case DelegationRequest |
POST /delegations/{delegation}/cancel |
Cancel requested work |
POST /ask |
{question, idempotency_key} |
POST /tools/{tool}/confirm |
{arguments}; returns confirmation_id |
POST /tools/{tool}/invoke |
{arguments, confirmation_id?} |
GET /topology |
Basic runtime inventory; requires room:inspect on * |
The event log is ordered by room sequence, not timestamps. HTTP history scans at most 1,000 records per request before authorization filtering; an empty visible page can have a newer X-RMC-Cursor. There is no HTTP selector query API at this revision. Use the pagination helper.
For cross-origin browser history access, the current CORS handler does not expose X-RMC-Cursor. Use the SDK stream, a same-origin proxy, or a server-side history reader. A custom cross-origin history client needs that header explicitly exposed by its host configuration/code.
Open a version 2 stream
Connect to ws://host/v1/rooms/{room}/stream, or wss:// for TLS. Send this first JSON frame within the server's five-second authentication deadline:
{"op":"authenticate","protocol_version":2,"token":"<room-token>"}
The server replies:
{"op":"authenticated","protocol_version":2}
There is no token query parameter. Omitting version 2, using an unsupported version, or failing authentication closes the connection with policy-violation code 1008. The event envelope's api_version is a separate version identifier.
Subscribe, receive, and unsubscribe
{"op":"subscribe","request_id":"request-1","subscription_id":"chat-view","after_seq":40,"selector":{"channel_ids":["chat"],"event_types":["text.message.*"]}}
Selector keys on the wire are channel_ids, channel_types, and event_types. Their matching rules are described in delivery.
The server acknowledges the subscription, then sends event and checkpoint frames carrying its ID. This example illustrates a single selected event followed by a checkpoint that advances past other room records:
{"op":"subscribed","request_id":"request-1","subscription_id":"chat-view"}
{"op":"event","subscription_id":"chat-view","cursor":41,"event":{"api_version":"rmc.dev/v1alpha1","id":"event-example","room_id":"room-example","seq":41,"type":"text.message.committed","channel_id":"chat","actor_id":"participant-example","occurred_at":"2026-09-09T00:00:00Z","visibility":{"audience":"room"},"content_type":"application/json","payload":{"text":"Hello"}}}
{"op":"checkpoint","subscription_id":"chat-view","cursor":45}
These IDs and times are illustrative. The actual server assigns them. Process frames serially per subscription and advance local progress only after successful processing. Checkpoints never overtake earlier events on that subscription. Initial checkpoints can arrive even when no visible event exists.
{"op":"unsubscribe","request_id":"request-2","subscription_id":"chat-view"}
The reply is unsubscribed with the same request and subscription IDs. An ID can be reused after unsubscribe; an implementation must fence queued frames from the old subscription generation. The shared SDK already does this with separate wire IDs. At most 64 active subscriptions are allowed on one connection, and each wire subscription ID is limited to 128 bytes.
Publish on the wire
The shared SDK publishes over HTTP. A direct protocol client can also send:
{"op":"publish","request_id":"send-1","client_event_id":"logical-send-1","channel_id":"chat","type":"text.message.committed","payload":{"text":"Hello"}}
The successful published frame echoes request_id and contains the committed event. A matching subscription can also receive that event. Do not treat the publication acknowledgment and subscription delivery as two separate business messages. Both use the same publication authority and idempotency checks as HTTP.
Errors and limits
Stream errors carry op: "error", code, and error, with a request or subscription ID when applicable. Request-local validation errors need not terminate unrelated subscriptions.
| Code | Meaning |
|---|---|
invalid_subscription |
Invalid/duplicate ID or active-subscription limit |
subscription_rejected |
Runtime could not open the requested subscription |
invalid_publication |
Required publication fields are missing |
publication_rejected |
Publication failed validation, idempotency, or another runtime condition |
not_authorized |
Current authority does not permit the operation or delivery |
subscription_closed |
Runtime delivery ended; the SDK reconnects the transport to recover |
unsupported_operation |
Unknown op |
Reauthentication with the original revoked token cannot restore access. The SDK treats authorization failure as terminal; an application must obtain valid credentials through its own auth flow.
HTTP error bodies generally contain error. Common statuses include 400 malformed input, 401 missing/invalid room authentication, 403 denied operation, 404 missing resource, 409 version/room/work conflict, 422 validation, 428 required tool confirmation, and 502 provider/capability failure. Endpoint-specific behavior matters: a 502 after revoke can follow a successful local commit with failed provider cleanup.
The current general request/frame size limit is 1 MiB. Writes have deadlines and queues are bounded; the wire is not an unlimited event buffer. Multiplex tests cover subscription isolation, ID reuse, protocol versions, impersonation attempts, and revocation. Stream tests cover replay across multiple pages.
Native media endpoints
POST /media/{adapter}/sessions returns a provider join credential. GET /media/presence reads the existing projection, and POST /media/{adapter}/presence/refresh requests a provider refresh. Scoped publishing uses a JSON publish_channel_ids array; omission and an empty array differ materially.
Voice sessions use /voice/sessions, with dedicated SDP negotiation and control handling. Sampled frames use /video/frames, a bounded in-memory ingress with its own validation. Prefer the voice SDK and existing processor client over inventing another media wire client from this overview. The media guide explains the authority and lifetime boundaries.