Idempotency and caching

Two mechanisms that both make "the same request twice" cheap, and that answer different questions. Conflating them is the most common way to misread a response.

| | Idempotency-Key | The cached lane | |---|---|---| | Question | Did this exact request already happen? | Have we already transcribed these exact bytes for this account? | | Keyed on | (account, Idempotency-Key) | (account, media fingerprint) — the video id, or the media's sha256 | | Result | The same run, returned again | A new run, lane: "cached", cached_from_run_id naming the source | | Visible as | the same id you already had | a different id, already done on the first poll | | Window | 24 hours | as long as the source run exists | | Bypassed by | nothing — omit the header | force: true |

Idempotency

Send Idempotency-Key on POST /v1/runs and POST /v1/uploads — a UUID, or any value you will not reuse by accident. A repeat within 24 hours returns the original response with the original status code; it never creates a second run and it never errors. The key does not need to match the body: a repeat with a different body still returns the first run. That reveals a client bug immediately and visibly, where a 409 on a key you have forgotten reveals nothing.

curl -sS -X POST https://<host>/v1/runs \
  -H "Authorization: Bearer $SCRIPTRIP_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4f0a9e21-59f2-4d64-8e18-7d9b0b1c66aa" \
  -d '{"source":{"type":"youtube","url":"https://youtu.be/dQw4w9WgXcQ"}}'
{"id":"0195c8e4-8d02-7c19-b3e7-5a1f9d20c68b","status":"queued","idempotency_key":"4f0a9e21-59f2-4d64-8e18-7d9b0b1c66aa","…":"…"}

Send it again — same key — and the same id comes back with the same 201. After 24 hours the key is forgotten and the same request is a new run. A program that retries should always send one; without it a retry after a lost response creates a second run.

The cached lane

Submit media this account has already transcribed — the same YouTube video, or a file with the same sha256 — and a new run is created and completed in the submission response: status: "done", lane: "cached", cached_from_run_id pointing at the source, exactly one step named cached, and HTTP 200 rather than 201 because no work was created. Cached seconds do not count against your monthly allowance.

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"}}'
{"id":"0195cb10-4e2a-7f61-9c0b-1d2e3f4a5b6c","status":"done","lane":"cached","cached_from_run_id":"0195c8e4-8d02-7c19-b3e7-5a1f9d20c68b","step_log":[{"step":"cached","started_at":"2026-03-11T18:04:12Z","ended_at":"2026-03-11T18:04:12Z","ok":true,"detail":"Served from a transcript this account already had."}],"expected_steps":["cached"],"…":"…"}

A cache hit is never silent: the one cached step is how you can tell. The cache is scoped to your account — another account's transcript of the same video is never yours — and one hop deep. A pinned requested_language bypasses it, because a run pinned to a different language is a different request.

Transcribe it again

"force": true skips the cached lane and runs the engine. It does not affect idempotency: a force: true submission with a repeated key still returns the original run, because the request already happened.

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"},"force":true}'
{"id":"0195cb11-0a3c-7b84-8e1f-2a3b4c5d6e7f","status":"queued","lane":null,"cached_from_run_id":null,"…":"…"}