BFL 402 and 429 retry rules, and the same split on Sume
BFL raises on 402 and backs off on 429. Sume splits the same way: 402 insufficient_credits is a stop, 429 queue_full or rate_limited means wait and retry.

Use two rules: stop on 402 because credits are missing, and wait then retry on 429. BFL's error-handling example does exactly that (exponential backoff on 429, an exception on 402), and Sume's documented errors line up: 402 insufficient_credits is not retryable until the balance changes, while 429 means back off.
Sources: BFL's integration guidelines and Sume's Errors and credits, read 2026-10-01.
What does BFL's example do?
Its Python helper retries up to max_retries. On 429 it sleeps 2 ** attempt seconds and continues. On 402 it raises "Insufficient credits". Other 4xx and 5xx responses raise.
How does Sume classify the same errors?
| Status and code | Meaning in the docs | Client action |
|---|---|---|
402 insufficient_credits | Balance is not sufficient for the requested generation | Stop; change plan, wait for balance, or submit a cheaper request |
429 rate_limited | Request volume exceeded a limit | Back off, use retry-after when present |
429 queue_full | No accepted generation capacity left in the workspace | Wait for jobs to finish or cancel queued ones, retry with the same key |
409 idempotency_conflict | Key reused for a different payload | Reuse keys only for exact retries |
How do I retry safely?
The docs say to back off on 429, use retry-after when present, and not retry unsafe submits without an Idempotency-Key. error.details.scope is read or write, so you can tell polling pressure from submit pressure. Details in 429 and retry-after.
async function submit(body, key) {
for (let attempt = 0; attempt < 4; attempt++) {
const res = await fetch("https://api.sume.com/v1/image-1.0/generate", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.SUME_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(body),
});
if (res.status === 402) throw new Error("insufficient credits: stop");
if (res.status !== 429) return res.json();
const wait = Number(res.headers.get("retry-after")) || 2 ** attempt;
await new Promise((r) => setTimeout(r, wait * 1000));
}
throw new Error("still rate limited");
}What should I do about 402?
Do not loop. Surface it, check GET /v1/balance, and only resubmit after the balance can cover the estimate.
Sources
Related posts
More in Developers
- BFL polling_url on api.bfl.ai vs Sume status_url
BFL says to always poll the polling_url it returns. Sume's job envelope carries status_url and result_url for the same reason: follow them, do not build URLs.
- BFL webhooks vs polling_url, and Sume webhook mode with polling
BFL says webhook users need no polling_url change. On Sume, webhook mode still returns status_url, so verify the signed callback and keep polling as a backup.
- Boost dull video colors by API: the vibrance filter intensity
Sume's video filter allowlists vibrance, with intensity from -2 to 2 and a default of 0. A small positive value lifts muted color; a negative one mutes it.
- C2PA 2.3 editing history: what trim and filter return in Sume
Content Credentials 2.3 shows clearer edit history such as resizing, markup and redactions. Sume trim and filter return a new MP4; inspect never makes one.
Written by Sume