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.

TierMonthly priceConcurrent connectionsHosted units/monthAppsExtra peak connectionExtra 1M hosted units
Hobby (noncommercial)$5100100,0001Hard capHard cap
Builder$291,0001,000,0003$0.05$50
Pro$794,0005,000,00010$0.04$40
Scale$29920,00020,000,00010$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:

OperationRouteRequired PAT scope
Register an app keyPUT /v3/developer/live/apps/<appTag>developer:apps:write
Revoke an app keyDELETE /v3/developer/live/apps/<appTag>developer:apps:delete
List active PATsGET /v3/developer/live/patsdeveloper:pats:read
Revoke a PAT, including itselfDELETE /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.