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.

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.
| What changed | Key | What the API does |
|---|---|---|
| Nothing; the HTTP call timed out | Same | Returns the original job, no second charge |
start moved from 2 to 3 | New | Different operation; same key gives 409 idempotency_conflict |
duration 8 to 10 | New | Different operation |
precision exact to keyframe | New | Different operation |
| Source clip replaced | New | Different 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
- Rolling a webhook secret: Stripe's 24-hour overlap vs Sume's header
Stripe can keep an old signing secret live for up to 24 hours. Sume sends one sume-v1 entry per live secret; verify any match. Python verifier included.
- Route a Kling video job in Python: scene, motion clip or 30 seconds
A small Python router for Sume: performance copies go to Kling motion control, 4-15 s scenes to kling-3, and longer jobs to a catalog id that lists 30 s.
- Route low-confidence transcripts to review: STT language_probability
Sume STT returns language_code and language_probability. Flag results under a threshold you set and send them to a person. Python, about 10 cents per file.
- Ruby Net::HTTP never follows redirects: saving a Sume video
Net::HTTP returns the 302 from /v1/videos/{id}/content as-is. A 26-line Ruby script submits Seedance 2.5, polls, follows the redirect and saves the MP4.
Written by Sume