Documentation

Build with RTLayer

RTLayer delivers realtime messages from your servers to your users. Publish with one HTTPS request; every connected web or mobile client receives it over a websocket within milliseconds. No socket servers to build, scale or babysit.

For AI agents: llms.txt

How RTLayer works

Your server publishes over HTTPS; RTLayer pushes to every subscribed client over websockets.

RTLayer sits between your backend and your users’ apps. Your server sends a message to the HTTP API; RTLayer fans it out to every client connected to the same service over a websocket. Clients never publish: anything they need to send goes through your own backend, which keeps you in control of what is broadcast.

flow

  • HTTP API: https://api.rtlayer.com. Publish messages and create services. Authenticated with an API key.
  • Websocket: wss://ws.rtlayer.com/<orgId>/<serviceId>/. Clients connect, join channels and receive messages.
  • Dashboard: rtlayer.com/dashboard. Create services, manage API keys and teammates, try messages in the Playground and watch live analytics.
Servers run in Mumbai, India (asia-south1). From India a message typically reaches clients about 50 ms after you publish it.

Quickstart

From zero to a live message in five minutes.

  1. Sign in. The first visit to Home creates an organization, a service and an API key for you.
  2. Open your service on the Dashboard and go to API Keys. Copy the Organization ID, the Service ID and an API key.
  3. Connect a client and listen on a channel (step 3 below).
  4. Publish to that channel from your server (step 4 below).
  5. Watch it arrive. The service’s Playground tab can also send and receive test messages, with timings.

3. Receive in your app

terminal

app.js

4. Publish from your server

terminal

Keep API keys on your server. An API key can publish to your service. Never ship it in frontend or mobile code, and prefer the x-api-key header over the apiKey query parameter so it stays out of request logs.

Core concepts

Organizations, services, IDs, API keys, channels, messages and TTL.

ConceptWhat it is
OrganizationYour team’s workspace. It holds services, teammates (owner, admins, members) and org-level API keys.
ServiceOne realtime app or environment, such as "Production chat" or "Staging". Each has its own ID, API keys, settings and analytics. Messages never cross services.
Org ID and service IDPublic identifiers. Clients need both to connect, so they are safe in frontend code.
API keySecret credential for the HTTP API. A service key publishes to one service; an org key can publish to any service in the org and create services. See API keys.
ChannelA named stream inside a service, such as orders or room-42. Clients join the channels they care about; a message published to a channel reaches only those clients.
BroadcastA message published without a channel. It reaches every client connected to the service.
Personal channelEvery connection is also subscribed to a channel named after its user ID (uid). Publishing to that name reaches one user. Useful with authentication, which sets the uid.
MessageAny string or JSON value. Strings are delivered as-is; other values are sent as their JSON text, so clients JSON.parse them.
TTLOptional seconds to keep a message for a channel nobody is listening to yet. See Delivery and TTL.

Publish messages

POST /message: send a message to a channel or to everyone.

endpoint

HeaderValue
x-api-keyYour service API key (or an org key; see below). The apiKey query parameter also works, but the header wins and keeps the key out of logs.
Content-Typeapplication/json (form-encoded bodies work too)
Body fieldTypeDescription
messageany, requiredThe payload. A string is delivered as-is; objects, arrays, numbers and booleans are delivered as their JSON text. Up to 8 MB, though small messages deliver fastest.
channelstring, optionalChannel to publish to. Omit it (or send "") to broadcast to every client of the service. Any characters are allowed; colons are fine.
ttlinteger seconds, optionalKeep the message for up to this long if no client is subscribed to the channel right now. Default 0 (deliver now or drop). See Delivery and TTL.
serviceIdstringOnly with an org API key: the service to publish to. Ignored for service keys.

A successful call returns 200 with {"status":"ok"}. The response is the same whether the message reached clients, was stored for later, or found nobody listening; it is not a delivery receipt.

Every request is authenticated and rate limited per service (see Limits). For bursts, batch several updates into one message rather than sending one request per update.

