job_id, request_id, Idempotency-Key: which one goes in which column
Your Idempotency-Key is the unique key before submit, job_id or run_id is the poll key, and request_id dedupes webhooks and goes into support tickets.

Keep three different ids in three different columns. The Idempotency-Key is yours. You derive it before the submit and it makes the paid call safe to repeat. The job_id, or run_id for a run, is Sume's. It arrives in the submit response and is the key you poll with. The request_id is the id on webhook payloads and on error envelopes, and it is the one you dedupe deliveries on and quote to support.
A common failure comes from using the wrong id as a unique key. If you use the Sume id as the unique key of your intent table, you cannot write the row until the response arrives, and a crash between the call and the response leaves a paid job with no record. Using your own key, written first, closes that gap.
Why they get mixed up
The three look similar because a job webhook shows request_id and job_id with the same job_... value, and teams then assume they are one thing. They are not interchangeable. A run webhook carries a run_id and a request_id, and an error response carries a request_id for the failed call, which does not point to any job at all, because a rejected submit creates none.
Support is the second place where the mix-up costs time. When you contact support about a failed call, the request_id from the error envelope is what they can search. A job_id of a different job, or the key you derived, will not find the failed request. Log the error request_id on every non-2xx answer.
One role per column
Give each id one job in your schema, and keep the roles apart.
| Id | Who makes it | Use it for |
|---|---|---|
| Idempotency-Key | You, before the submit | Unique key on your intent table, safe retry of the paid call |
| job_id or run_id | Sume, in the submit response | Polling, result fetch, cancel, redeliver |
| request_id on a webhook | Sume, in the payload | Dedupe of deliveries |
| request_id on an error | Sume, in the error envelope | Quote in a support ticket |
A schema that keeps them apart
The table definition below follows that split. The intent row is written first, with the key, and the Sume id is filled in after the response. A unique index on the key means that two workers cannot start the same paid job.
Notice the partial unique index. It lets the sume_id column stay empty until the response arrives, and it still prevents two intents from claiming the same Sume job later. Add a column for the delivery request_id in a second table if you keep a record of webhooks.
import sqlite3
db = sqlite3.connect(":memory:")
db.execute('''create table intents (
idempotency_key text primary key,
sume_id text,
state text not null default 'pending')''')
db.execute("create unique index seen_events on intents(sume_id) where sume_id is not null")
def reserve(key):
cur = db.execute("insert into intents (idempotency_key) values (?) on conflict do nothing", (key,))
return cur.rowcount == 1
def attach(key, sume_id):
db.execute("update intents set sume_id = ?, state = 'submitted' where idempotency_key = ?", (sume_id, key))
print(reserve("acme-order-77-promo-v3"), reserve("acme-order-77-promo-v3"))
attach("acme-order-77-promo-v3", "job_demo")
print(db.execute("select * from intents").fetchall())
What the key does on the server side
With a Format run, the same key and the same body returns 200 with the original receipt and idempotency_hit: true, while a fresh run returns 202. The same key with a different body returns 409 idempotency_conflict. Without a key, every call is a new paid run. These rules make the key column the safest place to enforce one run per order.
The idempotency window is the reason to keep the key for as long as the order lives. A retry that comes hours later should still find the same key, and with a stable derivation from ids it will, even if your cache was cleared.
What to dedupe a webhook on
For webhooks, dedupe on the id inside the payload and not on the delivery attempt. Delivery is at least once, and a manual redelivery sends the same event again with a fresh timestamp and signature. A receiver that records request_id before it does any work will process each terminal event once.
Cancelled or skipped runs send no webhook, so they never create a delivery request_id row. Your reconciliation poll reads them by run_id, and that is a further reason to keep the Sume id in its own column.
Sources
Related posts
More in Developers
- 503 status_busy on GET /v1/jobs/{id}/status: back off and jitter
status_busy means Sume's job status read gate is full. Reads of the same job are shared; the cap is 100 distinct in-flight reads. Poll slower, add jitter.
- Sume jobs_wait outcome: wait_slice_expired is not a failed job
jobs_wait returns outcome terminal, wait_slice_expired or operator_stopped. Only terminal means the jobs ended; an expired slice says nothing about the jobs.
- jq one-liners for the video model catalog: ids, durations, 1080p
Two jq filters over GET /v1/videos/models: a table of ids with min and max seconds, and a filter for models that take 20 s at 1080p. Tested on a local copy.
- Kling 3 image-to-video on Sume: the first frame sets the shape
With a first frame, Sume's kling-3 builder does not send aspect_ratio. Crop the image to the shape you want before you submit. Here is the request.
Written by Sume