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.

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.
| Status | Class | Action |
|---|---|---|
| 401 | SumeAuthenticationError | Fix the key, and send only one credential header |
| 402 | SumeInsufficientCreditsError | Stop, add balance or choose a cheaper request |
| 403 | SumePermissionError | Use a key with the needed scope |
| 404 | SumeNotFoundError | Check the id and the id family |
| 409 | SumeConflictError | Read the code, for example idempotency_conflict |
| 429 | SumeRateLimitError | Wait for retryAfterSeconds, then retry |
| 5xx | SumeServerError | Retry 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
- Sume SDK retries: which requests it repeats and how long it waits
createSumeClient retries 408, 429 and 5xx up to maxRetries (default 2), honours retry-after up to 60 seconds, and replays a POST only with an Idempotency-Key.
- Sume STT silence split: it only runs after the last sentence end
Sume STT cuts at terminal punctuation first; the 0.5 s silence rule only splits the unpunctuated run after the last terminal, not earlier pauses.
- Sume sync mode: set your client timeout above 30 s, then poll
Sume sync waits at most 30 seconds. If your HTTP client gives up at 30 s too, you lose the job id. Use a 45 s timeout, then poll and reuse the Idempotency-Key.
- Sume TTS 1.0 or TTS Router: which endpoint for a voiceover?
Both bill $0.0475 per 1,000 characters. TTS 1.0 always runs sonic-3.6; the router makes you name a Sonic id. Pick by whether you need to pin the engine.
Written by Sume