Idempotency in practice
POST /v1/conversions honours an Idempotency-Key header so that a retried create returns the first job instead of making a second one. This page describes exactly what the server does with the key, including the cases it does not guard against, and how to recover when the response to the first call was lost. The short version of the contract is in the reference.
How the server treats the key
- Only create is keyed.
POST /v1/conversionsis the only endpoint that reads the header. Every other endpoint ignores it, whether or not a client sends one. - The header is optional and is at most 255 characters. Use a UUID.
- Scope is your account. The server looks a key up by account and key together, and the pair is unique. Two API keys on the same account share one key space: a key used with one API key is a replay when presented with the other. Two accounts never collide.
- A replay answers
200, not201, with{ "id": "…", "duplicate": true }. It carries nouploadUrl. Nothing new is created: no second job, no second concurrency slot, no second reservation of minutes. - The request body is not compared. The server finds the conversion by key and returns it. If you send the same key with a different
filename,presetorprogramme, you get200 duplicate: truefor the first conversion and your new parameters are ignored. There is no mismatch error. Use one key per logical create and never reuse a key for a different file. - There is no expiry. The key is stored on the conversion row, and the lookup has no time window. Rows are soft-deleted rather than removed, so a key keeps resolving to its conversion after you delete it. Do not recycle keys.
- Only a successful create stores the key. A create that is refused, for example with
quota-exhaustedorconcurrency-limit, leaves nothing behind. Retrying it with the same key is a fresh attempt.
What is checked before the lookup
The route validates the body first, so a bad create is refused the same way every time, with or without a key: a missing filename, a non-positive durationSec, a bad preset, programme, profile or source, a source over the per-file duration or size cap. After those, and before the concurrency and minutes checks, the server looks up the key. So a replay is answered even when the account is now at its concurrency limit or out of minutes.
Racing requests
The key is written in a separate step just after the conversion row is created. Two requests carrying the same key that arrive within milliseconds of each other can both create a job, and the one that loses the race is left without the key. Send the retry after the first request has failed or timed out, not alongside it. A job left over from such a race sits in uploading and is failed by the server without costing minutes.
When the first response was lost
The upload URL appears only in the first response. If you never saw it, you have a conversion in uploading and no way to send it bytes through that URL. You have two ways out:
- Open a multipart upload. While the conversion is
uploading,POST /v1/conversions/{id}/multipart/initreturns fresh part URLs. Upload the master as parts, complete it, then analyze. This works for any size and is the recovery the SDK's multipart helpers are built on. See Uploading large masters. - Start over. Delete the conversion with
DELETE /v1/conversions/{id}and create a new one with a new key.
In both cases GET /v1/conversions/{id} tells you the status first.
In the SDK and the CLI
conversions.create() always sends an Idempotency-Key: a random UUID, or the idempotencyKey you pass. It retries on 429, 5xx and network errors with the same key, so a retry cannot create a second job. When the server answers duplicate: true, create() skips the upload and the analyze call, because the first call already did them, and the returned job has uploaded === false.
That last point matters if the first attempt died before it finished uploading. A job you did not upload to is still in uploading. Check job.uploaded, and when it is false read the conversion's status before deciding whether to upload (through multipart) or start over.
const job = await esfera.conversions.create({
file: "master.wav",
preset: "esfera",
idempotencyKey: `nightly-${date}-master`, // stable per logical job, so a restarted process reuses it
});
if (!job.uploaded) {
const c = await job.refresh();
console.log("replay: the first call created", c.id, "in status", c.status);
}A stable key per logical job is what lets a restarted process find its earlier conversion. Make it specific to the file and the run, and never reuse it for another file.
Related
- Idempotency reference.
- Auto-start and two-step: create, upload, analyze.
POST /v1/conversions.