409 Conflict error: what it means and when to retry
A 409 Conflict means the request clashed with the server's current state. Read the error code to choose: resend, wait for the job, or fix the call.

A 409 Conflict means your request clashed with the current state of the thing it targets, not that the request was malformed. Resending it only helps once that state changes, so read the error code first: some 409s clear in a second, some clear when a job finishes, and some need you to change the request.
The HTTP definition is quoted from MDN's 409 Conflict page, and the Sume codes come from its Errors and spend, Create a run and Jobs and results docs, all read on 2026-09-27.
What does a 409 Conflict mean?
MDN defines it as a request conflict with the current state of the target resource. Its examples range from uploading a file older than the one on the server to a server that refuses to run two jobs at once.
Error text like Request failed with status code 409 is how axios words a response it rejects for its status code; 409 Client Error: Conflict for url is the message Python Requests builds in raise_for_status(). Neither says why. The why is in the response body: axios puts it in error.response.data, and with Requests you read response.json().
Should I retry a 409?
Only once the conflict clears. For one Sume code that takes about a second; for others it takes a run finishing, and some never clear until you change the request. On Sume, group the codes by what clears them:
| What clears it | `error.code` | What to do |
|---|---|---|
| About a second | idempotency_key_in_use | Another request with the same key is in flight. Wait about a second and resend to receive the original run. |
| The run finishing | run_not_completed | You read result_url too early. Poll status_url, then read the result. |
| The run finishing | previous_run_not_terminal, run_not_terminal | You continued, or asked to redeliver, a run that is still running. Poll it, then call again. |
| The other run finishing | format_run_in_progress, action_run_in_progress | You sent on_active_run: "reject" while a run was active. Wait, or drop reject. |
| Nothing: fix the request | idempotency_conflict | The key was already used with a different body. Fix how you derive keys; don't resend as is. |
| Nothing: the owner must act | format_inactive, format_api_trigger_disabled | The Format's owner set it Inactive or turned off API calls, on the Format page's API tab. |
| Nothing: it's too late | job_generation_already_started | The cancel came after generation began; the job runs to completion. |
| A fresh read | format_content_sha_mismatch, format_package_sha_mismatch | Someone committed to the Format package first. Re-read it and retry with the current sha. |
Why does the same Idempotency-Key return 409?
Either the first request with that key is still in flight (idempotency_key_in_use, above), or the body changed. A replay of the same key with the same body returns the original run with 200; the same key with a different body, including a different instruction or attachment list, is 409 idempotency_conflict, and nothing runs. In current code the comparison also covers communication, so a new webhook_url makes it a different body too. Derive keys from the thing being made, plus a version you bump on purpose; idempotency keys for AI video APIs covers key design.
Is job_not_completed always worth waiting for?
No. GET /v1/jobs/{id}/result answers 409 job_not_completed for failed and canceled jobs too, because there is no result to hand back. In current code that error is marked retryable: true with next_action: poll_status even then, so don't loop on the flag: read the job record at GET /v1/jobs/{id} and stop once its status is terminal. Format runs differ: run_not_completed comes back only while the run is still in flight, with its current status in details.status, so waiting is the right move there.
How do I handle a 409 in code?
Branch on the HTTP status first, then on error.code, a lowercase token you can switch on; message is for humans and may change. retryable says whether resending the same request can succeed. The TypeScript SDK won't resend a 409 for you: its client retries 408, 429 and 5xx, and it raises a 409 as SumeConflictError, carrying code, retryable and details (its run helpers throw SumeRunRequestError instead). A small decision function keeps the table above in code:
type NextStep = "resend" | "wait" | "check-job" | "fix";
function nextStepFor409(code: string): NextStep {
switch (code) {
case "idempotency_key_in_use":
return "resend"; // after about a second: same key, same body
case "run_not_completed":
case "previous_run_not_terminal":
case "run_not_terminal":
case "format_run_in_progress":
case "action_run_in_progress":
return "wait"; // poll until the run is terminal, then call again
case "job_not_completed":
return "check-job"; // read GET /v1/jobs/{id}: it may have failed
default:
return "fix"; // resending as is won't help: see the table
}
}
// const { error } = await res.json(); when res.status === 409
// nextStepFor409(error.code);Sources
- MDN: 409 Conflict (read 2026-09-27)
- axios source: settle.js (read 2026-09-27)
- axios: Handling errors (read 2026-09-27)
- Requests source: requests.models (read 2026-09-27)
- Errors and spend
- Create a run
- Runs and results
- Jobs and results
- Advanced: run a schedule via API
- Editing a Format package
- Waiting for runs and jobs
Related posts
More in Developers
- 415 Unsupported Media Type: causes and the fix
A 415 Unsupported Media Type error means the server refused your request body's format. Fix the Content-Type header: send JSON as application/json.
- Batch transcription API: transcribe many audio files
Batch transcription by API is a loop: one speech-to-text job per file, keyed by the file's id, collected by webhook or polling. How it works on Sume.
- Bulk image generation: script hundreds of AI images via API
Send one image request per row, each with its own Idempotency-Key, async mode, and up to four images per call. Your plan's concurrency sets the pace.
- Can multiple people use the same API key?
They can, but they then share its rate limit, usage record, and revocation. Give each person or service its own key, and know what stays shared.
Written by Sume