Examples

terminal

Node.js SDK (optional)

The rtlayer-node package wraps the same request. A plain fetch (shown above) is just as short and also supports org keys, which the SDK can’t send serviceId for.

server.js

GET /message

For quick tests, GET /message?apiKey=…&message=…&channel=… publishes too (so does HEAD). Avoid it in production: the key and message end up in URLs and logs, and anything that prefetches the URL sends a message.

Receive messages (JavaScript)

The rtlayer-client SDK for browsers, React Native and Node 22+.

terminal

RTLayer(orgId, serviceId, token?, retryInterval?) returns a client that connects at once and reconnects every retryInterval ms (default 5000) when the connection drops, re-joining your channels. It needs a global WebSocket: browsers, React Native and Node 22+ have one.

app.js

Client API

CallWhat it does
RTLayer(orgId, serviceId, token?, retryInterval = 5000)Returns the client for that service. Calling it again with the same IDs returns the same client.
on(channel, handler)Joins channel and calls handler(message) for each message on it. Returns { remove() }.
on("*", handler)Every frame the connection receives, without joining anything. See the table below for what it gets.
removeListener(channel, handler)Removes a handler; leaves the channel once no handlers remain.
subscribe(channel) / unsubscribe(channel)Join or leave a channel without a handler (re-joined after reconnects).
close()Closes the socket. Note: the client then reconnects after retryInterval; reload or navigate away to stop it for good.
HandlerChannel messageBroadcastSystem frames
on("orders")the payload string——
on("*")the payload string (no channel name)the raw frame: {"channel":"","message":"…"}raw: {"status":"connected"} etc.
Avoid on("message"), on("open") and on("close"): besides the event, they also join channels literally named "message", "open" and "close". Use on("*") for everything, and named channels for your data.

Broadcasts

Broadcasts (messages published without a channel) reach * handlers as the raw frame. Unwrap them like this:

app.js

React

useOrders.js

Websocket protocol (any language)

Connect, join channels and read frames without an SDK.

The SDK is a thin layer over a small JSON protocol, so any language with a websocket client works.

StepDetails
Connectwss://ws.rtlayer.com/<orgId>/<serviceId>/, plus ?token=<jwt> when the service requires authentication. Servers may send Authorization: Bearer <jwt> instead.
On connectThe server sends {"status":"connected"}. You are already subscribed to broadcasts and to your personal channel (your uid).
Join a channelSend {"action":"join","id":"orders"}.
Leave a channelSend {"action":"leave","id":"orders"}.
ReceiveEach message arrives as {"channel":"orders","message":"<payload>"}. channel is "" for broadcasts; message is always a string (JSON text for published objects).
Errors{"status":"error","message":"Invalid JSON"} for frames the server can’t read; {"status":"error","message":"You are not allowed to join <id>"} when your token doesn’t allow the channel. The connection stays open.
Keep-aliveThe server pings every connection and closes it after 30 s without a pong. Browsers and standard libraries answer pings automatically.
ReconnectIf the socket closes, reconnect and send your joins again; channel membership doesn’t survive a reconnect.

Examples

app.js

Authenticate clients

Require signed tokens so only your users connect, and control which channels they join.

By default anyone who knows your org and service IDs can connect and join any channel. That’s fine for public data. For anything private, turn on authentication: clients must then present a JWT that your server signs with the service’s secret.

  1. In the dashboard, open the service’s Settings tab, switch on Require authentication and set a long random secret (owners and admins only).
  2. On your server, after you have authenticated the user, sign a token with that secret (HS256) carrying the claims below.
  3. Give the token to the client, which connects with it.
ClaimRequiredMeaning
orgIdyesMust equal the org ID in the connection URL.
serviceIdyesMust equal the service ID in the connection URL.
uidyesYour user’s ID. It becomes the connection’s personal channel.
room.allowedyes, in practiceChannel patterns this user may join, e.g. ["orders", "room-*"]. * matches anything. If it’s missing, every join is refused.
room.deniednoPatterns to refuse even if allowed; deny wins.
exprecommendedExpiry (standard JWT claim). Expired tokens are rejected.

