Retry a video-trim: same key for the same body, new key for new start

A trim that timed out can be retried with the same Idempotency-Key and body. Changing start, duration or precision is a new operation and needs a new key.

4 min readSume
All posts

When a POST /v1/video-trim call times out on your side, retry it with the same Idempotency-Key and exactly the same body: you get the original job back and are billed once. When you change what you are asking for, such as start, duration, end, precision or output, treat it as a new operation with a new key. Reusing the key with a different payload returns 409 idempotency_conflict.

The rule, in terms of trim fields

The trim docs require an Idempotency-Key, and the general rule on the jobs page is to use the same key only for the same operation and payload. A trim job costs $0.02, so a duplicate is cheap, but a stream of duplicates across a batch is not.

Same key or new key on a trim retry (read 2026-10-05)
What changedKeyWhat the API does
Nothing; the HTTP call timed outSameReturns the original job, no second charge
start moved from 2 to 3NewDifferent operation; same key gives 409 idempotency_conflict
duration 8 to 10NewDifferent operation
precision exact to keyframeNewDifferent operation
Source clip replacedNewDifferent input

Distinguishing retry from redo

Hash the request body on your side and store it with the key. On a retry, compare the stored hash with the new body. If they match, send the stored key. If they differ, create a new key and record both. That keeps the audit trail honest and avoids the 409 your own code caused.

A different failure needs a different action. If the trim job itself failed with a refusal code, fixing the request is a new operation. The stable codes are video_trim_range_required, video_trim_range_conflict, video_trim_range_empty, video_trim_output_requires_exact, ffmpeg_fields_rejected, unsupported_media_source, source_not_found, unsupported_media_type and source_duration_exceeded. None of them is cured by resending the same body.

What not to do

Do not use the HTTP timeout as evidence that no job exists. The job may have been created and be running. If you stored a job id, read GET /v1/jobs/:id/status; if you did not, resend with the same key so that you get the job back. Do not generate a new random key per attempt, which is how a flaky network turns one cut into several billed jobs.

When the response is a 202, poll the job envelope. The trim default mode is async; mode: "sync" waits at most 30 seconds for a 200 with the completed job.

A small helper

The easiest way to stay consistent is to compute the key from the request. Hash the canonical JSON of the body with a stable field order, and use trim-<hash> as the key. The same body then always gives the same key, and a new start always gives a new one. The hash must cover every field that changes the output: video_url, start, end or duration, precision and output.

This also makes a deliberate redo easy. If you want a fresh cut with the same times, add a counter to the hash input. The counter is a decision you recorded, not a side effect of a retry.

Check the response on the retry: the same job id as the first attempt confirms the replay.

  • Hash every field that changes the output.
  • Same body, same key.
  • Deliberate redo: add a counter.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume