StoreKitdocs
Concepts

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 Product

This 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 error

StorefrontError

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:

CodeTypical cause
BAD_REQUESTInvalid input.
UNAUTHORIZEDMissing/invalid store key or session.
FORBIDDENAuthenticated but not allowed.
NOT_FOUNDNo such resource.
CONFLICTState conflict (e.g. INSUFFICIENT_STOCK).
GONEResource expired (e.g. STALE_CHECKOUT).
RATE_LIMITEDToo many requests — see retryAfter.
NETWORK / TIMEOUTThe request never reached the API or was aborted.
INTERNAL / BAD_GATEWAY / SERVICE_UNAVAILABLEServer-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.

On this page