token.js

  • A missing, invalid or expired token, or IDs that don’t match the URL, fail the websocket handshake (HTTP 401). Browsers report this only as a failed connection.
  • A join the token doesn’t allow gets an error frame; the connection stays open.
  • Patterns are matched against the whole channel name. Stick to letters, digits, -, _ and : in channel names: other symbols such as . have special meaning in patterns.
  • Turning authentication on or changing the secret can take up to 10 minutes to apply to new connections.
  • The JavaScript client is reused per org and service, so a fresh token takes effect after a page reload.

Delivery and TTL

What happens to a message, and how TTL keeps it for late joiners.

  • Live delivery: a message goes to every client subscribed to its channel at that moment (all clients of the service for a broadcast). Messages you publish one after another arrive in that order; requests sent in parallel can arrive in any order.
  • At most once: there are no acknowledgements or retries. Clients that are offline, reconnecting or too slow to keep up miss messages. Treat RTLayer as the fast path, and let clients re-sync from your API after a reconnect when every message matters.
  • No history: messages are not stored, except as described for TTL below.

With ttl, a message published to a channel nobody is currently subscribed to is kept for up to ttl seconds. The first client to join that channel within that time receives it. Details worth knowing:

  • If anyone is subscribed when you publish, the message is delivered live and ttl has no effect.
  • A stored message is handed out once: to the client(s) that join first, not to everyone who joins later.
  • Identical messages stored for the same channel are kept once. Add an ID or timestamp if repeats matter.
  • Stored messages are delivered in expiry order and are kept for at most about 24 hours, whatever the ttl.

Org API keys and creating services

One key for every service in an organization, including creating services from code.

An org key belongs to an organization rather than a single service. Use it on servers that manage many services. Owners and admins create and disable org keys on the organization page; every member can see them.

Publish to any service

Send the org key in x-api-key and name the target with serviceId. The service must belong to the key’s organization.

terminal

Create a service

terminal

Both name and description are required. The service is created in the key’s organization and comes with a new service API key:

response

Save key.key from this response: API keys can’t list a service’s keys later (the dashboard can). Service keys get 403 This operation needs an org API key on this endpoint.

API keys

Service keys vs org keys, who manages them, and disabling a key.

Service keyOrg key
Publishes toits own serviceany service in the org (with serviceId)
Creates servicesnoyes (POST /service)
Where to manageservice → API Keys taborganization page → API access
Who can createany memberowners and admins
Who can disableowners and adminsowners and admins
  • Every new service comes with one service key. Create more to rotate keys without downtime: add the new key to your servers, then disable the old one.
  • Disabling is permanent; a disabled key can’t be re-enabled. It stops working within about a minute.
  • A disabled key and a key that never existed both get 401 Invalid API Key.

Limits

Rate limits, sizes, connections and timeouts.

LimitValue
Publish rate3,000 requests per 3 minutes per service, shared by all its keys (including org keys targeting it). Over the limit: 429 Too many requests, no Retry-After header. Requests that are rejected still count, so back off before retrying.
Request body8 MB for JSON, 100 KB for form-encoded bodies. Keep messages small for the fastest delivery.
Concurrent connections10,000 per service. Further connection attempts get HTTP 429 during the handshake.
Idle connectionsClosed after 30 s without answering the server’s ping (handled automatically by clients).
Client framesUp to 16 MB; clients only ever send small join/leave frames.
TTL storageAbout 24 hours at most.

Errors

Status codes and messages from the HTTP API and the websocket.

Errors come back as JSON: {"status":"error","message":"<text>","code":<status>}. Validation errors are 400 with a list instead: {"status":"error","message":[{"field":"message","message":"Required"}]}.

