Sume SDK error.retryable: server flag first, status only as fallback
SumeApiError.retryable uses the error envelope's retryable flag when present and falls back to 408, 429 or 5xx otherwise. A 409 is never retried by status.

When a Sume SDK call fails, can you trust error.retryable? Yes, and it is computed in a fixed order. In @sume-com/sdk@0.2.0, toSumeApiError uses the retryable value from the server's error envelope when the server sent one. Only when the envelope has no such field does it fall back to the HTTP status: 408, 429 and any 5xx are retryable, and so is a failure with no response at all, such as a dropped connection. A 409 is never retryable by status, because repeating the call returns the same answer.
The envelope fields come from the errors and credits reference: code, message, request_id, plus retryable, retry_after_seconds and next_action where they apply. The SDK copies them to code, requestId, retryable, retryAfterSeconds and nextAction on the thrown error.
Why the server's flag wins
A status code cannot tell a transient 503 from one that says do not retry aggressively, and Sume uses both. The documented backpressure errors, such as provider_capacity_exceeded, say to retry later with the same idempotency key, while provider_not_configured says not to retry aggressively. If a client retried on status alone it would hammer the second kind. Reading the flag first lets the server steer.
The order also explains why a 409 sits outside the status fallback. On this API a 409 means an idempotency or run-state conflict. idempotency_conflict says you reused a key with a different payload, which no number of retries will fix, and job_not_completed on a result read says the job has not finished, which is a signal to poll the status instead.
| Situation | retryable |
|---|---|
| Envelope carries retryable: true | true |
| Envelope carries retryable: false on a 503 | false |
| No envelope, status 429 | true |
| No envelope, status 409 | false |
| No response (transport failure) | true |
Using it in your own retry loop
The client already retries GETs, and POSTs that carry an Idempotency-Key, a couple of times. Your outer loop is for the cases that survive that. The helper below retries only when the typed error says so, waits the server's retryAfterSeconds when given, and keeps one idempotency key for every attempt so a retry can never create a second paid job.
import { createSumeClient, generateImageV1, SumeApiError } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY ?? "", maxRetries: 0 });
export async function submitWithKey(prompt: string, attempts = 4) {
const key = crypto.randomUUID();
for (let i = 0; ; i += 1) {
const res = await generateImageV1({
client,
headers: { "idempotency-key": key },
body: { prompt, mode: "async" },
});
if (!res.error && res.data) return res.data.data.request_id;
const err = res.error as { error?: { retryable?: boolean; retry_after_seconds?: number; code?: string } };
const info = err?.error;
const status = res.response?.status;
const retryable = info?.retryable ?? (status === 408 || status === 429 || (status ?? 0) >= 500);
if (!retryable || i + 1 >= attempts) {
throw new Error(`submit failed: ${info?.code ?? status}`);
}
await new Promise((r) => setTimeout(r, (info?.retry_after_seconds ?? 2 ** i) * 1000));
}
}Checks before you ship it
- Log
codeandrequest_idon every failure so support can find the call. - Treat a 402
insufficient_creditsas a stop, not a retry. It maps toSumeInsufficientCreditsErrorand topping up is the fix. - Cap attempts. A server that keeps saying retry later still deserves a deadline on your side.
- Never mint a new idempotency key inside the loop.
Related posts
More in Developers
- Sume STT language_code: set a hint or omit it for auto-detect?
language_code is optional on Sume STT. Omit it to auto-detect, pass a BCP-47 hint like en or ko when you know the language. A quick way to choose.
- Sume STT takes 10 minutes per request: a 90-minute file is 9 jobs
Sume speech-to-text caps a request at 600 seconds of audio, so a 90-minute recording is nine requests at up to $0.10 each, $0.90 in total.
- Turn Sume STT word timings into an SRT file in 30 lines of Python
Sume speech-to-text returns words[] with start and end seconds. Group them into SRT cues of 42 characters or 5 seconds, and see what the caption endpoint takes.
- Count Sume job replays with idempotency_hit after a client retry
A Sume job submit returns data.idempotency_hit. Log it after a retried video submit to prove the second call replayed the first job, not a new one.
Written by Sume