Quickstart

Under ten minutes from an empty terminal to a transcript on screen. Six steps, no detours. You need curl, jq and uuidgen.

1. Get a key

Sign in, then Account → API keys → New key. Copy it: it is shown once and never again.

export SCRIPTRIP_KEY=sk_live_…

2. Check you can reach the API

curl -sS https://<host>/v1/health
{"ok":true,"version":"v1","time":"2026-03-11T18:11:44Z","build":"070e121"}

3. Submit a run

RUN=$(curl -sS -X POST https://<host>/v1/runs \
  -H "Authorization: Bearer $SCRIPTRIP_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"source":{"type":"youtube","url":"https://youtu.be/dQw4w9WgXcQ"}}' \
  | tee /dev/stderr | jq -r .id)
{"id":"0195c8e4-8d02-7c19-b3e7-5a1f9d20c68b","source_type":"youtube","status":"queued","lane":null,"step_log":[],"segments":null,"seconds_until_modal":79,"live_workers":{"count":2,"kind":"utility"},"created_at":"2026-03-11T18:04:11Z","…":"…"}

The Idempotency-Key means a retried submission returns the same run instead of creating a second one. Always send one from a program.

4. Watch it work

One command, and it prints the real steps as they happen — fetch, normalize, transcribe, diarize — not a script of what usually happens.

while :; do
  curl -sS "https://<host>/v1/runs/$RUN" -H "Authorization: Bearer $SCRIPTRIP_KEY" \
    | jq -r '"\(.status)\t\(.lane // "—")\t\(.current_step // "—")"'
  sleep 2
done
queued	—	—
running	modal	fetch
running	modal	transcribe
running	modal	diarize
done	modal	—

Stop the loop with Ctrl-C when status reads done. While it runs, segments is nullnot yet, never there is none — so the poll stays small.

5. Read the transcript

curl -sS "https://<host>/v1/runs/$RUN/transcript?format=speakers" \
  -H "Authorization: Bearer $SCRIPTRIP_KEY"
Matt: Right, so this is day nine. Day nine and we still haven't seen land.

Amy: I keep telling him that's the point.

Six formats: prose, speakers, timestamped, srt, vtt, json. They are six renderings of one segments array; none is the real one.

6. Do it from code

The same five calls in TypeScript with @script-rip/core:

import { ScriptRip } from '@script-rip/core/client';

const sr = new ScriptRip({ apiKey: process.env.SCRIPTRIP_KEY!, baseUrl: 'https://<host>/v1' });

const run = await sr.runs.create({ source: { type: 'youtube', url: 'https://youtu.be/dQw4w9WgXcQ' }, nameSpeakers: true });
let status = run.status;
while (status !== 'done' && status !== 'failed' && status !== 'canceled') {
  await new Promise((r) => setTimeout(r, 2000));
  status = (await sr.runs.get(run.id)).status;
}
console.log(await sr.runs.transcript(run.id, { format: 'speakers' }));
Matt: Right, so this is day nine. Day nine and we still haven't seen land.

Amy: I keep telling him that's the point.

And in Python with requests, because the second audience does not necessarily write TypeScript:

import os, time, uuid, requests

BASE = "https://<host>/v1"
H = {"Authorization": f"Bearer {os.environ['SCRIPTRIP_KEY']}"}

run = requests.post(f"{BASE}/runs", headers={**H, "Idempotency-Key": str(uuid.uuid4())},
                    json={"source": {"type": "youtube", "url": "https://youtu.be/dQw4w9WgXcQ"}}).json()
while run["status"] not in ("done", "failed", "canceled"):
    time.sleep(2)
    run = requests.get(f"{BASE}/runs/{run['id']}", headers=H).json()
print(requests.get(f"{BASE}/runs/{run['id']}/transcript", headers=H, params={"format": "speakers"}).text)
Matt: Right, so this is day nine. Day nine and we still haven't seen land.

Amy: I keep telling him that's the point.

Next: stop polling, get a webhook · upload a file instead