Channel API
The Channel API is realtime pub/sub in the App Engine Channel tradition, modernized to WebSockets. Servers publish messages over HTTP; clients subscribe over a single WebSocket that can carry many channels. Clients authenticate with short-lived, channel-scoped tokens, so a browser subscribes without ever holding your API key. A channel with no open connections costs nothing.
How it fits together
- Your server mints a subscriber token for the channels a client may use (
POST /tokens). - The client opens a WebSocket with that token and receives messages (
GET /subscribe). - Your server (or a publish-capable client) publishes a message, which fans out to every open subscriber (
POST /publish).
Authentication and base URL
Send requests to https://api.altengine.net. The /tokens and /publish endpoints authenticate with an organization API key (bearer token); /subscribe authenticates with a subscriber token instead. Every path is scoped to a channel instance you provision in the console: /v1/channel/{instance}/….
| Method & path | Auth | Purpose |
|---|---|---|
POST /v1/channel/{instance}/tokens | API key (read; write for publish tokens) | Mint a subscriber token. |
POST /v1/channel/{instance}/publish | API key (write) or a publish token | Fan a message out to a channel. |
GET /v1/channel/{instance}/subscribe | Subscriber token | Open a WebSocket to receive messages. |
Channel requests are rate-limited per instance, with separate budgets so one kind of load cannot starve another and one busy app never throttles another. HTTP publishes get a sustained 300 per second (18,000 per minute) by default; token mints and new subscribe connections share a 200 per second (12,000 per minute) budget with a large burst to absorb reconnect storms. (WebSocket publishes are governed by their own per-connection cap instead.) Both are separate from the Search API's budget. You can set a lower limit per instance for each of these two budgets in the dashboard (Channels → your app → Rate limit); a configured value can only reduce the rate below the plan maximum, never raise it. Fan-out to subscribers is not rate-limited — only the publish call is — so a single publish reaching thousands of subscribers counts once. Over a limit, a request returns 429 with a RATE_LIMITED code; back off and retry.
Mint a subscriber token
Mint a token server-side and hand it to your client. A token declares the channels it may use and expires after ttl_seconds (default 3600, max 14400). Minting a subscribe-only token needs a read grant; minting a publish-capable token requires a write grant. The publish field controls which transport(s) the token may publish over:
publish | Token may publish |
|---|---|
false (or omitted) | Not at all — subscribe only. |
"http" | Over POST /publish only. Best for server-side publishing. |
"ws" | Over its WebSocket only (see Publish over WebSocket). Best for browser clients — it is rate-capped per connection. |
"all" | Both transports. |
true | Alias for "all". |
curl -X POST https://api.altengine.net/v1/channel/live/tokens \
-H "Authorization: Bearer $ALTENGINE_KEY" \
-H "Content-Type: application/json" \
-d '{ "channels": ["room-42"], "ttl_seconds": 3600, "publish": false }'The response includes a ready-to-use ws_url — the WebSocket URL with the token pre-filled (it carries both subscribe and, for a publish-capable token, publish):
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…",
"expires_at": 1752345600,
"channels": ["room-42"],
"publish": false,
"ws_url": "wss://api.altengine.net/v1/channel/live/subscribe?token=eyJhbG…"
}Publish over HTTP
Publish a JSON data payload to one channel. Authenticate with an API key that has a write grant, or with a subscriber token minted with publish of "http" or "all" for that channel. The response reports how many open subscribers received it. (Browser clients can also publish over their socket — see Publish over WebSocket.)
curl -X POST https://api.altengine.net/v1/channel/live/publish \
-H "Authorization: Bearer $ALTENGINE_KEY" \
-H "Content-Type: application/json" \
-d '{ "channel": "room-42", "data": { "text": "hello" } }'
# → { "delivered": 3 }Each subscriber receives the message as a JSON frame carrying the channel, your payload, and a server timestamp (milliseconds since the epoch):
{ "channel": "room-42", "data": { "text": "hello" }, "ts": 1752345600000 }Delivery is at-most-once: a message reaches only the subscribers connected at publish time — there is no backlog or replay for clients that are offline or reconnecting. Publishing to a channel with no open connections returns { "delivered": 0 }.
Subscribe over WebSocket
Open a WebSocket to the ws_url (or build it yourself: wss://api.altengine.net/v1/channel/{instance}/subscribe?token=…). The connection subscribes to all of the token's channels by default; pass ?channel= (repeatable) or ?channels=a,b to start with a subset. Browsers that cannot set query strings may instead pass the token as a subprotocol: Sec-WebSocket-Protocol: bearer, <token>. Requesting /subscribe without a WebSocket upgrade returns HTTP 426.
One socket carries many channels. Incoming frames are the message objects shown above. You can adjust the live subscription — within the channels the token authorized — by sending control frames:
| Send | Effect |
|---|---|
{ "type": "subscribe", "channels": ["room-7"] } | Add channels to this connection. |
{ "type": "unsubscribe", "channels": ["room-7"] } | Remove channels from this connection. |
ping (raw string) | Keepalive; the server replies pong. |
After a subscribe or unsubscribe, the server acknowledges with the connection's current set: { "type": "subscribed", "channels": [ … ] }. The socket is closed automatically when the token expires — with WebSocket close code 4401 — so refresh the token and reconnect before then.
Publish over WebSocket
A token minted with publish of "ws" or "all" can publish up its own socket instead of calling POST /publish — lower latency for the sender (no per-message HTTP round-trip) and, because it rides an established connection, rate-capped per connection rather than against the instance's publish budget. Send a publish frame naming a channel the token is authorized for:
ws.send(JSON.stringify({ type: "publish", channel: "room-42", data: { text: "hello" } }));The message fans out to that channel's subscribers as the same frame an HTTP publish produces ({ "channel", "data", "ts" }), and the server acknowledges to the sender with the delivery count:
{ "type": "published", "channel": "room-42", "delivered": 3 }If the frame is rejected — the token lacks WS publish, the channel is not authorized, the message is over 32 KiB, or the per-connection rate cap (about 10 publishes per second) is exceeded — the server replies with an error frame instead, and the message is not delivered:
{ "type": "error", "error": "publish rate exceeded — slow down" }WebSocket publish is ephemeral. Like all channel delivery it is at-most-once and never touches your backend, so there is no server-side record of the message. If you need message history, moderation, or validation, publish server-side instead: have the client send to your backend, which records the message and then calls HTTP publish. Reserve WebSocket publish for low-latency ephemeral traffic — chat where history is optional, typing indicators, cursors, and live reactions.
End-to-end example
// Server: mint a token (never expose the API key to the browser)
const res = await fetch("https://api.altengine.net/v1/channel/live/tokens", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.ALTENGINE_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({ channels: ["room-42"], ttl_seconds: 3600 })
});
const data = await res.json();
// Browser: connect with the returned URL
const ws = new WebSocket(data.ws_url);
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
console.log(msg.channel, msg.data, msg.ts);
};
// Adjust channels live (within the token's authorized set)
ws.send(JSON.stringify({ type: "subscribe", channels: ["room-7"] }));Limits
| Limit | Value |
|---|---|
| Channels per token | 100 |
| Subscribers per channel | 10,000 |
| Channel name length | 200 bytes |
| Message size | 32 KiB |
| WebSocket publishes per connection | ~10 per second |
| Token TTL (default) | 3600 seconds (1 hour) |
| Token TTL (max) | 14400 seconds (4 hours) |
Channel names must be printable ASCII, may not start with !, and may not use the reserved __*__ form.