Skip to content

@ab-ecosystem/aistore-storefront

0.1.0PublicHeadless commerce · storefront

Build a product catalog, shopping cart, checkout, customer accounts, payments and search into any frontend — Next.js, Nuxt, Astro, a mobile app, a WhatsApp bot or a voice-AI agent. One typed method per storefront route on the /webstore router, with customer sessions and silent token refresh handled for you.

$ npm install @ab-ecosystem/aistore-storefront
View on npm

@ab-ecosystem/aistore-storefront

TypeScript SDK and REST API client for the AI Store headless ecommerce storefront API. Build a product catalog, shopping cart, checkout, customer accounts, payments and search into any frontend — Next.js, Nuxt, Astro, a mobile app, a WhatsApp bot or a voice-AI agent — with full type safety and zero runtime dependencies.

One typed method per storefront route on the /webstore router, grouped by resource, with the server path in the JSDoc.

Works in Node 18+, modern browsers and edge runtimes.

Table of contents

Features

  • Complete storefront API coverage — catalog and categories, server-side cart, customer registration and login, checkout and order creation, payment links and Razorpay capture, text / fuzzy / vector / voice / image search, theme assets, shipping charges, custom fields and quick filters.
  • Customer sessions handled for you — login, OTP login and magic-URL verification store the access and refresh tokens; silent server-side refresh is captured from the response headers and reported through onTokensChanged.
  • Guest, logged-in and phone-based shoppers — the same cart and checkout calls serve an anonymous visitor, an authenticated customer, or a WhatsApp shopper addressed by phone number.
  • Typed end to end — TypeScript definitions for every method, with generics so you can type each response payload: store.products.fetch<MyProduct>().
  • Zero runtime dependencies — nothing but the platform fetch. Nothing to audit, nothing to patch.
  • ESM and CommonJS — dual build with import and require entry points, plus sideEffects: false for tree-shaking.
  • Typed errors — distinguish API, configuration, network and timeout failures without string matching, and branch on the checkout code when a cart goes stale.
  • Cancellation and timeouts — per-client and per-call timeoutMs, plus AbortSignal support on every method.
  • Pluggable fetch — inject undici, a polyfill, or a wrapper that adds logging, tracing or retries.

Installation

npm install @ab-ecosystem/aistore-storefront
pnpm add @ab-ecosystem/aistore-storefront
yarn add @ab-ecosystem/aistore-storefront

Quick start

import { AIStoreStorefront, AIStoreApiError } from "@ab-ecosystem/aistore-storefront";

const store = new AIStoreStorefront({
  baseUrl: "https://api.your-domain.com",
  origin: "https://shop.your-domain.com",       // required in Node; browsers set Origin themselves
  accessToken: saved?.access_token,             // optional: restore a customer session
  refreshToken: saved?.refresh_token,
  onTokensChanged: (tokens) => save(tokens),    // persist new / rotated tokens
});

// Catalogue (guest or logged-in; prices follow the customer's segment)
const { data: products } = await store.products.fetch({ status: "publish", page: 1, size: 24 });
const { data: cats } = await store.categories.fetch();
const results = await store.search.fuzzy({ q: "blue shirt", limit: 10 });

// Account
await store.customers.login({ phone_no: "919876543210", password: "secret" });   // tokens stored automatically
const me = await store.customers.me();

// Cart (server cart for logged-in customers)
await store.cart.batch({
  operations: [{ action: "add", product_id, variant_id, quantity: 2 }],
});
const cart = await store.cart.populate();

// Checkout
const order = await store.orders.create({
  userType: "logged_in",
  personalDetails: { fullname: "Asha", email: "[email protected]", phone: "919876543210" },
  shipping: { address_1: "12 MG Road", city: "Bengaluru", state: "KA", zipCode: "560001", country: "IN" },
  summary: { total: 1298, tax: 0, shipping_charge: 0, currency: "INR" },
  items: [{ product_id, variant_id, quantity: 2, price: 649 }],
  payment: { method: "cod" },
});

