Short, copy-pasteable answers to “how do I X” for the streams modality. Each assumes you’ve imported from @clutchcall/sdk/streams (TS) or clutchcall.streams (Python) and constructed a control-plane Streams client with baseUrl/apiKey/orgId.

Create a live input

A live input is the destination an encoder or browser publishes into. Pick the ingest kind your source speaks.
const { input, streamKey } = await streams.liveInputs.create({
  name:   "Saturday Show",
  ingest: "rtmp",            // "rtmp" | "srt" | "whip" | "fmp4"
});
console.log(input.external_input_id, streamKey); // save streamKey NOW
The cleartext stream key is returned only here. Persist it immediately.

Build the RTMP / SRT publish endpoint

RTMP and SRT encoders ingest with the per-input stream key. The path segment is the input’s external_input_id.
RTMP:  rtmp://ingest.clutchcall.dev/live/<external_input_id>?sk=<stream_key>
SRT:   srt://ingest.clutchcall.dev:<port>?streamid=<external_input_id>:<stream_key>
Paste the RTMP URL + key into OBS (Settings → Stream → Custom) or your hardware encoder and go live.

Go live from the browser with WHIP

WHIP is the WebRTC-HTTP ingest path — capture the camera and POST an SDP offer. Create the input with ingest: "whip", then publish with a standard WHIP client.
const { input, streamKey } = await streams.liveInputs.create({
  name: "Webcam", ingest: "whip",
});
const cam = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
const pc  = new RTCPeerConnection();
cam.getTracks().forEach((t) => pc.addTrack(t, cam));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const answer = await fetch(
  `https://ingest.clutchcall.dev/whip/${input.external_input_id}`,
  { method: "POST", headers: {
      "content-type": "application/sdp",
      "authorization": `Bearer ${streamKey}`,
    }, body: offer.sdp },
).then((r) => r.text());
await pc.setRemoteDescription({ type: "answer", sdp: answer });

Publish a browser MediaStream over MoQT

The SDK’s BroadcastPublisher is a WHIP-style alternative that fragments a MediaStream to CMAF and pushes it over MoQT directly.
const dataPlane = new Streams({ relayUrl: "https://relay.clutchcall.dev" });
const pub = dataPlane.publisher({ timesliceMs: 1000 });
const screen = await navigator.mediaDevices.getDisplayMedia({ video: true });

await pub.publish(
  input.external_input_id,
  streamKey,
  screen,
  `stream/org_abc/${input.external_input_id}`,
);

Upload a VOD file with TUS2 (resumable)

VOD uploads use TUS2 so large files survive network drops. Create the upload, PATCH chunks at the confirmed offset, and the engine packages to CMAF on completion.
// 1) create the resumable upload, get a location to PATCH into
const { uploadUrl } = await streams.assets.createUpload({ name: "Keynote.mp4" });

// 2) send chunks; on reconnect, HEAD to learn the offset, then resume
let offset = 0;
const file = await fileHandle.arrayBuffer();
while (offset < file.byteLength) {
  const end = Math.min(offset + CHUNK, file.byteLength);
  await fetch(uploadUrl, {
    method: "PATCH",
    headers: {
      "tus-resumable": "2.0.0",
      "upload-offset": String(offset),
      "content-type": "application/offset+octet-stream",
    },
    body: file.slice(offset, end),
  });
  offset = end;
}
streams.assets.* is Preview — until the typed helper ships, drive the TUS2 endpoint directly with the same bearer key.

Mint a signed playback URL

The playback URL carries a short-lived JWT. The relay verifies it — the URL is the auth.
const input = await streams.liveInputs.get({ id: "li_xyz" });
const { url, expiresAt } = await input.signedPlaybackUrl({ ttlSeconds: 3600 });

Play a live stream into a <video>

lv_* ids play live over MoQT; pb_* ids play VOD over HTTPS — the same viewer handles both.
const dataPlane = new Streams({ apiBase: "", token: jwt });
const viewer = dataPlane.viewer({ onError: (e) => console.error(e) });
viewer.attach(document.querySelector("video")!);
await viewer.play("lv_abc123");

Consume playback chunks headless (Python)

For server-side recording or re-muxing, take chunks via callback instead of a <video> element.
ticket = inp.signed_playback_url(ttl_seconds=600)
out = open("capture.mp4", "wb")
viewer = BroadcastViewer.open(
    ticket.url,
    on_chunk=lambda is_init, c: out.write(c.data),
    on_close=lambda reason, detail: out.close(),
)

Read the catalog before playing

The catalog enumerates the available renditions so you can pick or label them.
import { parseCatalog, videoTrack, audioTrack } from "@clutchcall/sdk/streams";

const cat = parseCatalog(await fetch(catalogJsonUrl).then((r) => r.json()));
console.log("video:", videoTrack(cat)?.name, videoTrack(cat)?.bitrate);
console.log("audio:", audioTrack(cat)?.codec);

Rotate a stream key

Rotation invalidates the old key immediately and returns a fresh cleartext one.
const { streamKey } = await input.rotateStreamKey();

Manage signing keys

Create a signing key (Ed25519 by default), then deactivate it to revoke every token minted from it.
const key = await streams.signingKeys.create({ alg: "Ed25519" });
console.log(key.id, key.publicKeyPem);
// revoke later
await key.deactivate();

Re-mint before a long viewer expires

Playback tokens are server-clamped (≤ 24 h). For a long session, re-mint and re-open ahead of expiresAt.
async function keepAlive(input, viewer, videoEl) {
  let { url, expiresAt } = await input.signedPlaybackUrl({ ttlSeconds: 3600 });
  setInterval(async () => {
    if (Date.now() / 1000 > expiresAt - 60) {
      ({ url, expiresAt } = await input.signedPlaybackUrl({ ttlSeconds: 3600 }));
      viewer.stop();
      await viewer.attach(videoEl).play(input.external_input_id);
    }
  }, 30_000);
}

List live inputs

Page through the org’s inputs to render a dashboard.
const inputs = await streams.liveInputs.list({ page: 1, perPage: 50 });
for (const i of inputs) console.log(i.external_input_id, i.status, i.ingest);

Pull viewer analytics

Overview KPIs, viewer time-series, and glass-to-glass latency are exposed by the control-plane analytics surface.
// Preview: streams.analytics.* — reach the control-plane analytics route
// directly with the bearer key until the typed helper ships in your SDK version.
streams.analytics.* is Preview.