Errors & results
The { data, error } contract, typed error codes, and the API's error envelope.
The result contract
SDK methods don't throw by default. They return a discriminated result:
type Result<T> =
| { data: T; error: null }
| { data: null; error: StorefrontError };Always check error first:
const { data, error } = await storekit.products.get("nonexistent");
if (error) {
if (error.code === "NOT_FOUND") return null;
throw error; // or handle other codes
}
return data; // fully typed ProductThis keeps the happy path flat and forces you to consider failures — no try/catch pyramids.
Prefer throwing?
Set throwOnError: true when creating a client and methods will throw
StorefrontError instead of returning it:
const client = createStorefrontClient({ baseURL, throwOnError: true });
const product = await client.products.get("slug"); // throws on errorStorefrontError
Every error is a StorefrontError:
class StorefrontError extends Error {
code: StorefrontErrorCode; // e.g. "NOT_FOUND", "RATE_LIMITED"
status: number; // HTTP status (0 for network/abort)
retryAfter?: number; // seconds, from Retry-After on 429
details?: unknown; // raw error body, for debugging
}Type guards
Import guards instead of comparing strings by hand:
import {
isStorefrontError,
isRateLimited,
isUnauthorized,
isNotFound,
} from "@usestorekit/sdk";
if (isRateLimited(error)) {
await sleep((error.retryAfter ?? 1) * 1000);
}Error codes
Common code values:
| Code | Typical cause |
|---|---|
BAD_REQUEST | Invalid input. |
UNAUTHORIZED | Missing/invalid store key or session. |
FORBIDDEN | Authenticated but not allowed. |
NOT_FOUND | No such resource. |
CONFLICT | State conflict (e.g. INSUFFICIENT_STOCK). |
GONE | Resource expired (e.g. STALE_CHECKOUT). |
RATE_LIMITED | Too many requests — see retryAfter. |
NETWORK / TIMEOUT | The request never reached the API or was aborted. |
INTERNAL / BAD_GATEWAY / SERVICE_UNAVAILABLE | Server-side problem. |
The code is an open union — routes can return specific codes like
INSUFFICIENT_STOCK, COUPON_EXPIRED, or PAYMENT_UNAVAILABLE. Handle the
ones you care about and fall back on the HTTP-status families above.
On the wire: the API error envelope
If you call the API directly (without the SDK), every 4xx/5xx JSON response
has the same shape:
{ "error": "Product not found", "code": "NOT_FOUND" }The SDK turns this envelope into a StorefrontError for you.
One documented exception
POST /v1/coupons/validate always returns 200 with a
{ valid: boolean, reason? } union rather than an error envelope — an invalid
coupon isn't a request failure. The SDK surfaces it as data, not error.