@ab-ecosystem/aistore-storefront
0.1.0PublicHeadless commerce · storefrontBuild 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@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
- Installation
- Quick start
- Configuration options
- How the store is identified
- Customer sessions
- API key routes
- API reference
- Checkout error codes worth handling
- Per-call options
- Image search upload
- Error handling
- Calling unwrapped endpoints
- Use cases
- Requirements and compatibility
- FAQ
- Known server quirks
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
importandrequireentry points, plussideEffects: falsefor tree-shaking. - Typed errors — distinguish API, configuration, network and timeout failures without string matching, and branch on the checkout
codewhen a cart goes stale. - Cancellation and timeouts — per-client and per-call
timeoutMs, plusAbortSignalsupport 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
| Option | Type | Default | Description |
|---|---|---|---|
baseUrl | string | — | Required. Server origin, e.g. https://api.example.com. The /webstore prefix is added by the SDK. |
origin | string | — | 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. |
accessToken | string | — | Customer access token from a previous login, to restore a session. |
refreshToken | string | — | 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. |
apiKey | string or token provider | — | Store API key. Only needed for the WhatsApp / voice-AI endpoints marked api-key. |
fetch | typeof fetch | global fetch | Custom fetch implementation for logging, tracing, retries or polyfills. |
timeoutMs | number | none | Default per-request timeout. Overridable per call. |
headers | Record<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
Originautomatically from the page's URL and ignores theoriginoption. 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 withsetOrigin().
The magic-URL routes and orders.whatsappCreateOrder identify the store by sub_domain instead and do not need Origin.
Customer sessions
| Call | Effect |
|---|---|
customers.login(), customers.loginWithOtp(), magicUrl.verify() | Server returns access_token + refresh_token; the SDK stores them and calls onTokensChanged. |
| any later call | Sends 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.session | Read 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
| Resource | Methods | Server routes |
|---|---|---|
products | fetch, getById, fetchVariants, fetchByCategory | fetch-products, fetch-product, fetch-variants, fetch-category-products |
categories | fetch, fetchByPath | fetch-category, fetch-category-path |
cart | get, populate, details, batch, clear, merge | cart, cart/populate, cart/details, cart/batch, cart/clear, cart/merge |
customers | register, login, loginWithOtp, sendOtp, verifyOtp, refresh, me, fetchByWhatsApp, saveAddress, logout | register, login, login-otp, send-otp, verify-otp, refresh, fetch-user-details, fetch-user-details-wa, save-address |
orders | create, getById, fetch, fetchMine, shopifyCheckoutUrl, whatsappCheckout, whatsappCreateOrder, whatsappSaveAddress, createDispute, getDisputeDetails | create_order, get_order, fetch_order, fetch-customer-orders, shopify-checkout-url, whatsapp-checkout, whatsapp-create-order, whatsapp-address, create-dispute, get-dispute-details |
voiceAi | createAddress, saveAddress, saveOrder | create-new-address-voice-ai, save-address-voice-ai, save-order-voice-ai |
payments | fetchMethods, createRazorpayOrder, captureRazorpayPayment, generatePaymentLink, regeneratePaymentLink, createDispute, disputeImagePresignedUrl | fetch-payment-method, generate-razorpay-order, razorpay-payment-capture, generate-payment-link, regenerate-payment-link, create-payment-dispute, dispute-image-presigned-url |
search | search, fuzzy, vector, voiceAi, image (multipart) | search, fuzzy-search, vector-search, voice-ai-search, image-search |
theme | fetchStaticFile, fetchCss, fetchSkeleton, fetchVersion, fetchWhitelist | fetch-static-theme, fetch-css, fetch-skeleton, fetch-theme-version, fetch-whitelist |
magicUrl | registerCustomer, verify, registerOnWhatsapp | register-magicurl-customer, verify-magic-customer, register-new-customer-on-wa |
location | getStreetAddress, getShippingCharges | get-street-address, get-shipping-charges |
customFields | getProductFields, getOrderFields | get-product-fields, get-order-fields |
quickFilters | fetch, fetchProducts | fetch-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:
code | status | Meaning |
|---|---|---|
PRICE_CHANGED | 409 | A 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_UNAVAILABLE | 409 | A variant no longer exists. err.body.items lists the variant ids. |
TOKEN_EXPIRED / SESSION_EXPIRED / INVALID_TOKEN | 401 | Ask 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
| Class | When |
|---|---|
AIStoreApiError | non-2xx response. Has status, code, body, headers, request. |
AIStoreConfigError | missing baseUrl, or a customer token / API key the route needs. Thrown before the request. |
AIStoreNetworkError | fetch itself failed (DNS, CORS, offline). |
AIStoreTimeoutError | timeoutMs 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.js | 18 or newer (needs the built-in fetch) |
| Browsers | Any modern browser; the storefront must be served from a hostname registered to the store |
| Edge and serverless | Any runtime providing the WHATWG fetch, Headers and AbortSignal |
| Module formats | ESM (import) and CommonJS (require) |
| TypeScript | Types bundled; no @types package needed |
| Runtime dependencies | None |
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. Useorders.fetchMine()for a customer's orders.cart.get()cannot address a phone-based cart (the server readswa_numberfrom a GET body); usecart.populate({ wa_number, recover_cart: true })instead.
License
ISC
Every resource is a namespace on the client. This is the full surface of @ab-ecosystem/aistore-storefront at v0.1.0.
Storefront resources
/webstore — one typed method per route
| Resource | Methods |
|---|---|
products | fetch · getById · fetchVariants · fetchByCategoryfetch-products · fetch-product · fetch-variants · fetch-category-products |
categories | fetch · fetchByPathfetch-category · fetch-category-path |
cart | get · populate · details · batch · clear · mergecart · cart/populate · cart/details · cart/batch · cart/clear · cart/merge |
customers | register · 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 |
orders | create · 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 |
voiceAi | createAddress · saveAddress · saveOrdercreate-new-address-voice-ai · save-address-voice-ai · save-order-voice-ai |
payments | fetchMethods · 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 |
search | search · fuzzy · vector · voiceAi · image (multipart)search · fuzzy-search · vector-search · voice-ai-search · image-search |
theme | fetchStaticFile · fetchCss · fetchSkeleton · fetchVersion · fetchWhitelistfetch-static-theme · fetch-css · fetch-skeleton · fetch-theme-version · fetch-whitelist |
magicUrl | registerCustomer · verify · registerOnWhatsappregister-magicurl-customer · verify-magic-customer · register-new-customer-on-wa |
location | getStreetAddress · getShippingChargesget-street-address · get-shipping-charges |
customFields | getProductFields · getOrderFieldsget-product-fields · get-order-fields |
quickFilters | fetch · fetchProductsfetch-quick-filters · fetch-products-by-filters |
Install
$ npm install @ab-ecosystem/aistore-storefront$ pnpm add @ab-ecosystem/aistore-storefront$ yarn add @ab-ecosystem/aistore-storefrontEntry points
import { AIStoreStorefront, AIStoreApiError } from '@ab-ecosystem/aistore-storefront';
const { AIStoreStorefront } = require('@ab-ecosystem/aistore-storefront'); // CommonJS
Quickstart
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),
});
// 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 results = await store.search.fuzzy({ q: "blue shirt", limit: 10 });
// Account
await store.customers.login({ phone_no: "919876543210", password: "secret" });
const me = await store.customers.me();
// Cart
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" },
});
Error classes
| Class | When it is thrown |
|---|---|
AIStoreApiError | Non-2xx response. Has status, code, body, headers, request. |
AIStoreConfigError | Missing baseUrl, or a customer token / API key the route needs. Thrown before the request. |
AIStoreNetworkError | Fetch itself failed — DNS, CORS, offline. |
AIStoreTimeoutError | timeoutMs elapsed. |
Compatibility
- Node.js
- >=18
- Module formats
- ESM + CommonJS · sideEffects: false
- Runtime dependencies
- none
- TypeScript
- Types bundled — no @types package
Invariants that are easy to get wrong
Read these before generating code against aistore-storefront. Each one is behaviour the API guarantees, not a style preference.
- 01
Handle PRICE_CHANGED (409) at checkout — err.body.items lists submitted vs current prices. Re-run cart.populate() and show the new totals.
- 02
ITEMS_UNAVAILABLE (409) means a variant no longer exists; err.body.items lists the variant ids.
- 03
origin is required in Node and other non-browser runtimes — the server resolves the store from that header.
- 04
Never ship the store API key to a browser. It is only needed for the WhatsApp and voice-AI routes marked api-key.
- 05
Known server quirk: orders.fetch() is not scoped to a store on the server and returns an empty list for storefront callers — use orders.fetchMine().
- 06
Known server quirk: cart.get() cannot address a phone-based cart; use cart.populate({ wa_number, recover_cart: true }) instead.
- 07
Guest checkout works — pass a userType other than "logged_in" to orders.create and no customer token is required.
- 08
The magic-URL routes and orders.whatsappCreateOrder identify the store by sub_domain instead, and do not need Origin.
Highlights
- Customer sessions handled for you
- Login, OTP login and magic-URL verification store the access and refresh tokens. A 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 with wa_number + recover_cart.
- Five kinds of search
- Text, fuzzy, vector, voice-AI and multipart image search — everything a search page or a "shop the look" experience needs, including quick filters.
- Stale-cart detection at checkout
- orders.create rejects with PRICE_CHANGED or ITEMS_UNAVAILABLE and lists the offending lines, so you re-populate the cart and show new totals instead of failing blind.
- One API host, many storefronts
- The server resolves the store from the Origin header. Browsers set it for you; in Node you pass origin yourself and can change it later with setOrigin().
- Themed white-label stores
- Pull per-store CSS, skeletons and static theme files to render each merchant’s own branding from a single codebase.
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
@ab-ecosystem/cpaas
WhatsApp Business API, CRM and campaigns behind one typed client
v1.1.0 · MIT · 844.9 kB
@ab-ecosystem/aistore-admin
The headless ecommerce admin API, fully typed
v0.1.0 · ISC · 465.5 kB