Hash the avatar payload into your Idempotency-Key, adopt the 409 job

Hash the avatar request into the Idempotency-Key: a retry returns the same job (idempotency_hit true); a 409 names the job holding the key.

4 min readSume
All posts

For batch avatar work, build the Idempotency-Key from a hash of the request body. A repeat of the same request then returns the original job with idempotency_hit: true instead of billing a second render, and a changed script produces a new key and a new job. If Sume answers 409 idempotency_conflict, the error details name the job that already holds the key, so you adopt it instead of creating another.

What the contract says

The OpenAPI defines Idempotency-Key as an optional header of 1-255 printable ASCII characters on paid job creation. Reusing a key with the same operation and normalized payload returns the original job with idempotency_hit: true. Reusing it for a different operation or payload returns 409 idempotency_conflict, and error.details names the holder: job_id, job_type, job_status, status_url and result_url. The docs add that you should reuse a key only for an exact retry.

Idempotency behavior on avatar submits (Sume OpenAPI, read 2026-10-05)
You sendSume doesYour move
Same key, same payloadReturns original job, idempotency_hit trueKeep polling that job
Same key, different payload409 idempotency_conflict with the holder jobAdopt the holder or choose a new key
New key, same payloadCreates a new paid jobAvoid, this double-bills
No key, network timeoutUnknown whether a job existsNever resubmit blind

Key from the payload

A key derived from the content makes duplicate detection automatic in a queue worker. Serialize the body with sorted keys, hash it, and prefix it with the purpose. 255 characters is plenty for a 64-character digest. Add an explicit version when you want a deliberate fresh render of an unchanged script.

import hashlib, json, os, urllib.error, urllib.request

def key_for(body, version="v1"):
    blob = json.dumps(body, sort_keys=True, separators=(",", ":"))
    return "avatar-" + version + "-" + hashlib.sha256(blob.encode()).hexdigest()

def submit(body):
    req = urllib.request.Request(
        "https://api.sume.com/v1/avatar-1.0/talking-video",
        data=json.dumps(body).encode(), method="POST",
        headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
                 "Content-Type": "application/json",
                 "Idempotency-Key": key_for(body),
                 "User-Agent": "idem/1.0"})
    try:
        with urllib.request.urlopen(req, timeout=60) as r:
            data = json.load(r)["data"]
            return data["job"], data["idempotency_hit"]
    except urllib.error.HTTPError as e:
        err = json.load(e).get("error", {})
        if e.code == 409 and err.get("code") == "idempotency_conflict":
            return err["details"], True
        raise

Cautions

  • The hash is over your body, but Sume compares a normalized payload. Two bodies that you hash differently may still count as the same on the server, and the reverse is not true, so a content hash errs on the safe side.
  • Do not put the script text or personal data in a key. Hash it. Keys can appear in logs and URLs on your side.
  • A hit means the job exists, not that it finished. Read next_action and poll or wait for the webhook.
  • If a job failed and you want to try again with the same body, change the version suffix on purpose so the retry is a visible decision and not an accident.

When this is not enough

Idempotency covers retries of the submit call. It does not deduplicate two different scripts that say the same thing, and it does not protect you from approving a wrong preview. Keep a record of avatar_video_id, key, and script version so a reviewer can trace each file back to the request that made it.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume