Hosted live coordination
Explicit app-authenticated subscriptions without device enrollment or a peer runtime.
This source capability is currently available for local testing and selected internal staging tests. The production route and new paid offer remain inactive pending release integration and activation.
Create an explicit live-only root when you need ephemeral hosted publications and subscriptions. Your backend owns user identity and signs scoped session credentials. This path needs no legacy API key, v2 assertion exchange, Firebase client enrollment, durable device inventory, WASM or Iroh startup.
import { OpenRTC } from 'openrtc';
const rtc = OpenRTC({ profile: 'live' });
const live = await rtc.live.open({
credentials: async ({ forceRefresh, signal }) => {
const response = await fetch('/api/live-session', {
method: 'POST',
credentials: 'include',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ forceRefresh }),
signal,
});
if (!response.ok) throw new Error('Live access is unavailable');
return response.json(); // { certificate, session }
},
});
live.onMessage(({ channel, from, data }) => {
console.log(channel, from, data);
});
live.onClose(() => console.log('Live session closed'));
await live.subscribe('project:example');
const accepted = await live.publish('project:example', 'hello');
console.log(accepted.units);
await live.unsubscribe('project:example');
await rtc.close();
The root owns one live socket across its subscriptions. A second open on that root requires confirmed retirement of the first socket. Separate roots, tabs and native runtime sessions count separately. There is no hidden legacy socket. The paid account peak is simultaneous across its apps, not a sum of app peaks.
Publications accept UTF-8 strings up to 64 KiB. A hosted unit is one independently rounded 4 KiB publication or attempted live delivery. Sending five bytes to one other subscriber consumes two units. Direct peer traffic is outside this meter. This first protocol supports up to 100 channel scopes and 100 live recipients per publication. It rejects a larger fanout; the SDK does not split or retry it.
An acceptance receipt means the service accepted the operation. It does not confirm that a recipient applied a product effect. Socket loss or an unknown ACK closes the handle, and no publication is replayed automatically. Retrying a publication explicitly is a new publication and can repeat an effect. There is no offline queue, reconnect replay, history or room-member roster.
The socket owner refreshes a one-hour app session one minute before expiry.
live.renew() also requests a fresh credential on the same socket. The service
requires at least thirty minutes between renewals and strictly newer issue and
expiry times; an early or incompatible renewal is rejected and closes the
handle. Keep the app, subject and channel scope unchanged during renewal.
When a session is already capped at its app certificate's expiry, the owner
keeps it alive until that cutoff and skips the impossible refresh. Manual
renew() refuses locally with live-certificate-expiry and preserves the
still-valid handle. Credentials too short to meet the renewal spacing expire
closed. A changed identity,
channel scope or paid term requires closing and explicitly opening a new session.
Your credential provider should honor its AbortSignal. Closing fences late responses even when the provider cannot cancel its request. Do not add a background app reconnect loop. After a close, decide whether a new explicit session is appropriate for your product's current identity and user action.
Planned subscription and usage prices
These are the new live-only offer's prices; production activation is pending. Existing accepted terms retain their original prices and allowances.
| Tier | Monthly price | Concurrent connections | Hosted units/month | Apps | Extra peak connection | Extra 1M hosted units |
|---|---|---|---|---|---|---|
| Hobby (noncommercial) | $5 | 100 | 100,000 | 1 | Hard cap | Hard cap |
| Builder | $29 | 1,000 | 1,000,000 | 3 | $0.05 | $50 |
| Pro | $79 | 4,000 | 5,000,000 | 10 | $0.04 | $40 |
| Scale | $299 | 20,000 | 20,000,000 | 10 | $0.03 | $30 |
Additional spending defaults to $0. Enable it explicitly and choose one cap for combined connection and hosted-unit overages. The cap covers additional pretax usage, excluding the subscription and applicable invoice taxes. No prepaid packs are required. Hobby stops at its included limits.
An extra connection is charged against the account's highest simultaneous count in the billing cycle, once for that cycle—not per minute or cumulative reconnect. Connections and units aggregate across all apps. Hosted units count publications and attempted recipient deliveries separately in 4 KiB increments as described above; pings and ACKs do not consume customer hosted units. Overage quantities are calculated proportionally, without requiring million-unit blocks.
For example, Builder at 2,000 peak connections and 1.5M hosted units is $104 before tax: $29 subscription + $50 connections + $25 units. Pro includes that usage for $79. Both the included allowances and marginal prices improve with the higher tiers. These are plan comparisons; automatic in-cycle upgrades and proration are not available in this first lean release.
App backend authority
Register your own Ed25519 app public key through the authenticated developer
administration owner. Keep its private key and developer credentials on your
backend. The platform returns a term-bound app certificate only after the
current gateway authority is acknowledged. Send only the canonical public JWK
fields { "kty": "OKP", "crv": "Ed25519", "x": "..." }; omit export metadata
such as ext, key_ops, alg or use. Registration is administrative work,
performed on demand rather than per client or periodically while idle.
After authenticating a user, return that certificate and an EdDSA JWT signed by
your app private key. Set the protected JWT header to
{ "alg": "EdDSA", "typ": "JWT" }; the payload's typ field below is separate.
Its claims are:
{
"typ": "live-session",
"aud": "the audience from the app certificate",
"appTag": "the app name from the certificate",
"sub": "your authenticated subject",
"channels": ["project:example"],
"iat": 1800000000,
"exp": 1800003600
}
Use current Unix seconds, at most a one-hour lifetime, and an expiry no later
than the app certificate. Subjects and channel names contain 1–128 characters
from letters, digits, _ . : @ / -. Select scopes from your backend authorization;
never sign a channel list merely because the browser requested it. The service
verifies signatures and current account/app authority. The SDK's local expiry
parsing supplies scheduling hints only.
Session tokens are bearer credentials. Keep them in memory and never put them in URLs or logs. Hosted message contents are visible to the service. Use existing protected Iroh channels when your product requires that different delivery and security contract.
Backend automation
First sign in as the developer and create a PAT with
POST /v3/developer/live/pats. Supply your Firebase ID token as the bearer,
the account response's liveAdministration.certificate in
X-OpenRTC-Live-Administration, and a unique X-OpenRTC-Idempotency-Key. Send JSON bodies with
Content-Type: application/json:
{
"name": "backend automation",
"scopes": ["developer:apps:write", "developer:apps:delete", "developer:pats:read", "developer:pats:delete"],
"expiresInDays": 90
}
Store the response's one-time token, tokenId, routingProof and
liveAdministration on your backend. Use the PAT as the bearer and its
current-term administration certificate in X-OpenRTC-Live-Administration
for subsequent requests:
| Operation | Route | Required PAT scope |
|---|---|---|
| Register an app key | PUT /v3/developer/live/apps/<appTag> | developer:apps:write |
| Revoke an app key | DELETE /v3/developer/live/apps/<appTag> | developer:apps:delete |
| List active PATs | GET /v3/developer/live/pats | developer:pats:read |
| Revoke a PAT, including itself | DELETE /v3/developer/live/pats/<tokenId> | developer:pats:delete |
Registration sends {"termId":"<current term>","publicKey":{"kty":"OKP","crv":"Ed25519","x":"<public key>"}}.
App-key revocation sends {"termId":"<current term>"}.
When the administration certificate expires or is lost, send {} to
POST /v3/developer/live/authorization with the PAT bearer and the retained
routing proof in X-OpenRTC-Live-Administration. Save the returned current-term
certificate and routing proof. Neither can extend access past the PAT's expiry
or an active purchased term.
Cache this administrative setup; it is not a per-client or per-deployment call. Your backend signs end-user session credentials locally. Administration attempts, including retries, have separate limits from client connections and hosted units. If the one-time PAT response is lost, repeating the identical request and idempotency key within the same purchased term returns 409 with the reserved token ID, not the secret. After rollover, list active tokens to recover an uncertain earlier creation. Revoke the unusable token and create with a new idempotency key. Initial PAT creation and spending consent require signed-in developer authorization. Keep app-key revocation and PAT revocation separate; revoking a PAT does not revoke an app key.
Native hosts
Use the same capability from OpenRTC.native({ profile: 'live' }). Install the
matching first-party openrtc-tauri JavaScript package and Rust plugin. A lean
host can initialize only these commands:
.plugin(openrtc_tauri_plugin::init_live())
Existing hosts that use openrtc_tauri_plugin::init(config) retain their original
APIs and also expose the explicit live capability. Native sockets, heartbeat,
expiry and accepted-operation correlation stay in Rust. The JavaScript bridge
only forwards commands, app credential responses and events. The owning WebView's
destruction retires its sessions, including admission still in flight.
Pure Rust hosts use openrtc::native::NativeLiveCoordination. Hosts with a native
socket policy use open_with_socket_initializer and the existing
NativeGatewaySocketInitializer, including its shared DNS resolver when enabled.
Do not replace that policy with an application WebSocket or DNS client.