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 null — not
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.