ShopEazydevelopers
Libraries

Storefront client

@shopeazy/storefront-client: the typed client for the Storefront API.

@shopeazy/storefront-client is the typed client for the Storefront API. It has no runtime dependencies, uses fetch, and is ESM with types. It runs on Node 18 and later, in browsers and on edge runtimes.

It holds no platform credentials. Every call is HTTPS with a per-store key the merchant issued and can revoke. A site built on it can be handed to someone else by issuing one new key.

Install

The package is on npm under an MIT licence:

npm install @shopeazy/storefront-client

Create a client

import { createStorefrontClient } from '@shopeazy/storefront-client';

const shopeazy = createStorefrontClient({ apiKey: process.env.SHOPEAZY_API_KEY! });

const store = await shopeazy.store.get();
const { items: products } = await shopeazy.products.page();

The client throws if you construct it with a secret key (sez_sk_) in a browser. Use a publishable key (sez_pub_) in client code. See Storefront keys.

Options

createStorefrontClient({
  apiKey,
  baseUrl,         // default https://api.shopeazy.co
  shopperToken,    // a one-hour Firebase ID token, sent on every call
  shopperSession,  // a 14-day shopper session, sent on every call
  fetch,           // your framework's fetch, or a fake in tests
  timeoutMs,       // default 15000
  paths,           // 'current' (default) or 'planned'
});
OptionMeaning
baseUrlhttps://api.shopeazy.co by default.
shopperToken, shopperSessionA shopper credential on every call. See Shoppers
timeoutMsHow long to wait before the call fails with timeout
pathsWhich paths to call. planned calls the /v1/store/... paths. current, the default, calls the paths the API answered on before the move

The paths option

The Storefront API lives under /v1/store. The client calls the earlier paths by default, and paths: 'planned' switches to /v1/store/.... When the move is complete, a client release flips the default, and your code does not change. Until you upgrade, set paths: 'planned' to match these docs. See Versioning and deprecation.

What you can call

// Catalogue
await shopeazy.store.get();                         // membership gate, auth.tenantId, payments.canCharge
await shopeazy.products.page({ kind: 'service' });  // physical by default. 'all' for everything
await shopeazy.products.get(slug);
await shopeazy.products.availability(slug, { from, to });
await shopeazy.services.page();
await shopeazy.kits.page();

// Policies. The slug for refunds is `refunds`, plural
await shopeazy.policies.page();
await shopeazy.policies.get('refunds');             // .html is sanitised by the API

// Selling
await shopeazy.checkout.quote({ lines, fulfilment: 'delivery', shippingAddress });
const placed = await shopeazy.orders.create({ lines, customer: { name } });
await shopeazy.orders.get(id, { orderToken: placed.accessToken });
await shopeazy.orders.pay(id, { successUrl, cancelUrl });
await shopeazy.orders.confirm(id, { orderToken: placed.accessToken });

// Contact form
await shopeazy.enquiries.submit({ name, phone, notes });

// A signed-in shopper
await shopeazy.shopper.createSession(idToken);      // 14-day session
await shopeazy.asShopperSession(token).me();
await shopeazy.asShopperSession(token).members.apply({ applicationNote });
await shopeazy.asShopperSession(token).account.ordersPage();
await shopeazy.asShopperSession(token).account.bookingsPage();

// Merchant side. Secret key only
await shopeazy.members.page({ status: 'pending' });
await shopeazy.members.approve(uid, { note: 'Welcome' });

Every input and result type is exported, for example Store, Product, Quote, Order and Me. They come from the specification.

Lists

Each list has a page() method that takes { limit, cursor } and returns { items, nextCursor }. Pass nextCursor back as cursor until it is null, or use listAll:

import { listAll } from '@shopeazy/storefront-client';

const everything = await listAll((cursor) => shopeazy.products.page({ cursor }));

The older list() methods still work but are deprecated, as are the API fields they read. They go after 1 February 2027. Use page().

Idempotency

orders.create and orders.pay send a fresh Idempotency-Key on every call. To make your own retry safe, make one key per attempt and pass it each time:

await shopeazy.orders.create(input, { idempotencyKey });

A retry returns the first order instead of placing a second. The same key with a different body is 409 idempotency_key_reused. See Idempotency.

Keeping a shopper signed in

import { shopperCookie, readShopperToken, clearShopperCookie } from '@shopeazy/storefront-client/session';

const { sessionToken } = await shopeazy.shopper.createSession(idToken);
headers.set('Set-Cookie', shopperCookie(sessionToken, { maxAgeSeconds: 60 * 60 * 24 * 14 }));

const token = readShopperToken(request.headers.get('cookie'));
const me = token ? await shopeazy.asShopperSession(token).me() : null;

peekTokenClaims() and isTokenExpired() read a token without verifying it. They are advisory: never authorise on them.

Helpers

Pure functions, also at @shopeazy/storefront-client/helpers:

HelperDoes
formatMoney(money){ amount: 123450, currency: 'ZAR' } to R1 234,50
displayPrice(product)The card price: the cheapest priced variant, else the item price
membersOnly(store.membership)true when the store sells to members only
membershipGate(store.membership)'checkout' or 'browse'

Errors

import { StorefrontApiError } from '@shopeazy/storefront-client';

try {
  await shopeazy.orders.create(input);
} catch (error) {
  if (error instanceof StorefrontApiError) {
    if (error.isShopperTokenExpired) return refreshAndRetry();
    if (error.needsShopper) return showSignIn();
    if (error.isNotMember) return showApply();
    if (error.isStoreClosed) return showShopClosed();
    if (error.isAccessRevoked) return tellSomeoneTheKeyIsOff();
    console.error(error.status, error.code, error.message, error.requestId);
  }
}

code is typed from the specification, plus three codes the client raises itself: timeout, network_error and bad_response. Branch on code, never on message. requestId and details are on the error.

Versions

The package version is the Storefront API contract's version, release part only. A new operation or optional field is a minor release. A breaking change waits for a new major version of the API and a 2.0.0 client. See Versioning and deprecation.

On this page