StatusMessageMeaning
400[{"field":"message","message":"Required"}]The body has no message.
400[{"field":"ttl","message":"Expected number, received nan"}]ttl isn’t a number.
400serviceId is required when using an org API keyOrg keys must say which service to publish to.
401API Key not foundNo x-api-key header or apiKey parameter.
401Invalid API KeyThe key doesn’t exist or was disabled.
401Token not foundPOST /service without credentials.
403This operation needs an org API keyPOST /service with a service key.
404Service not foundAn org key’s serviceId doesn’t exist or belongs to another org.
429Too many requestsPublish rate limit; wait and retry with backoff.
500Internal Server ErrorAlso returned for malformed JSON and bodies over the size limit. Check your JSON first.
WebsocketMeaning
Handshake fails with HTTP 401Authentication is on and the token is missing, invalid, expired or for other IDs.
Handshake fails with HTTP 429The service already has 10,000 connections.
{"status":"error","message":"You are not allowed to join <id>"}The token’s room.allowed / room.denied don’t permit the channel.
{"status":"error","message":"Invalid JSON"}The client sent a frame the server couldn’t parse.

Using the dashboard

Services, keys, Playground, analytics and team roles.

WhereWhat you can do
DashboardEvery service with its live connection count. Press Ctrl K (⌘K on Mac) to jump to any service or org.
Service → OverviewConnections right now, messages in the last 24 hours, message chart, active sessions.
Service → PlaygroundSend test messages and watch them arrive live, with the round-trip time of each one.
Service → API KeysOrg and service IDs, create and disable service keys.
Service → SettingsRequire authentication and set the token secret.
Organization pageOrg API keys, team summary, org-wide message analytics, rename.
AnalyticsSessions by device, platform, browser and origin; message volume over time.
Account menu → TeamInvite teammates by email (they join on their first sign-in) and change roles.
RoleCan
OwnerEverything an admin can do. The creator of the organization; can’t be removed from it.
AdminInvite and remove members, change roles, create and disable org keys, disable service keys, change service authentication, rename the org.
MemberCreate services and service keys, rename services, use the Playground and analytics, view keys.

For AI agents

Machine-readable docs and a checklist for integrating RTLayer from code.

  • /llms.txt: a short index of these docs in the llms.txt format.
  • /llms-full.txt: this whole page as one Markdown file. The Copy as Markdown button at the top copies the same text.
  1. Ask the user for their org ID, service ID and a service API key (dashboard → service → API Keys). Never put the API key in client code; read it from an environment variable such as RTLAYER_API_KEY.
  2. Server side, publish with POST https://api.rtlayer.com/message, header x-api-key, JSON body {"channel": "...", "message": ...}. Check for {"status":"ok"}.
  3. Client side, use rtlayer-client: RTLayer(orgId, serviceId).on(channel, handler), and JSON.parse the message when you published an object.
  4. If the data is private, enable authentication and sign tokens on the server with orgId, serviceId, uid and room.allowed.
  5. Verify end to end: subscribe, publish, and confirm the handler fires (or use the service’s Playground tab).

Troubleshooting

Common problems and how to fix them.

SymptomCheck
Publish returns ok but nothing arrivesThe client uses the same org and service IDs as the key’s service; it joined the channel before you published (or use ttl); the channel names match exactly.
Broadcasts show up as {"channel":"","message":…}That’s the raw frame on("*") receives for broadcasts; unwrap it as shown in Broadcasts, or publish to a channel.
Objects arrive as stringsExpected: messages are delivered as text. JSON.parse them.
Client can’t connect after enabling authenticationPass a token; check orgId/serviceId match the URL, the secret is current and the token hasn’t expired. Changes can take up to 10 minutes to apply.
Connected but joins are refusedThe token needs room.allowed patterns that match the channel.
401 Invalid API KeyThe key was disabled or mistyped; copy a current one from the API Keys tab.
429 Too many requestsOver 3,000 publishes in 3 minutes for this service: batch updates and back off.
500 on publishUsually malformed JSON or an oversized body.