Hear2Text

Idempotency

A create that times out has probably succeeded. An Idempotency-Key is how you find out without paying for a second recording.

Why it matters

A create is the one request here that costs money. If your connection drops while the upload is finishing, you cannot tell from your side whether we accepted the recording — and a blind retry transcribes it twice, billing the minutes twice. That is what this header is for.

Sending a key

Put a unique string in Idempotency-Key on every POST /transcriptions. A UUID is ideal. It is optional, and strongly recommended — treat it as required in production code.

curl https://heartotext.com/api/public/v1/transcriptions \
  -H "Authorization: Bearer $H2T_API_KEY" \
  -H "Idempotency-Key: 9f1c2b7a-1f2e-4a0c-9f0b-2c1d3e4f5a6b" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/episode-42.mp3"}'

Replays

Send the same key with the same body again and you get the stored answer — the same status, the same recording id — with one extra header. No second recording is created and nothing is billed twice.

A replayed create
HTTP/1.1 201 Created
Idempotency-Replayed: true

This makes a retry safe to perform unconditionally after a timeout, a 5xx or a dropped connection. Your worker does not have to know whether the first attempt arrived.

Conflicts

  • Same key, different body → 409 idempotency_key_reused. This almost always means a key is being reused where a new one was meant; it is a bug in the caller, and we refuse rather than guess which recording you wanted.
  • Same key while the first request is still running → 409 idempotency_request_in_progress. Wait a few seconds and ask again — this is the shape a slow upload takes when a second worker picks up the same job.

Scope and lifetime

  • Keys are remembered for 24 hours. After that the same string is a fresh request and will create a second recording.
  • A key is scoped to one API key. Two of your keys using the same string do not collide — and neither do two different accounts.
  • Only POST /transcriptions uses it. The reads are already idempotent, and a delete answers the same way whether it ran once or twice.

Choosing a key

  • Generate it where the job is created, store it with the job, and reuse it on every attempt. A key generated inside the retry loop protects nothing.
  • A natural key works too — podcast-42-episode-7 — as long as it is unique per recording and you never change the body underneath it.
  • Up to 255 characters. Anything longer is a 422.