axios rejects Sume 409 and 429: read response.data.error.code
axios rejects any status outside 200-299 by default, so a Sume 409 or 429 throws. Read error.response.data.error.code, and use AbortSignal for timeouts.

Short answer
By default axios rejects the promise when the status code is outside 200 to 299, per its request config docs. A Sume 409, 429 or 402 therefore throws, and the useful part is in error.response.data.error.code. Pass an AbortSignal for cancellation, since the docs say the signal option lets you cancel with the AbortController API.
What the error object holds
Sume error bodies have one shape: an error object with code, message, request_id and details. In axios that body is on error.response.data, and the status is on error.response.status. Branch on the code, not the message text, because the code is the stable part.
Match the code to a decision. The common ones are listed on the Sume errors page, and a handful drive almost every retry choice.
| Status | Code | Decision |
|---|---|---|
| 400 | invalid_request | Fix the request; do not retry |
| 401 | unauthorized | Fix the credential; send only one of Authorization or x-api-key |
| 402 | insufficient_credits | Add balance or wait; do not retry |
| 404 | not_found | Check the id and the key's member |
| 409 | job_not_completed and related | Keep polling or read the job record |
| 429 | rate_limited or queue_full | Wait for retry-after, then retry with the same key |
| 503 | provider_capacity_exceeded | Retry later with backoff |
Handling it
Catch the error, check axios.isAxiosError, and pull the code out of the response. The sample submits an image job with an Idempotency-Key and prints the code on failure. Note that timeout is documented in milliseconds and aborts the request when exceeded; the docs do not state a default, so set one explicitly rather than assuming.
A timeout or abort stops your wait, not the job. The submit may already have been accepted, so retry only with the same Idempotency-Key.
import axios from "axios";
const key = crypto.randomUUID();
try {
const { data } = await axios.post(
"https://api.sume.com/v1/image-1.0/generate",
{ prompt: "A matte black bottle on marble", mode: "async" },
{
headers: { "x-api-key": process.env.SUME_API_KEY, "Idempotency-Key": key },
timeout: 30000,
signal: AbortSignal.timeout(35000),
},
);
console.log(data);
} catch (err) {
if (axios.isAxiosError(err) && err.response) {
console.error(err.response.status, err.response.data?.error?.code);
} else {
throw err;
}
}
Using validateStatus
The same docs describe validateStatus. If you set it to accept 409, axios resolves instead of throwing and you inspect the status yourself. That can be convenient for a poller that treats job_not_completed as a normal answer, but it moves the burden onto you: every call site must check the status before reading data. A narrow validateStatus, scoped to the one request that expects a 409, is safer than a global change.
Retries are a separate layer
axios itself does not decide whether to retry. If you add a retry plugin, apply the same rule as the Sume SDK: replay GET and HEAD freely, and replay POST only when it carries an Idempotency-Key. Do not retry 400, 401, 402 or 404, and honor retry-after on a 429. Then poll the status route until terminal is true rather than waiting inside one request.
Logging without leaking
When you log a failure, include error.request_id and the status, and leave out the headers. The request headers hold your API key, and axios error objects can carry the request config. Log a small object you build yourself rather than the raw error, so the key never lands in a log aggregator. The request_id is the piece to quote when you ask for help diagnosing a failure.
Sources
Related posts
More in Developers
- Batch of 30-second Seedance clips: Pro, Startup and Scale queues
How many 30-second Seedance 2.5 jobs can a Sume workspace hold? Plan concurrency and queue limits, what happens at job 25 on Pro, and a safe submit loop.
- Batch the shots of a micro-drama episode in Python, safe to re-run
A 25 line Python script submits an episode's Seedance 2.5 shots with one Idempotency-Key per shot and a job ledger, so a crash and re-run never double-bills.
- Benchmark TTS latency yourself: submit to finished file in Python
Vendors quote 45 ms or 150 ms; your pipeline waits for a file. A Python harness that times Sume TTS jobs from submit to a finished state, with p50 and p95.
- Bruno collection that polls a Sume job until it is terminal
Use bru.setNextRequest and bru.sleep in a Bruno post-response script to poll a Sume job's status route with a bounded attempt count, then fetch the result.
Written by Sume