transcrevo docs

Safe retries

Idempotency-Key, the retryable field, long polling with ?wait=, cancellation, cursors and the limits the API reports about itself: what an integration needs to defend itself.

A transcription integration runs with nobody watching: the network blips, a deploy restarts the process mid-call, a backlog releases all at once. This page is what the API offers so none of that turns into audio charged twice or a job lost.

Retrying creation without paying twice

POST /v1/transcripts creates AND charges. A response lost on the way back leaves you unsure whether the job exists, and retrying blindly creates a second one. Idempotency-Key closes that window:

curl https://api.transcrevo.com/v1/transcripts \
  -H "Authorization: Bearer $TRANSCREVO_API_KEY" \
  -H "Idempotency-Key: 8e1f0f1a-3c1a-4a1f-9f0a-2b7d5c9e1234" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/audio.mp3", "speakers": true}'

The same key returns the same transcript, without creating or charging another. The same key with a different body is 409 idempotency_conflict: that is a bug on your side, not a retry, and returning the old job would make the new audio disappear silently.

Pick the key from what is stable on your side. With uploads, key it on the FIRST uploadId: it changes on every new upload attempt, so using your own media id would return 409 forever after the first time.

Knowing what is worth retrying

Every error carries retryable:

{ "error": { "code": "rate_limited", "message": "…", "retryable": true, "requestId": "…" } }
const { error } = await createTranscript(input);
if (error && !error.retryable) throw new Error(`${error.code}: ${error.message} (${error.requestId})`);
// retryable: exponential backoff, or the Retry-After header when it is present

Without that field, every integration keeps its own table of "which errors deserve a retry", and the one that gets it wrong repeats forever what will never pass. retryable: true does not mean "retry now": on rate_limited the Retry-After header says when.

Keep the requestId (also in the X-Request-Id header) in your logs. It is how we find, on our side, what happened on that exact call.

Waiting for the result without polling

GET /v1/transcripts/{id}?wait=30

The response is held until the transcript finishes, and comes back the moment it does. One request instead of a polling loop. The ceiling is 60 seconds; the wait expiring is not an error (you get the transcript still in processing and call again).

While it is processing, the response carries Retry-After with the interval worth waiting before asking again.

For many files at once, webhookUrl is still cheaper: one delivery per job instead of one open connection per job.

Cancelling

POST /v1/transcripts/{id}/cancel

Stops a transcript that is still processing. It becomes canceled, terminal, and is never charged; the credit it was holding (reserved in GET /v1/balance) is released immediately.

Cancelling is about the charge, not the machine: audio already on a GPU finishes the work in flight and the result is discarded. None of it reaches you or your invoice.

Paging a long history

GET /v1/transcripts?cursor=<nextCursor>&perPage=100

The cursor never skips or repeats a transcript when new ones arrive while you page, which is exactly what offsets do. It also does not count the account total: total comes back null under a cursor, because that count is the cost the cursor exists to avoid.

?page= keeps working for integrations already built on it.

Asking for the limits instead of hardcoding them

GET /v1/limits

Maximum audio duration, accepted formats, upload / chunk / request-body ceilings, glossary limits, and how many transcripts this account can run at once. Reading it at startup avoids discovering a ceiling after the file is already uploaded.

Bursts

Every response from a limited route carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. With those, your queue slows down before hitting the ceiling instead of finding it by being refused. Codes and details in Errors.

On this page