import { Voice } from "@clutchcall/sdk/voice";
import { Streams } from "@clutchcall/sdk/streams";
import { Robotics } from "@clutchcall/sdk/robotics";
import { Games } from "@clutchcall/sdk/games";
import { Data } from "@clutchcall/sdk/data";
import { MoqtClient } from "@clutchcall/sdk/moqt";
Voice
const v = new Voice({
baseUrl: "https://app.clutchcall.dev", // control-plane API base
apiKey?: string, // server-side
browserToken?: string, // browser-side
orgId?: string,
});
Calls (control plane)
await v.calls.originate({
to: string, // E.164
from?: string, // E.164
trunkId?: string, // your trunk
agent?: string, // AI agent name (optional)
}) → Call
await v.calls.get(sid) → Call
await v.calls.list({ orgId?, cursor?, limit? }) → { calls, cursor? }
await v.calls.transfer(sid, { to, trunkId? }) → Call
await v.calls.hangup(sid) → void
await v.calls.terminate(sid) → void
Call exposes:
call.sid: string
call.status: CallStatus
call.from: string
call.to: string
call.onStatus((s: CallStatus) => void): Unsubscribe
await call.refresh(): Promise<CallData>
await call.hangup(): Promise<void>
await call.transfer(args): Promise<Call>
AudioBridge (data plane)
const bridge = await v.audioBridge.attach(callSid, {
codec: "opus" | "pcm16" | "g711_ulaw" | "g711_alaw",
onUplink?: (frame: Uint8Array, tsUs: bigint) => void,
});
bridge.publishUplink(frame: Uint8Array): void // browser → cloud
bridge.publishDownlink(frame: Uint8Array): void // cloud → caller
bridge.onUplink(cb): Unsubscribe
bridge.onDownlink(cb): Unsubscribe
await bridge.close(): Promise<void>
import { captureMicrophone, OpusPlayer } from "@clutchcall/sdk/voice";
const mic = await captureMicrophone({ onFrame: f => bridge.publishUplink(f) });
const player = new OpusPlayer();
bridge.onDownlink(f => player.play(f));
Agents
await v.agents.attach(callSid, agentName): Promise<void>
await v.agents.detach(callSid): Promise<void>
await v.agents.list({ orgId? }): Promise<Agent[]>
Streams
const streams = new Streams({
baseUrl: "https://app.clutchcall.dev",
apiKey: string,
orgId: string,
});
Control plane
streams.liveInputs.create({ name, codecs?, recordingProfile? })
→ { input, streamKey } // streamKey returned ONCE
streams.liveInputs.get({ id }) → LiveInput
streams.liveInputs.list({ orgId, cursor?, limit? }) → { liveInputs, cursor? }
streams.liveInputs.rotateStreamKey({ id }) → { streamKey }
streams.liveInputs.delete({ id }) → void
streams.signingKeys.create({ orgId, label }) → { id, keyMaterial }
streams.signingKeys.list({ orgId }) → SigningKey[]
streams.signingKeys.retire({ id }) → void
streams.apiKeys.create({ orgId, label, scopes }) → { id, secret } // secret returned ONCE
streams.apiKeys.list({ orgId }) → ApiKey[]
streams.apiKeys.revoke({ id }) → void
streams.webhooks.create({ orgId, url, events }) → Webhook
streams.webhooks.list({ orgId }) → Webhook[]
streams.webhooks.delete({ id }) → void
streams.events.listDeliveries({ webhookId, cursor?, limit? }) → { deliveries, cursor? }
streams.analytics.viewerMinutes({ orgId, from, to, groupBy? }) → ViewerMinutesRow[]
streams.analytics.popCache({ orgId, from, to }) → PopCacheRow[]
LiveInput exposes:
input.external_input_id: string
await input.signedPlaybackUrl({ ttlSeconds: number, scopes? }) → { url, expiresAt }
Data plane
const pub = await BroadcastPublisher.open({
inputId: string,
streamKey: string, // from create() / rotateStreamKey()
codecs: { video: string, audio: string },
});
pub.write(chunk: Uint8Array): void
await pub.close(reason?: string): Promise<void>
const viewer = await BroadcastViewer.open(signedUrl, {
onChunk: (init: BroadcastInit, chunk: BroadcastChunk) => void,
onClose: (reason: string) => void,
});
await viewer.close(): Promise<void>
Robotics
const r = new Robotics({
relayHost: "relay.clutchcall.dev",
token: string,
robotId: string, // unique per (tenant, robot)
});
// publish
await r.publishTelemetry({
topic: string, // e.g. "odom"
typeName: string, // e.g. "nav_msgs/msg/Odometry"
qos?: QoSProfile,
}) → RoboticsPublication
await r.publishCommand({
topic: string, // e.g. "cmd_vel"
typeName: string, // e.g. "geometry_msgs/msg/Twist"
qos?: QoSProfile,
}) → RoboticsPublication
// subscribe
await r.subscribeTelemetry({ topic, qos? },
(payload: Uint8Array, typeName: string) => void) → RoboticsSubscription
await r.subscribeCommand ({ topic, qos? },
(payload: Uint8Array, typeName: string) => void) → RoboticsSubscription
RoboticsPublication:
pub.write(cdrBytes: Uint8Array): void
await pub.close(): Promise<void>
QoSProfile:
{
reliability?: "reliable" | "best_effort";
durability?: "volatile" | "transient_local";
depth?: number;
}
Games
const g = new Games({
relayHost: "relay.clutchcall.dev",
token: string,
roomId: string,
playerId?: string, // omit for the authoritative server
});
Player
g.subscribeState((bytes: Uint8Array) => void): GamesSubscription
const input = await g.publishInput(): FromPublisher
input.write(bytes: Uint8Array): void
const events = await g.publishEvent(): FromPublisher
events.send(obj: any): void
g.subscribeEvents((event: any, fromPlayerId: string) => void): GamesSubscription
Authority (no playerId)
const state = await g.publishState({ tickHz?: number }): StatePublisher
state.write(bytes: Uint8Array): void
await g.subscribeInputs((playerId: string, bytes: Uint8Array) => void): GamesSubscription
await g.subscribeEvents((event: any, fromPlayerId: string) => void): GamesSubscription
Data
const d = new Data({
relayHost: "relay.clutchcall.dev",
token: string,
clientId: string, // your publisher identity
});
await d.publish({
topic: string,
payload: Uint8Array,
reliable?: boolean, // default false → datagram
retain?: boolean, // last-value-stuck
}): void
await d.subscribe({ topicFilter: string }, (msg: DataMessage) => void)
: DataSubscription
DataMessage:
{
topic: string; // the actual topic published to
payload: Uint8Array;
fromClientId: string;
retain: boolean;
tsUs: bigint;
}
+ (one segment) and # (multi-segment,
trailing only).
Realtime Tracks
The raw substrate. See Realtime Tracks for the full surface. Common methods:const moqt = await MoqtClient.connect(relayUrl, token, onState);
moqt.publishFrame(namespace, name, opts?: FrameOptions): FramePublication
moqt.subscribeFrame(namespace, name, cb): FrameSubscription
moqt.publishAudio(namespace, name, opts?: AudioOptions): AudioPublication
moqt.subscribeAudio(namespace, name, cb): AudioSubscription
moqt.publishVideo(namespace, name, opts?: VideoOptions): VideoPublication
moqt.subscribeVideo(namespace, name, cb): VideoSubscription
moqt.publishText(namespace, name, opts?: TextOptions): TextPublication
moqt.subscribeText(namespace, name, cb): TextSubscription
moqt.subscribeNamespace(prefix: string[],
(suffix: string, active: boolean) => void): void
moqt.connectionRttUs(): bigint
moqt.maxDatagramSize(): number
moqt.close(): void
Legacy RPC
The root import is the legacy voice-RPC surface (dial, originate_bulk,
hangup, barge, push_audio, …). Documented for backwards compat — new
code should use the Voice modality.
import { ClutchCallClient } from "@clutchcall/sdk";
const client = new ClutchCallClient("https://pbx.clutchcall.dev");
await client.dial(/* ... */);
await client.hangup(/* ... */);

