Sume SDK error classes: HTTP status, class and action in TypeScript

Map 401, 402, 403, 404, 409, 429 and 5xx to the SumeApiError subclass and the right action, and read code, requestId, retryable and retryAfterSeconds.

5 min readSume
All posts

The SDK turns an API error into a subclass of SumeApiError: SumeAuthenticationError for 401, SumeInsufficientCreditsError for 402, SumePermissionError for 403, SumeNotFoundError for 404, SumeConflictError for 409, SumeRateLimitError for 429 and SumeServerError for 5xx. Each carries code, requestId, retryable, retryAfterSeconds and nextAction, so one handler can decide between a retry, a fix and an alert.

The classes exist so a plain catch does not have to parse messages. If you want to handle only a rate limit, you can test for SumeRateLimitError and let everything else bubble up. If you want a single policy, test for SumeApiError and read retryable.

Two kinds of failure

Generated operations resolve to { data, error, response } and do not throw, so the class matters mostly for the helpers and for code that wraps the result in an error. The wait helpers are different. They throw SumeRunRequestError, SumeRunTimeoutError, SumeJobTimeoutError and SumeJobRequestError, and these are not SumeApiError subclasses, so an instanceof SumeNotFoundError check will not catch them.

The practical consequence is that a try block around a wait helper needs two kinds of catch branches. One covers API errors thrown by your own direct calls. The other covers the helper errors, including the timeout errors. A timeout error means that your wait ended, and it does not mean that the paid work stopped, so resume with the same id and do not submit again.

From status to action

Use the table to pick the action by class, and use retryable from the error as the final word.

Two of these deserve a special note. A 402 is never retryable, because the balance cannot cover the reservation, and a retry only burns time. A 409 depends on the code. idempotency_conflict means you reused a key with a different body, and job_not_completed means you asked for a result too early and should poll the status instead.

Error class, status and action (read 2026-10-05)
StatusClassAction
401SumeAuthenticationErrorFix the key, and send only one credential header
402SumeInsufficientCreditsErrorStop, add balance or choose a cheaper request
403SumePermissionErrorUse a key with the needed scope
404SumeNotFoundErrorCheck the id and the id family
409SumeConflictErrorRead the code, for example idempotency_conflict
429SumeRateLimitErrorWait for retryAfterSeconds, then retry
5xxSumeServerErrorRetry with backoff, and keep the idempotency key

One decision function

The function below turns any thrown value into a decision. It does not import the SDK, so you can paste it into a test file. In your app, replace the structural check with instanceof SumeApiError.

The default of two seconds in the sample is only a fallback for the case where the server sent no hint. When retryAfterSeconds is present, it always wins, and the job status route adds its own next_poll_after_seconds, which the wait helpers also respect.

interface ApiErrorLike {
  code: string;
  requestId?: string;
  retryable?: boolean;
  retryAfterSeconds?: number;
}

type Decision = { action: "retry" | "stop"; waitSeconds: number; log: string };

export function decide(err: ApiErrorLike): Decision {
  const log = `${err.code} ${err.requestId ?? "no-request-id"}`;
  if (err.retryable) {
    return { action: "retry", waitSeconds: err.retryAfterSeconds ?? 2, log };
  }
  return { action: "stop", waitSeconds: 0, log };
}

console.log(decide({ code: "rate_limited", retryable: true, retryAfterSeconds: 7 }));
console.log(decide({ code: "insufficient_credits", requestId: "req_1" }));

What to log

Always log requestId. It is the id support can search for, and it is safe to paste into a ticket. Do not log the Authorization header or the key. Also keep code as a string and not as a free text message, because messages change and codes are the stable part of the error envelope.

For a batch, count the decisions by code and print the totals at the end. A line such as three rate limits and one credit error tells an on call person what to do without reading each log entry.

Do not stack retries

A retry of a paid POST is only safe with an Idempotency-Key. The client enforces that rule by itself. With maxRetries at its default of 2, it retries 408, 429 and 5xx, honours retry-after up to 60 seconds and adds about 20 percent jitter, and it retries a POST only when the key is present. Your own retry on top of that doubles the attempts, so pick one layer.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume