Handle errors
The SuqoError hierarchy in the SUQO TypeScript SDK.
Every error the SDK throws is an instance of SuqoError. The base class itself is never thrown directly — always one of the subclasses below. Check instanceof, never the error's message string or the raw response shape — those aren't part of the contract and can change wording without notice.
import { SuqoError } from "@suqo/sdk";
try {
await suqo.subscriptions.create({ /* ... */ });
} catch (err) {
if (err instanceof SuqoError) {
console.error(err.name, err.message, err.status);
}
}The hierarchy
| Class | When | Notes |
|---|---|---|
SuqoConfigError | Constructing SuqoClient with a malformed key, or a baseUrl that disagrees with it; or calling customers.retrieve()/customers.update() with an empty, "." or ".." id | Thrown before any request — synchronously from the constructor, as a rejected promise from the customer methods. See Authentication |
AuthenticationError | 401 | Key is missing, malformed, or inactive |
KycRequiredError | 403 | Seller hasn't completed KYC. Carries kycStatus, if the response included one |
ValidationError | 400 | See below — two different body shapes, normalized into one class |
NotFoundError | 404 | Resource doesn't exist, or doesn't belong to your account |
RateLimitError | 429 | Reserved — see Rate Limiting; never thrown until the API enforces limits |
ServerError | 5xx (and any unmapped status) | Unexpected failure on SUQO's side |
NetworkError | No response at all | Covers both a genuine network failure and a timeout — there's no separate timeout class |
Every subclass carries whatever the base class does:
interface SuqoErrorOptions {
status?: number; // HTTP status, if there was a response
rawBody?: unknown; // the raw parsed response body, if any
requestId?: string; // SUQO's request id for this call, if the response included one
}ValidationError — two wire shapes, one class
A 400 comes back in one of two shapes on the wire; the SDK normalizes both so you only ever need instanceof ValidationError, never a shape check of your own:
import { ValidationError } from "@suqo/sdk";
try {
await suqo.subscriptions.create({ /* ... */ });
} catch (err) {
if (err instanceof ValidationError) {
if (Object.keys(err.fieldErrors).length > 0) {
// field-keyed body — e.g. { phone: ["This field is required."] }
console.error(err.fieldErrors);
} else {
// detail-shaped body instead — e.g. the duplicate-active-subscription case
console.error(err.message);
}
}
}fieldErrors is always a plain object (never undefined) — empty when the failure used the detail shape instead. Nested validation (e.g. on subscriptions.create()'s customer payload) comes through as dot-path keys like customer.phone, already renamed from the wire's client.phone.
Writes aren't automatically retried
Reads (list, retrieve) retry on NetworkError, 429, and 5xx with backoff. Writes (subscriptions.create/cancel/updateBillingCycle/resume, customers.create/update) never retry automatically — a blindly-retried write could double-act (e.g. a duplicate subscription) since the API has no idempotency-key support yet. See Idempotency for what changes once it does, and Rate Limiting for RateLimitError's retryAfter field once 429 responses start arriving.