Sume API errors: a 13-line function that says retry or fix

Map a failed Sume response to out-of-credit, wait, back off, fix the key, retry with the same key, or fix the request, using the documented error envelope.

4 min readSume
All posts

Every failed Sume API response carries the same envelope, { "error": { "code", "message", "request_id", "details" } }, so one function can decide what a caller should do next: flag the balance on 402, wait on queue_full, back off on rate_limited, fix your key on idempotency_conflict, retry with the same key on 5xx, and fix the request for everything else. The 13-line function below does that and returns a small object you can log or branch on.

The split matters because the retry advice differs per code. A blind retry on 400 or 401 never succeeds. A retry on a paid submit without the same Idempotency-Key can bill twice. And two different 429s need different waits.

What the docs define

Retry advice for queue and capacity errors in the docs is to retry with the same idempotency key, and to use retry-after when present.

Documented codes and the advice used in the function, read 2026-10-02 from docs.sume.com
Status and codeMeaningFunction returns
402 insufficient_creditsBalance cannot reserve the generationout_of_credit
429 queue_fullConcurrency plus queue capacity is fullwait_for_capacity
429 rate_limitedToo many requests in the windowback_off
409 idempotency_conflictKey reused for a different payloadfix_key
5xx, provider_capacity_exceededDispatch queue or runtime unavailableretry_same_key
400, 401, 403, 404, 415Request, auth, scope or media type problemfix_request

The function

It parses the body defensively, because a proxy can return an HTML 502 with no envelope, and prefers the retry-after header over the body's retry_after_seconds when both exist. Log requestId and nothing else when you contact support; the docs say it is safe to share.

// Decide what to do with a failed Sume response, from the documented error envelope.
export async function classify(res) {
  const body = await res.json().catch(() => null); // a proxy 502 may return HTML
  const e = body?.error ?? {};
  const base = { status: res.status, code: e.code ?? "unknown_error", requestId: e.request_id ?? null };
  const retryAfter = Number(res.headers.get("retry-after")) || e.retry_after_seconds || null;
  if (res.status === 402 || e.code === "insufficient_credits") return { ...base, action: "out_of_credit" };
  if (e.code === "queue_full") return { ...base, action: "wait_for_capacity", retryAfter };
  if (res.status === 429) return { ...base, action: "back_off", retryAfter };
  if (e.code === "idempotency_conflict") return { ...base, action: "fix_key" }; // same key, different payload
  if (res.status >= 500 || e.retryable) return { ...base, action: "retry_same_key", retryAfter };
  return { ...base, action: "fix_request" }; // 400, 401, 403, 404, 415: retrying cannot help
}

Using it

Wire it in right after the fetch call.

  • Call it only when res.ok is false.
  • For back_off and wait_for_capacity, sleep for retryAfter seconds or a doubling default, then resend with the same Idempotency-Key.
  • For wait_for_capacity, stop adding work and poll existing jobs first; retrying harder does not open queue slots.
  • For out_of_credit, alert a human. Do not loop; the docs point to upgrading the plan or waiting for included Gen$, not a top-up flow.

Limits

I ran it against hand-built responses shaped like the documented envelope (402, both 429s, 409, 503, an HTML 502, 400), not against live failures. retryable and retry_after_seconds appear on job errors and in the SDK's envelope type; whether a given endpoint fills them is not guaranteed, so the function treats them as hints. A 409 can also be job_not_completed or job_generation_already_started, which this function reports as fix_request; handle those at the call site that cancels or fetches results.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume