If a request to POST /api/v1/transcripts/resolve times out, you don't know whether it was billed before the connection dropped. Retrying blind risks charging the same batch twice. The Idempotency-Key header fixes that: send the same key on a retry, and Scriptube returns the original response instead of processing it again.
This matters specifically for /resolve because it can bill synchronously, inline, in the same request that times out. A dropped connection doesn't tell you whether the server finished processing before or after the network failed â from your side, a timeout and a successful-but-slow response look identical until you check.
How it works
Generate a key (a UUID is fine) once per logical request, and send it on every attempt of that request â not a new one per retry:
curl -s -X POST https://scriptube.me/api/v1/transcripts/resolve \
-H "X-API-Key: YOUR_API_KEY" \
-H "Idempotency-Key: 7f3a9e2c-1b4d-4e8a-9c7f-2d5e6a8b1c3d" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]}'
What happens on each attempt:
| Situation | Result |
|---|---|
| First time this key is used | Processed and billed normally. |
| Same key, first call already finished | The stored response is replayed â no new charge, no new batch. |
| Same key, first call still in flight | 409 â a concurrent call with that key is already running. |
| No key sent | Every call is processed and billed independently, as normal. |
A replayed key answers from the stored result for 24 hours. The in-progress lock self-clears after 60 seconds, so a call that genuinely died doesn't leave the key stuck forever. Keys are scoped to your account, so reusing a short key is safe across different accounts.
Worth being deliberate about key choice: generate one key per logical submission and reuse it only for retries of that exact same request. Reusing a key across two genuinely different requests (different URLs, different options) isn't an error on Scriptube's side â you'll just silently get the first request's result back for the second one, which is a confusing bug to track down later. A random UUID per submission, kept only as long as you might still retry, is the simplest correct approach.
Using it in a retry loop
import uuid
import requests
HEADERS = {"X-API-Key": "YOUR_API_KEY"}
BASE = "https://scriptube.me"
idempotency_key = str(uuid.uuid4()) # generate once, reuse on retry
def submit():
return requests.post(
f"{BASE}/api/v1/transcripts/resolve",
headers={**HEADERS, "Idempotency-Key": idempotency_key},
json={"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]},
timeout=10,
)
try:
r = submit()
except requests.exceptions.Timeout:
# Network timeout: we don't know if the first request billed. Retry with
# the SAME key instead of generating a new one.
r = submit()
r.raise_for_status()
print(r.json())
The bug this prevents is subtle: a naive retry-on-timeout generates no key at all, or a fresh key each time, and a batch that actually succeeded server-side gets submitted â and billed â again.
Seeing the replay in practice
Two curl calls with the same key, one right after the other, demonstrate the behavior directly:
# First call â processed and billed normally
curl -s -X POST https://scriptube.me/api/v1/transcripts/resolve \
-H "X-API-Key: YOUR_API_KEY" \
-H "Idempotency-Key: demo-key-001" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]}'
# Second call, same key â returns the exact same response, no new charge
curl -s -X POST https://scriptube.me/api/v1/transcripts/resolve \
-H "X-API-Key: YOUR_API_KEY" \
-H "Idempotency-Key: demo-key-001" \
-H "Content-Type: application/json" \
-d '{"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]}'
If you fire both calls at nearly the same instant (genuinely concurrent, not one after the other), you'll instead see the second one answer 409 â the first hasn't finished yet, so there's no stored result to replay, and the key is locked to prevent a double-charge race.
What it doesn't cover
Idempotency-Key is specific to /resolve. It doesn't apply to POST /api/v1/transcripts (plain submit), exports, or lifecycle actions like retry-failed â those don't have the same "might have already billed" ambiguity in the same way, since a plain submit always returns a batch_id you can poll to check what actually happened. If you're already polling or using webhooks to confirm batch state, you may not need idempotency keys at all for those paths â see webhooks vs. polling.
Frequently asked
Do I need a new key for every retry attempt? No â the opposite. Generate one key for the logical request and send that same key on every attempt of it, however many times you retry.
What happens if I never retry â does the key do anything? Nothing extra. A key sent once, with no retry, behaves exactly like not sending one: the request is processed and billed normally, and the result happens to be cached under that key for 24 hours in case you do need to retry later.
Can two different API keys on the same account collide on the same idempotency key? No â the lock is scoped per account, not per API key, so any key belonging to the same account sees the same replay behavior for a given Idempotency-Key value.
Is there a format requirement for the key itself? No fixed format is enforced â a UUID is the conventional choice because it's trivially unique, but any sufficiently unique string works.
Does the replayed response reflect the current state of the batch, or a frozen snapshot? It's the original /resolve response exactly as it was returned the first time â for a batch that's still processing in the background, poll GET /api/v1/transcripts/{batch_id} for current status rather than relying on a replay to reflect progress.
Next steps
- Retrying failed items â a different kind of retry, for items that errored out inside a completed batch.
- The complete API guide â auth, submit, poll, export, errors.
Full reference: API documentation.