Every method returns the server's JSON envelope, typically { status, message?, data?, total? }. Pass a type argument to type data: store.products.fetch<MyProduct>().

Configuration options

OptionTypeDefaultDescription
baseUrlstring—Required. Server origin, e.g. https://api.example.com. The /webstore prefix is added by the SDK.
originstring—The storefront's origin, e.g. https://shop.example.com. The server resolves the store from the Origin header. Required in Node and other non-browser runtimes; browsers set Origin themselves and ignore this.
accessTokenstring—Customer access token from a previous login, to restore a session.
refreshTokenstring—Customer refresh token from a previous login.
onTokensChanged(tokens) => void—Called with the latest tokens whenever they change — after a login, an explicit refresh, or a silent server-side refresh. Persist them here.
apiKeystring or token provider—Store API key. Only needed for the WhatsApp / voice-AI endpoints marked api-key.
fetchtypeof fetchglobal fetchCustom fetch implementation for logging, tracing, retries or polyfills.
timeoutMsnumbernoneDefault per-request timeout. Overridable per call.
headersRecord<string, string>—Extra headers sent with every request.

A token provider is any () => string | Promise<string>. Read the current origin with store.baseUrl, and change the configuration later with setOrigin(), setTokens() and setApiKey().

How the store is identified

The server resolves the store from the request Origin header.

  • Browser: the browser sets Origin automatically from the page's URL and ignores the origin option. Your storefront must be served from a hostname registered to the store.
  • Node / server-side / bots: pass origin (e.g. https://shop.example.com). Change it later with setOrigin().

The magic-URL routes and orders.whatsappCreateOrder identify the store by sub_domain instead and do not need Origin.

Customer sessions

CallEffect
customers.login(), customers.loginWithOtp(), magicUrl.verify()Server returns access_token + refresh_token; the SDK stores them and calls onTokensChanged.
any later callSends access-token and refresh-token. If the access token expired, the server refreshes it silently and returns a new-access-token header, which the SDK captures (and reports through onTokensChanged).
customers.refresh()Explicit rotation of both tokens.
customers.logout()Forgets the tokens client-side.
store.sessionRead tokens: getAccessToken(), getRefreshToken(), isLoggedIn().

Routes that require a logged-in customer (customers.me, customers.refresh, orders.fetchMine, orders.createDispute, cart.batch, and orders.create / payments.generatePaymentLink with userType: "logged_in") throw AIStoreConfigError if no token is held.

OTP login

const { data: otpToken } = await store.customers.sendOtp({ phone: "919876543210" });
await store.customers.verifyOtp({ phone: "919876543210", otp: "123456" }, otpToken);
await store.customers.loginWithOtp({ phone_no: "919876543210" });   // creates the account if new

Phone-based (WhatsApp) shoppers without a login

Cart calls accept wa_number + recover_cart: true to address a cart by phone number:

await store.cart.populate({ wa_number: "919876543210", recover_cart: true });

API key routes

A few endpoints used by WhatsApp and voice-AI automations take the store API key instead of a customer token. They are marked api-key in their JSDoc: customers.fetchByWhatsApp, orders.whatsappSaveAddress, voiceAi.saveAddress, voiceAi.saveOrder. Pass apiKey to the client (or setApiKey()) before calling them. Never ship the API key to a browser.

API reference

ResourceMethodsServer routes
productsfetch, getById, fetchVariants, fetchByCategoryfetch-products, fetch-product, fetch-variants, fetch-category-products
categoriesfetch, fetchByPathfetch-category, fetch-category-path
cartget, populate, details, batch, clear, mergecart, cart/populate, cart/details, cart/batch, cart/clear, cart/merge
customersregister, login, loginWithOtp, sendOtp, verifyOtp, refresh, me, fetchByWhatsApp, saveAddress, logoutregister, login, login-otp, send-otp, verify-otp, refresh, fetch-user-details, fetch-user-details-wa, save-address
orderscreate, getById, fetch, fetchMine, shopifyCheckoutUrl, whatsappCheckout, whatsappCreateOrder, whatsappSaveAddress, createDispute, getDisputeDetailscreate_order, get_order, fetch_order, fetch-customer-orders, shopify-checkout-url, whatsapp-checkout, whatsapp-create-order, whatsapp-address, create-dispute, get-dispute-details
voiceAicreateAddress, saveAddress, saveOrdercreate-new-address-voice-ai, save-address-voice-ai, save-order-voice-ai
paymentsfetchMethods, createRazorpayOrder, captureRazorpayPayment, generatePaymentLink, regeneratePaymentLink, createDispute, disputeImagePresignedUrlfetch-payment-method, generate-razorpay-order, razorpay-payment-capture, generate-payment-link, regenerate-payment-link, create-payment-dispute, dispute-image-presigned-url
searchsearch, fuzzy, vector, voiceAi, image (multipart)search, fuzzy-search, vector-search, voice-ai-search, image-search
themefetchStaticFile, fetchCss, fetchSkeleton, fetchVersion, fetchWhitelistfetch-static-theme, fetch-css, fetch-skeleton, fetch-theme-version, fetch-whitelist
magicUrlregisterCustomer, verify, registerOnWhatsappregister-magicurl-customer, verify-magic-customer, register-new-customer-on-wa
locationgetStreetAddress, getShippingChargesget-street-address, get-shipping-charges
customFieldsgetProductFields, getOrderFieldsget-product-fields, get-order-fields
quickFiltersfetch, fetchProductsfetch-quick-filters, fetch-products-by-filters

Not wrapped: /whatsapp-draft-order and /payment-webhook-handler, which are inbound webhooks called by WhatsApp and the payment gateway, not by storefront clients.

Checkout error codes worth handling

orders.create rejects with AIStoreApiError and a code when the cart is stale:

codestatusMeaning
PRICE_CHANGED409A line's price moved since it was shown. err.body.items lists submitted vs current prices. Re-run cart.populate() and show the new totals.
ITEMS_UNAVAILABLE409A variant no longer exists. err.body.items lists the variant ids.
TOKEN_EXPIRED / SESSION_EXPIRED / INVALID_TOKEN401Ask the customer to log in again.

Per-call options

await store.products.fetch({ page: 1 }, {
  signal: controller.signal,
  timeoutMs: 5_000,
  headers: { "x-request-id": id },
  responseType: "text",   // "auto" (default) | "json" | "text" | "blob"
});

Image search upload

// Browser
await store.search.image(fileInput.files[0], { limit: 8 });

// Node 18+
import { openAsBlob } from "node:fs";
await store.search.image(await openAsBlob("./photo.jpg"), { fileName: "photo.jpg" });

Error handling

ClassWhen
AIStoreApiErrornon-2xx response. Has status, code, body, headers, request.
AIStoreConfigErrormissing baseUrl, or a customer token / API key the route needs. Thrown before the request.
AIStoreNetworkErrorfetch itself failed (DNS, CORS, offline).
AIStoreTimeoutErrortimeoutMs elapsed.

All four extend AIStoreError, so a single catch can separate "the server said no" from "the request never arrived":

try {
  await store.orders.create(payload);
} catch (err) {
  if (err instanceof AIStoreApiError && err.code === "PRICE_CHANGED") {
    const cart = await store.cart.populate();
    return showUpdatedTotals(cart);
  }
  throw err;
}

Calling unwrapped endpoints

await store.request("GET", "/webstore/some-new-route", { query: { a: 1 } });

path is relative to baseUrl and must include the /webstore prefix (exported as WEBSTORE_PREFIX). Use this for routes added to the server before the SDK catches up.

Use cases

  • Headless storefronts — fetch catalog, categories and theme assets at build time or per request for Next.js, Nuxt, Astro, SvelteKit or a static site generator.
  • Shopping cart and checkout — a server-side cart with batched add / update / remove operations, shipping charges, payment methods and order creation with stale-cart detection.
  • Customer accounts — registration, password login, OTP login, magic-URL sign-in, saved addresses, order history and disputes.
  • Conversational commerce — back a WhatsApp catalog or chatbot with live products and carts addressed by phone number, no login required.
  • Voice commerce — voice-AI search plus address and order capture for an agent-driven purchase flow.
  • Mobile apps — one typed client for iOS, Android or React Native storefronts, with token persistence delegated to your own storage.
  • Product discovery — text, fuzzy, vector and image search, plus quick filters, for a search page or a visual "shop the look" experience.
  • Themed white-label stores — pull per-store CSS, skeletons and static theme files to render a merchant's branding.

Requirements and compatibility

Node.js18 or newer (needs the built-in fetch)
BrowsersAny modern browser; the storefront must be served from a hostname registered to the store
Edge and serverlessAny runtime providing the WHATWG fetch, Headers and AbortSignal
Module formatsESM (import) and CommonJS (require)
TypeScriptTypes bundled; no @types package needed
Runtime dependenciesNone

On Node 16 or an older browser target, pass a polyfill through the fetch option.

FAQ

Is this a headless ecommerce SDK? Yes. The storefront is fully decoupled from the API, so you can build any frontend — web, mobile, chat or voice — against these typed endpoints.

Why does every call need an Origin? The server identifies the store from that header, so one API host can serve many storefronts. Browsers set it for you; in Node you pass origin yourself. See How the store is identified.

Can I use it in the browser? Yes, and that is the intended setup for a storefront UI. Keep the store API key out of browser bundles — it is a long-lived store-wide credential, so confine the api-key routes to server-side code.

Do I have to handle token refresh? No. The server refreshes an expired access token silently and returns the new one in a response header, which the SDK captures. Supply onTokensChanged to persist whatever it captures.

Does it support guest checkout? Yes. Pass a userType other than "logged_in" to orders.create and no customer token is required.

How do I keep a WhatsApp shopper's cart? Address it by phone number with wa_number + recover_cart: true on the cart calls, instead of a customer session.

Does it retry failed requests? No. Retry policy is deliberately left to you, because the right behavior differs between an interactive checkout and a background job. Wrap the fetch option to add retries and backoff in one place.

Is TypeScript required? No. The package ships compiled JavaScript for both ESM and CommonJS; the types are a bonus if you use them.

How do I type a response? Pass a type argument to any method: store.products.fetch<MyProduct[]>({ page: 1 }).

Known server quirks

  • orders.fetch() (GET /fetch_order) is not scoped to a store on the server, so it returns an empty list for storefront callers. Use orders.fetchMine() for a customer's orders.
  • cart.get() cannot address a phone-based cart (the server reads wa_number from a GET body); use cart.populate({ wa_number, recover_cart: true }) instead.

License

ISC

What you can build with aistore-storefront

  • Headless storefronts for Next.js, Nuxt, Astro, SvelteKit or a static site generator
  • Shopping cart and checkout — a server-side cart with batched add / update / remove, shipping charges, payment methods and stale-cart detection
  • Customer accounts — registration, password login, OTP login, magic-URL sign-in, saved addresses, order history and disputes
  • Conversational commerce — a WhatsApp catalog or chatbot with live products and carts addressed by phone number, no login required
  • Voice commerce — voice-AI search plus address and order capture for an agent-driven purchase flow
  • Mobile apps — one typed client for iOS, Android or React Native, with token persistence delegated to your own storage
  • Product discovery — text, fuzzy, vector and image search, plus quick filters, for a visual "shop the look" experience
  • Themed white-label stores rendering each merchant’s branding from per-store CSS and skeletons

Other packages in @ab-ecosystem