Sume SDK errors by status: which class for 401, 402, 403, 404, 409

The @sume-com/sdk error classes by HTTP status, which fields they carry, and how run helpers differ from generated operations that resolve instead of throwing.

4 min readSume
All posts

In @sume-com/sdk, each non-2xx status maps to a SumeApiError subclass: 401 SumeAuthenticationError, 402 SumeInsufficientCreditsError, 403 SumePermissionError, 404 SumeNotFoundError, 409 SumeConflictError, 429 SumeRateLimitError, and 5xx SumeServerError. The run helpers are the catch: they always throw SumeRunRequestError, so you branch on its status or code.

The mapping

The Waiting for runs and jobs page lists the subclasses. Every one carries code, requestId, retryable, retryAfterSeconds, nextAction, details, and the raw body.

SDK error classes by HTTP status (Sume docs, read 2026-10-09)
HTTP statusClassTypical next step
401SumeAuthenticationErrorCheck the key and send one credential header
402SumeInsufficientCreditsErrorAdd funds or lower the cost
403SumePermissionErrorCheck the scope; scopes are fixed at key creation
404SumeNotFoundErrorWrong workspace or another member's job
409SumeConflictErrorRead the code: job_not_completed, idempotency_conflict, and others
429SumeRateLimitErrorWait for retryAfterSeconds
5xxSumeServerErrorRetry with the same Idempotency-Key

Which calls throw

Generated operations do not throw on an API error. They resolve with { data, error, response }. subscribeFormatRun and waitForRun do throw, because a poll loop has nowhere to put a non-result. waitForJob throws SumeJobTimeoutError or SumeJobRequestError, both carrying jobId.

A failed run is not an exception. The helpers resolve for any terminal status, so read status and error from the receipt.

A catch block that uses the fields

Log the requestId, which is safe to share with Sume support, and never log keys or signed URLs. The sample follows the SDK docs: it checks the status first, then reads retryAfterSeconds or retryable.

import { createSumeClient, subscribeFormatRun, SumeRunRequestError } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });

try {
  const run = await subscribeFormatRun({
    client,
    path: { handle: "acme", slug: "product-promo" },
    idempotencyKey: "order-8823-promo-v1",
    body: { input: { product_url: "https://shop.example.com/p/8823" } },
  });
  console.log(run.status);
} catch (error) {
  if (error instanceof SumeRunRequestError) {
    if (error.status === 402) console.error("add funds", error.requestId);
    else if (error.status === 429) console.error("retry in", error.retryAfterSeconds);
    else console.error(error.code, error.retryable, error.requestId);
  } else {
    throw error;
  }
}

The client retries for you, within limits

createSumeClient retries 408, 429, 5xx, and transport failures, two times by default, with exponential backoff and jitter, and it honors retry-after. It retries a POST only when the request carries an Idempotency-Key. Without a key, a replay starts and bills a second run, so subscribeFormatRun generates one for you unless you pass your own.

Handling each class

Retry only what is worth retrying. The SDK already retries 408, 429 and 5xx twice with backoff, and retries POST only when an Idempotency-Key is present, so adding your own loop on top multiplies attempts. Do not retry 401, 403 or 404; fix the key, the scope or the id.

A 402 means the wallet cannot cover the reserve. Show the user a top-up path rather than retrying. A 409 means the request conflicts with the current state, such as a cancel after generation started.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume