Idempotency-Key from a sha256 of the body: Seedance 2.5 retries

Retry a timed-out POST /v1/videos without paying twice by deriving Idempotency-Key from the request body. Python code plus the 409 conflict rule on Sume.

4 min readSume
All posts

A deterministic Idempotency-Key is a hash of the exact request body plus a label for your intent, so that a retry after a timeout always sends the same key and Sume returns the original job instead of a second paid one. For POST /v1/videos, send it as the Idempotency-Key header and never reuse it with a different body.

What Sume guarantees

Sume documents Idempotency-Key as supported on POST /v1/videos. The OpenRouter video route has no equivalent, which is a listed difference. A replay returns the original job, and for sume/auto it also resolves to the same route and the same price, because auto resolution is a pure function of the request and the catalog version.

If you reuse a key with a different body, the API answers 409 and does not create a job. The generation admission page names the code idempotency_conflict. That is the safety rail, and it is also why a random key per attempt is the wrong design: a retry would look like a brand-new job.

Think of the key as the receipt for one decision. You decided to render this storyboard beat once, and the key lets every retry, from any worker, prove it is the same decision. Without it the API cannot tell a nervous retry from a deliberate second order, so it has to treat them alike and charge both.

Random keys versus derived keys

There are two sound strategies. Store a UUID with the intent before you send, or derive the key from the content. Derived keys need no storage, which is useful when a worker crashes between building the request and recording it.

Key strategies for POST /v1/videos (Sume docs, read 2026-10-05)
StrategySurvives a crashSame body twiceRisk
Fresh uuid4 per attemptNoTwo paid jobsDouble billing on retry
uuid4 stored before sendYesOne jobNeeds a write first
sha256 of body plus intent labelYesOne jobTwo real intents collide without a label
Counter or timestampNoTwo paid jobsDrifts on retry

Code

The label is the part people forget. If you genuinely want two takes of one prompt, change the label, for example take-1 and take-2. Key order matters for the hash, so serialize with sorted keys.

import hashlib, json

def idem_key(body: dict, label: str) -> str:
    canon = json.dumps(body, sort_keys=True, separators=(",", ":"))
    return label + "-" + hashlib.sha256(canon.encode()).hexdigest()[:32]

body = {"model": "seedance-2.5", "prompt": "Rooftop sunrise, slow orbit",
        "duration": 30, "resolution": "1080p", "aspect_ratio": "16:9"}
k1 = idem_key(body, "campaign-42-take-1")
k2 = idem_key(dict(reversed(list(body.items()))), "campaign-42-take-1")
k3 = idem_key(body, "campaign-42-take-2")
assert k1 == k2          # same intent, same key, any dict order
assert k1 != k3          # a second take is a different key
print(k1)  # send as the Idempotency-Key header on POST /v1/videos

Where retries happen

The key earns its keep in four places. A client timeout, where the response is lost but the job exists. A 429 rate_limited, where Sume tells you to retry with the same key. A 429 queue_full, where capacity opens later. And 503 provider_capacity_exceeded, where Sume could not dispatch. In each case the docs say to retry with the same key, not a fresh one.

The cost of a wrong guess is real for long clips. Seedance 2.5 takes 4 to 30 seconds per job, so a duplicate 30-second render is a full price duplicate.

A worked example makes the stakes clear. A 30-second clip at 1080p on Seedance 2.5 is as long as a single job gets on the video catalog, and a retry storm after a network blip can resend the same submit three times in a minute. With a derived key, those three requests collapse to one job. With a fresh key each time, you own three renders and pay for three, and you find out on the invoice, not in the logs.

Rules to keep

  • One key per intent. Change the body, change the key.
  • Keep keys under your own prefix so you can search your logs for a campaign.
  • Do not put secrets or personal data in the key, since it appears in logs.
  • Also store the returned job id. The key protects the submit, and the id is how you poll.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume