# BlanxerAPI: all guides in one file > Every page of https://developers.blanxer.com/api-docs, concatenated in sidebar order. Index: https://developers.blanxer.com/llms.txt. API spec: https://developers.blanxer.com/openapi.yaml. --- # BlanxerAPI developer docs Build a custom Next.js website for a Blanxer store and deploy it to the merchant's own Cloudflare account. Blanxer stays the admin: products, stock, orders, payments, delivery and SEO data are managed in Blanxer, and the website is the front end. > **AI agents:** read [llms.txt](https://developers.blanxer.com/llms.txt), then follow the [Quickstart](https://developers.blanxer.com/api-docs/quickstart.md). Everything is also in [one file](https://developers.blanxer.com/llms-full.txt). Merchants start with one line to their AI agent: > Build my Blanxer store website in Next.js and deploy it to my Cloudflare account. Follow https://developers.blanxer.com/llms.txt My store is `.blanxer.io`. ## How it fits together | Channel | Who uses it | What for | |---|---|---| | Public API, `https://api.blanxer.com` | The website | Store, catalog, delivery, coupons, orders, payments, tracking. No key. | | Blanxer MCP, `https://mcp.blanxer.com/mcp` | The agent, while building | Store data: SEO text, products and photos, categories, coupons, the site link. The merchant signs in. | | Blanxer XML files | Google and Meta | Product feed and catalog sitemap | ## Install - Claude Code skill: `/plugin marketplace add blanxer-dev/blanxer-agent-skills` then `/plugin install blanxer-website@blanxer` - Blanxer MCP: `claude mcp add --transport http blanxer https://mcp.blanxer.com/mcp` - The site: start from `blanxer-dev/blanxer-storefront-starter`, which uses the `@blanxer-dev/storefront` package. ## Guides - [Quickstart](https://developers.blanxer.com/api-docs/quickstart.md): the procedure, step by step - [The 13 hard rules](https://developers.blanxer.com/api-docs/rules.md) - [Store and theme](https://developers.blanxer.com/api-docs/store-and-theme.md) · [Catalog](https://developers.blanxer.com/api-docs/catalog.md) · [Checkout](https://developers.blanxer.com/api-docs/checkout.md) · [Forms and extras](https://developers.blanxer.com/api-docs/forms-and-extras.md) - [SEO and tracking](https://developers.blanxer.com/api-docs/seo-and-tracking.md) · [Images and speed](https://developers.blanxer.com/api-docs/images-and-speed.md) · [Package](https://developers.blanxer.com/api-docs/package.md) - [MCP for your website](https://developers.blanxer.com/api-docs/mcp-for-website.md) · [Testing](https://developers.blanxer.com/api-docs/testing.md) · [Deploy to Cloudflare](https://developers.blanxer.com/api-docs/deploy-cloudflare.md) · [Troubleshooting](https://developers.blanxer.com/api-docs/troubleshooting.md) - [OpenAPI spec](https://developers.blanxer.com/openapi.yaml) for every endpoint a site uses --- # Quickstart BlanxerAPI lets you build a custom Next.js website for a Blanxer store and deploy it to the merchant's own Cloudflare account. Blanxer stays the admin: products, stock, orders, payments, delivery and SEO data live in Blanxer. The website is only the front end. The merchant usually starts with one line to an AI agent: > Build my Blanxer store website in Next.js and deploy it to my Cloudflare account. Follow https://developers.blanxer.com/llms.txt My store is `.blanxer.io`. This page is the procedure. Each step links to the guide with the details. ## Before you start Read [the 13 hard rules](https://developers.blanxer.com/api-docs/rules.md). In short: Next.js only, start from the starter, keep `@blanxer-dev/storefront` for checkout, keep the four required routes, call orders and payments from the browser, no secrets, no customer login, no invented data. Install the agent tools: - **Claude Code skill:** `/plugin marketplace add blanxer-dev/blanxer-agent-skills` then `/plugin install blanxer-website@blanxer` - **Blanxer MCP:** `claude mcp add --transport http blanxer https://mcp.blanxer.com/mcp` (the merchant signs in to Blanxer in the browser) - **Cursor, Codex, claude.ai:** add `https://mcp.blanxer.com/mcp` as a remote MCP server. See [MCP for your website](https://developers.blanxer.com/api-docs/mcp-for-website.md). The starter's `AGENTS.md` holds the same procedure as the skill. ## Three channels, three jobs | Channel | Used by | For | Auth | |---|---|---|---| | Public API, `https://api.blanxer.com` | The site (server and browser) | Read store, theme, catalog, delivery table, coupons. Place orders, start payments, track orders. | None. CORS `*`. | | Blanxer MCP, `https://mcp.blanxer.com/mcp` | You, the agent, in chat | Change store data: SEO text, products, photos, categories, brands, coupons, delivery, the site link | OAuth. The merchant signs in. | | Blanxer-served XML, `https://api.blanxer.com/public/...xml` | Google and Meta | Product feed and catalog sitemap | None | The site never holds a key and never talks to the MCP. ## The steps Confirm each step with the merchant before moving on. ### 1. Find the store and connect Look the store up by subdomain (or custom domain): ```bash curl -s "https://api.blanxer.com/store/check_domain?domain=mystore" ``` You get the store config, including `_id` (the store id every other call needs), `name`, `plan`, `addons`, `checkout`, `customization` and `categories`. An unknown domain returns HTTP 400 `{"message":"Domain not in use"}`. Field by field: [Store and theme](https://developers.blanxer.com/api-docs/store-and-theme.md). Show the merchant: - the store name and plan; - the product count (`GET /product/public/` returns all active products); - the payment methods the checkout will offer (see [Checkout → Payment methods](https://developers.blanxer.com/api-docs/checkout.md#payment-methods)); - **Free plan** (`plan` not one of `basic`, `premium`, `plus`, `platinum`): the checkout is Cash on Delivery only; - **Store with customer login turned on** (`customer_program.enabled`): the custom site still uses guest checkout. BlanxerAPI has no login. Then connect the Blanxer MCP, have the merchant sign in, and run `blanxer_audit_seo`. Also check the catalog for products with no photo, no price, no category or no SEO text. Show the gaps. ### 2. Design brief Ask for reference sites, the look, the pages and the tone. Use the store's logo, colours and font as defaults (`customization.brand_logo`, `customization.theme`, `customization.font`). ### 3. Scaffold from the starter ```bash npx degit blanxer-dev/blanxer-storefront-starter my-store cd my-store cp .env.example .env # set BLANXER_STORE= npm install ./node_modules/.bin/next dev ``` `BLANXER_STORE` is the subdomain (required). Optional: `BLANXER_API` (default `https://api.blanxer.com`), `SITE_URL` (the site's public origin; default the link saved in Blanxer) and `NEXT_PUBLIC_BLANXER_IMAGE_LOADER` (`wsrv`, `cloudflare` or `none`). See [Deploy → Configuration](https://developers.blanxer.com/api-docs/deploy-cloudflare.md#configuration). Open `http://localhost:3000` and show the merchant the starter running with their real products before changing the design. The starter's `npm run dev`, `npm run build` and `npm run deploy` scripts do the same as the direct commands on this page. The direct `./node_modules/.bin/...` form avoids surprises when the machine has another Node install on its path. ### 4. Build - Build pages with the package's data functions and components. See [Catalog](https://developers.blanxer.com/api-docs/catalog.md) and [Package](https://developers.blanxer.com/api-docs/package.md). - Theme the checkout; don't rewrite it. See [Checkout](https://developers.blanxer.com/api-docs/checkout.md). - Optimise every image (rule 11) and show a before/after size table. See [Images and speed](https://developers.blanxer.com/api-docs/images-and-speed.md). - Fix store data through the MCP: SEO text, categories and brands for the menus, product photos, launch coupons. Confirm bulk changes first (rule 13). Routes every site has: `/` · `/products` · `/product/[slug]` · `/collections/[slug]` (including `new-arrivals`) · `/brand/[slug]` · `/brands` · `/search?q=` · `/sale` · `/cart` · `/checkout` · **`/payment/success`** · **`/payment/failed`** · **`/track/[id]`** · `/forms/[slug]` · `/blogs` (and `?category=`) · `/blog/[slug]` · `/sitemap.xml` (index) · `/sitemap-catalog.xml` · `/robots.txt` No `/account`, `/login` or `/p/[slug]`. ### 5. Check ```bash ./node_modules/.bin/next build npx blanxer-storefront check # add --lighthouse http://localhost:3000 with the production build running ``` It checks the routes, secrets, login code, that order and payment calls run in the browser, the image budget (and warns when a page has no main image marked with `preload`), and with `--lighthouse ` the speed (mobile ≥ 90, LCP < 2.5 s on home, a product and checkout; needs `next start` running). Fix every failure before deploying, or tell the merchant why one can't be fixed. ### 6. Test the checkout See [Testing](https://developers.blanxer.com/api-docs/testing.md). On the merchant's real store, test orders use the phone number `0000000000`: the API validates the order and returns `{"success":true}` **without creating it**. The checkout then shows "Test order accepted" and stops. That tests the form, validation, delivery charges, COD per place and coupons. Because no order exists, the `0000000000` order can't open a gateway or a QR screen. Check that every payment method the store has on appears for a COD and a non-COD place, and that `/payment/success`, `/payment/failed` and `/track/` render. The gateway and QR screens themselves are checked once, live, in step 12 (see [Testing → The live check](https://developers.blanxer.com/api-docs/testing.md#4-the-live-check)). ### 7. Sign in to Cloudflare ```bash npx wrangler login ``` The merchant signs in to **their own** Cloudflare account in the browser. Before the first deploy, rename the Worker in `wrangler.jsonc` (`name` and the matching `WORKER_SELF_REFERENCE` service) to the merchant's site name. Check the Worker size: 3 MiB (compressed) on the free plan. See [Deploy to Cloudflare](https://developers.blanxer.com/api-docs/deploy-cloudflare.md). ### 8. Deploy ```bash ./node_modules/.bin/opennextjs-cloudflare build && ./node_modules/.bin/opennextjs-cloudflare deploy ``` Print the live `*.workers.dev` link and open it. ### 9. Link the site to Blanxer Call `blanxer_set_storefront_url { url }`. It returns a preview and a `confirm_token`; show the merchant, and after their yes call `{ dry_run: false, confirm_token }`. (Or the merchant pastes the link in the Blanxer dashboard → avatar menu → **BlanxerAPI** → Your website, rolling out.) Blanxer then uses the site in the tracking link it generates, the delivered email, flash-sale alerts, the product feed and the sitemap. Register the feed with Meta and submit the sitemap to Google Search Console. See [SEO and tracking](https://developers.blanxer.com/api-docs/seo-and-tracking.md). ### 10. Custom domain (optional) Add it in Cloudflare, then update the link (step 9). ### 11. Retire the subdomain site (optional, merchant's choice) Only after step 9. Every page on `.blanxer.io` then shows a placeholder, **including tracking links sent before today**. See [Deploy to Cloudflare → Retiring the old subdomain site](https://developers.blanxer.com/api-docs/deploy-cloudflare.md#retiring-the-old-subdomain-site). ### 12. Live check On the live site open home, a product, checkout, the three payment/tracking pages and a real `/track/`. With the merchant's OK, place one real COD order and have the merchant cancel it in the dashboard. For one online method, start a real order, open the gateway or QR screen and stop without paying (closing the QR releases the hold), then cancel it in the dashboard. Hand over a README that says how to edit and redeploy. ## Next - [Checkout](https://developers.blanxer.com/api-docs/checkout.md) is the most important page. Read it before touching `/checkout`. - The API spec for every endpoint a site uses: [openapi.yaml](https://developers.blanxer.com/openapi.yaml). --- # The 13 hard rules These rules apply to every BlanxerAPI website. Read them before writing any code, and follow them on every step. They are the same rules the `blanxer-website` skill and the starter's `AGENTS.md` state first. 1. **Next.js only:** TypeScript, App Router. If asked for another framework, say BlanxerAPI supports Next.js and continue in Next.js. 2. **Start from `blanxer-storefront-starter` and keep `@blanxer-dev/storefront`.** Restyle the checkout through theme props and wrappers. **Never rewrite** the payment, delivery, coupon or order logic, and never call payment endpoints directly. The checkout's **"Checkout Powered by Blanxer"** line under the place-order button ("Blanxer" links to https://blanxer.com) is part of it: restyle it through `.bx-powered-by`, never remove or hide it. 3. **Required routes:** `/checkout`, `/payment/success`, `/payment/failed` and `/track/[id]`, at these exact paths. 4. **Send the site's own origin as `url`.** The package does it; never hardcode it. 5. **Browser-only calls:** orders, coupons, payment starts, hold release, back-in-stock, forms, cancel and review go from client components. Never from server code, route handlers or server actions. 6. **No secrets in the site.** No `sk_` key, MCP token, dashboard login or gateway key in code, `.env` or the build. Never ask the merchant to paste a key. Store changes go through the Blanxer MCP. 7. **No customer login.** No `/customer/auth/*` calls, and no account, login, wishlist or rewards pages. 8. **Don't invent data.** Prices, stock, delivery and payment methods come only from the API. No sample products or placeholder prices. 9. **Payment methods come from the store's settings** via the package. Never add or remove them by hand. 10. **Use our URL paths** (`/product/[slug]`, `/collections/[slug]`, `/brand/[slug]`), because the feed and sitemap use them. **Don't build a product feed or catalog sitemap**; Blanxer serves both. Only add the site's own extra pages to the sitemap index. 11. **Optimise every image before it goes into the site.** - Use `npx blanxer-storefront images`: - resize to the largest size shown ×2 (hero 2560 px, banner 1600, card 800); - export AVIF + WebP at ~75 quality; - strip EXIF and location data; - use SVG for logos when available. - Never commit originals. - Load with `next/image`: each page's main (LCP) image gets `preload`, the rest load lazily with correct `sizes`. - **Product photos go into Blanxer through the MCP, never into the site.** - No autoplay video backgrounds or GIF banners unless the merchant insists after hearing the speed cost. - Fonts go through `next/font`, at most 2 families. 12. **The site's design lives in the site.** Don't use MCP page-builder tools for a custom site. Use the MCP only for store data. 13. **Confirm before bulk store changes.** Show the list (SEO for many products, imports, delivery charges) and get a yes. The MCP's own confirmations still apply. ## Why these rules exist | Rule | What breaks without it | |---|---| | 2, 9 | The checkout drifts from Blanxer's: wrong totals, missing gateways, orders the server refuses. | | 2 (Powered by) | The "Checkout Powered by Blanxer" line is a condition of using Blanxer's checkout on a custom site (owner decision, 2026-10-11). The package has no prop to turn it off. | | 3, 4 | Gateways return the shopper to `/payment/success` or `/payment/failed`, and the confirmation email links to `/track/`. A missing page means a 404 **after the shopper paid**. | | 5 | Order creation is limited to 5 per 10 minutes per store and IP. Every Cloudflare Worker shares a few IPs, so server-side orders would block each other. The Meta relay also uses the caller's IP. | | 6 | The site is public code on the merchant's account. Anything in it is readable. The public API needs no key. | | 7 | Customer accounts are not part of BlanxerAPI. Custom sites use guest checkout. | | 10 | Blanxer's product feed and sitemap link to `/product/`, `/collections/` and `/brand/` on the site. Other paths turn every feed and sitemap link into a 404. | | 11 | Image weight is the main reason a store site is slow. See [Images and speed](https://developers.blanxer.com/api-docs/images-and-speed.md). | | 12 | Page-builder tools change the Blanxer-hosted site, not the custom site. | | 13 | Bulk changes touch live store data shared with the Blanxer dashboard, POS and app. | --- # Store and theme One call returns everything about the store that a site needs to look like the store: name, logo, colours, font, categories, menus, social links, checkout settings, payment add-ons and tracking plugins. ## Looking up the store ```bash curl -s "https://api.blanxer.com/store/check_domain?domain=mystore" ``` - `domain` is the store's subdomain (`mystore` for `mystore.blanxer.io`) or its custom domain (`shop.example.com`). It is lower-cased. - `POST /store/check_domain` with `{"domain": "mystore"}` returns the same. - An unknown domain: HTTP 400 `{"message":"Domain not in use"}`. A missing `domain`: HTTP 400 `{"fields":{"domain":"Domain is required"}}`. - The API caches the answer for 1 hour and clears it when the merchant changes store settings. The cache is per API process, so on a multi-process deployment an old answer can still be served for up to 1 hour. The package adds a 60-second cache on top. In the site: `getStore()` and `getStoreId()` from `@blanxer-dev/storefront/server` read `BLANXER_STORE` and make this call. **`_id` is the `store_id` every other endpoint needs.** ## What it returns | Field | What it is | |---|---| | `_id` | Store id | | `name`, `description`, `display_image` | Store name, one-line description, an image | | `sub_domain`, `custom_domain` | `mystore`, and the Blanxer custom domain if any | | `storefront_url` | The saved link to the custom site, `''` when none. See [Deploy → Link the site](https://developers.blanxer.com/api-docs/deploy-cloudflare.md#link-the-site-to-blanxer). | | `plan` | `''` (free), `pos`, `basic`, `premium`, `plus`, `platinum`. Online payments need `basic`, `premium`, `plus` or `platinum`. | | `customization` | Logo, name, theme colour, font, currency label, top bar, popup, hero images (below) | | `navigation`, `footer` | Menu items and footer content as set in Blanxer | | `categories[]` | `_id`, `name`, `slug`, `image`, `order`, `seo_title`, `seo_description`, `seo_image`, `hide_on_product` | | `blog_categories[]` | `name`, `slug`, `image`, `order`, SEO fields | | `social` | `facebook`, `instagram`, `tiktok`, `whatsapp` | | `compliance` | Business details for the footer: `business_name`, `business_address`, `pan`, `registration_number`, `ecommerce_number`, `contact`, `email`, `complain_officer`, `permits`, `outlets`, `exim_code`. Show them only when `compliance.style === 'show'` | | `checkout` | Which checkout fields are on. See [Checkout → Fields](https://developers.blanxer.com/api-docs/checkout.md#fields). | | `addons`, `setting` | Payment add-ons, and store settings. The payment-credential keys in `setting` are masked to `'yes'` / `''`; other keys (delivery settings and more) come as stored. See [Checkout → Payment methods](https://developers.blanxer.com/api-docs/checkout.md#payment-methods). | | `plugins[]` | `{name, details}`: tracking tags and small features (below) | | `use_advanced_inventory` | Stock is per outlet. See [Catalog → Stock](https://developers.blanxer.com/api-docs/catalog.md#stock). | | `customer_program` | `{enabled, loyalty_enabled, membership_enabled, back_in_stock: {enabled}, login}` | | `under_construction` | `off`, `sub`, `cus` or `both`. Controls the **Blanxer-hosted** site only. Ignore it in a custom site. | | `kyc_status`, `blanxer_pay`, `loyalty`, `current_template` | Not needed by a custom site | ### Don't use - `customer_program.enabled` turns on customer accounts on the Blanxer-hosted site. A custom site has no login (rule 7) and uses guest checkout either way. Use only `customer_program.back_in_stock.enabled`. - `setting` holds store configuration. Only test the masked payment keys listed in the checkout guide, for truthiness. Don't display, log or depend on any other value in it. - `customization.page_styles`, `customization.current_template`, `customization.selected_navbar` and `customization.selected_hero` are layout choices for the Blanxer-hosted site. A custom site ignores them. ## Brand and theme From `customization`: | Field | Use | Default when empty | |---|---|---| | `brand_name` | Store name in the header, titles, JSON-LD | `name` | | `brand_logo` | Logo URL | Show the name as text | | `favicon` | Favicon URL | Your own | | `theme` | Primary colour (below) | `violet` | | `font` | A Google Fonts family name, e.g. `Poppins` | `Poppins` | | `currency_indicator` | Currency label before prices | `Rs.` (the package's default) | | `image_ratio` | The shape new product photos are cropped to on upload: `1` = 1:1, `2` = 4:5, `3` = 9:16. Use it for product card frames. Older photos may have other shapes, so use `object-fit` | 1:1 | | `top_bar_text`, `top_bar_goto` | Announcement bar text and link | No bar | | `top_bar_ui` | Announcement bar style: `default`, `marquee` or `scallop` | `default` | | `popup_image`, `popup_goto` | A promo popup image and its link. The hosted storefront shows it on the home page only, once per browser session (`sessionStorage['homepageBannerShown']`) | No popup | | `hero_images[]`, `hero_taglines[]`, `hero_links[]` | Home hero slides (image, tagline, link, by index) | Design your own | The `top_bar_color` plugin, when present, gives the bar's colours: `details: {bg, text}`. Without it, the hosted storefront uses shade 800 of the primary palette as the bar's background. These are **defaults** for the design brief. The merchant may want a different look; the site's design lives in the site (rule 12). If they want the shared brand values changed (also on the Blanxer-hosted site and in emails), use `blanxer_update_store_profile` / `blanxer_update_appearance` through the MCP. ### Theme colour `customization.theme` is either a palette name or `custom#RRGGBB`. - **`custom#RRGGBB`**: the merchant picked their own colour. The hex is shade 500 of a palette the hosted storefront derives from it; its buttons use the derived shade 600, so they look a little darker than the hex. The package uses the hex itself as `--bx-primary`. - **A name**: one of the palettes below (10 shades, 50 → 900). The Blanxer storefront's buttons use shade 600, hover 700. | `theme` | Shade 50 | Shade 600 (buttons) | Shade 900 | |---|---|---|---| | `dark` | #C9C9C9 | #2e2e2e | #141414 | | `gray` | #f8f9fa | #868e96 | #212529 | | `red` | #fff5f5 | #fa5252 | #c92a2a | | `oliz_red` | #fef2f2 | #db272a | #7f1d1f | | `pink` | #fff0f6 | #e64980 | #a61e4d | | `baby_pink` | #fff7fa | #ffa5c2 | #f9739e | | `grape` | #f8f0fc | #be4bdb | #862e9c | | `violet` | #f3f0ff | #7950f2 | #5f3dc4 | | `indigo` | #edf2ff | #4c6ef5 | #364fc7 | | `blue` | #e7f5ff | #228be6 | #1864ab | | `cyan` | #e3fafc | #15aabf | #0b7285 | | `teal` | #e6fcf5 | #12b886 | #087f5b | | `green` | #ebfbee | #40c057 | #2b8a3e | | `lime` | #f4fce3 | #82c91e | #5c940d | | `yellow` | #fff9db | #fab005 | #e67700 | | `orange` | #fff4e6 | #fd7e14 | #d9480f | | `brown` | #f6f4f0 | #816a4d | #4e3e35 | | `blanxer_purple` | #f6f2ff | #8830f7 | #54169c | | `blanxer_red` | #fff1f2 | #ed1520 | #88141a | | `blanxer_blue` | #eff8ff | #007ace | #084572 | | `blanxer_green` | #eefff5 | #01b859 | #0a5d35 | Map the primary colour to a CSS variable (the name below is an example) and pass it to the package components through their `theme` prop or CSS variables: ```tsx // app/layout.tsx import { getStore } from '@blanxer-dev/storefront/server'; const NAMED: Record = { violet: '#7950f2', blue: '#228be6' /* ...from the table */ }; export default async function RootLayout({ children }: { children: React.ReactNode }) { const store = await getStore(); const t = store.customization?.theme || 'violet'; const primary = t.startsWith('custom#') ? t.slice(6) : NAMED[t] ?? NAMED.violet; return ( {children} ); } ``` ### Font Load the store's font with `next/font/google`, not a `` to Google Fonts (rule 11: at most 2 families, self-hosted, no layout shift): ```tsx import { Poppins } from 'next/font/google'; const font = Poppins({ subsets: ['latin'], weight: ['400', '600'], display: 'swap' }); ``` `next/font` needs the family at build time. Read `customization.font` once while scaffolding, and change the import if the merchant changes the font later. ## Menus and footer - `navigation.items` is a list of `{n, g, i, l?}`: `n` the label, `g` the link, `i` the children (same shape, for dropdowns), `l` an optional section logo. `navigation.style` (default `LC`) and `navigation.extras` (may hold `socialLinks` and `contextual_nav`) are Blanxer layout settings; a custom site may ignore them. - `footer.details` holds the footer content the merchant set. Its shape depends on `footer.style` (one per Blanxer footer layout), so inspect it for the store you are building. - Restyle both freely. Keep the merchant's labels and links. - **Links to `/p/`** are Blanxer page-builder pages. Custom sites don't have them (and Blanxer's sitemap leaves them out once the site link is saved). Ask the merchant what each should become, or leave it out. - Other links use the same paths as a custom site: `/products`, `/collections/`, `/product/`, `/brand/`, `/brands`, `/sale`, `/search`, `/blogs`, `/blog/`, `/forms/`. Only `https://` links are external. - If `compliance.style === 'show'`, show the compliance details in the footer: Nepali e-commerce stores usually display their business name, address, PAN/registration and complaint officer. - `categories`, brands and other lists come in the order they are stored. Sort categories by `order` in the site. ## Plugins `plugins` is a list of `{name, details}`. The ones a custom site uses: | `name` | `details` | Use | |---|---|---| | `meta_tags` | `[{n, c}]`: `` | Search Console verification and other verification tags. See [SEO and tracking](https://developers.blanxer.com/api-docs/seo-and-tracking.md#search-console-verification). | | `ga` | `"G-XXXXXXXXXX"` | Google Analytics 4 | | `gtm` | `"GTM-XXXXXXX"` | Google Tag Manager | | `fb_pixel` | Pixel id | Meta Pixel | | `meta_capi` | `true` | The store has Meta Conversions API on; relay events (the token stays on the server) | | `clarity` | Project id | Microsoft Clarity | | `whatsapp_chat` | `{phone}` | WhatsApp chat button | | `tawk` | `{p, w}` | Tawk.to chat: property and widget id | | `top_bar_color` | `{bg, text}` | Announcement bar colours | | `product_custom_options` | `{showProductStock, outOfStockBaseValue, discountBadgeColor, ...}` | Stock display and the discount badge colour on product pages | | `newsletter` | `{enabled, headerImage, title, description, list_id}` | A newsletter sign-up block | | `samparka` | `{enabled: true}` | The store uses Samparka (loyalty); custom sites have no login, so ignore it | Most delivery-partner plugins are removed from this payload. Treat any other plugin as Blanxer-internal: don't display, log or depend on its `details`. ## Product fields for the product page Besides the catalog fields (see [Catalog](https://developers.blanxer.com/api-docs/catalog.md#one-product)), the product response has: - `color_name` and `size_name`: the labels for the two option pickers. Defaults: "Choose Variant" and "Choose Size". - Colour swatches only when a colour carries a code (`"Maroon:#7a1f2b"`); otherwise show the name. - `print_addon`: the print add-on, which the package can't sell yet (see [Checkout → Print add-on products](https://developers.blanxer.com/api-docs/checkout.md#print-add-on-products)). - `desc_page` (also on categories and brands): a page-builder description block for the Blanxer-hosted site. A custom site uses the HTML `description` / `long_description` instead. - `categories[].hide_on_product` (in `check_domain`): don't list that category on product pages. ## The site's own origin - In the browser, the site's origin is `window.location.origin`. The checkout sends it as `url` (rule 4). - On the server (metadata, JSON-LD, sitemap index, robots), use `siteOrigin()` from the package: the `SITE_URL` env, else the store's saved `storefront_url`, else the request host. Pages that should stay static pass `{ fromRequest: false }` (then it returns `''` instead of reading the request). - `storefront_url` is what Blanxer uses for links it sends. It changes only when the merchant saves the site link in Blanxer (not when a custom domain is added). Don't hardcode it in the site. --- # Catalog Products, variants, stock, categories, brands, search, sale and flash sales. All catalog reads are public `GET`s on `https://api.blanxer.com` with no key. In the site, read them on the server through `@blanxer-dev/storefront/server` (60-second cache, normal `User-Agent`). Never put sample products or placeholder prices in the site (rule 8). ## Endpoints | What | Endpoint | Returns | |---|---|---| | All products | `GET /product/public/` | Array of list items, every Active, non-POS product. Not paginated. | | A page of products | `GET /product/p/public/?page=1&per_page=30&sort_by=created_at&sort_order=desc` | `{success, meta, products}` | | One product | `GET /product/public//` | Full product (see below). `` may also be the product `_id`. | | A category | `GET /product/public//category/` | `{category, products}` | | New arrivals | `GET /product/public//category/new-arrivals` | `{category: {_id: 1, name: "New Arrivals", slug: "new-arrivals"}, products}`: up to 12 of the newest products | | Search | `GET /product/public/search/?q=` | Array of list items, up to 50 | | On sale | `GET /product/public/sale/` | Array of list items | | All brands | `GET /brand/public/all/` | `{brands}` | | A brand's products | `GET /brand/public/products//` | `{brand, products}` | | A brand's details | `GET /brand/public/details//` | `{brand, page}` | | Flash sales | `GET /public/flash-sale/` | `{server_time, sales}` | | One product's flash sale | `GET /public/flash-sale//product/` | `{server_time, sale, units}` | Categories are not an endpoint: they come with the store in `check_domain.categories` (see [Store and theme](https://developers.blanxer.com/api-docs/store-and-theme.md)). There is also `GET /product/public/barcode/?code=` (looks a product up by barcode). A custom site rarely needs it. Errors: - HTTP 400 `{"message": "..."}`: `Product not found`, `Category not found`, `Brand not found`. - HTTP 400 `{"error_type":"zod_validation","fields":{...}}` for a malformed id on search, sale, the paged list, brands and flash sales. - HTTP 500 `{"errors":{"message":"Cast to ObjectId failed..."}}` for a malformed `store_id` on all products, a category and one product. Always pass the `_id` from `check_domain`. - A well-formed but unknown store id is not an error: `[]` from the lists, `{category: false, products: []}` from a category. ### Server-side caching - The API caches only three catalog reads for up to 1 hour: all products, the paged list and a category (new arrivals included). It also caches `check_domain`. It clears them when the merchant changes the store's products, categories or settings. - Orders don't clear the cache, so stock and `in_stock` in those lists can be up to 1 hour old. The product page and the server's order check always use current stock. - Product detail, search, sale and brands are not cached by the API. - The package adds `next: { revalidate: 60 }`. Store changes show on the site within about a minute, without a redeploy. ## List items Every list endpoint (all products, page, category, search, sale, brand products, similar products) returns this item shape. Each loads a slightly different set of fields; the differences are listed below the example. ```json { "_id": "66f1c0ffee0000000000aa01", "name": "Pashmina Shawl", "slug": "pashmina-shawl", "price": null, "min_price": 2500, "max_price": 3200, "compare_at_price": 0, "has_variants": true, "in_stock": true, "quantity": 14, "total_variants": 6, "channel": 1, "variants": [{ "price": 2500, "compare_at_price": 0, "quantity": 4, "inventory_summary": [] }], "total_rating": 18, "review_count": 4, "image_urls": ["https://.../shawl-1.jpg"], "status": "Active", "colors": ["Maroon:#7a1f2b", "Cream"], "sizes": ["Standard", "Large"], "tags": ["new"], "categories": ["Shawls"], "created_at": "2026-09-30T10:12:00.000Z", "release_date": null, "brand": { "_id": "...", "name": "...", "slug": "...", "logo": "..." } } ``` - **Price:** a product without variants has `price`. A product with variants has either `price` or `min_price` / `max_price` with `price: null`. Show "Rs 2,500 – 3,200" for a range. `price` is set (and min/max are null) when all variants cost the same, and also when any variant's price is 0 or missing; then `price` is the highest variant price. - **`compare_at_price`** above the price means the product is discounted; show it struck through. - **`categories`** here are category **names**, not ids. Exception: on the category endpoint (not new arrivals) they are `{_id, name, slug}` objects. - **`brand`** is `{_id, name, slug, logo}` on the category, new-arrivals and sale endpoints, and `{name, slug, logo}` (no `_id`) on search. All products, the paged list, brand products and similar products leave it out. It is never a plain id. - **`quantity`:** with variants, the sum of the positive variant stock; without variants, the product's own quantity (it can be 0 or below). - **`variants`** carry `price`, `compare_at_price`, `quantity` and `inventory_summary`. Only search results add `_id` and `option_name`. Get variant ids from the product endpoint. - **Differences per endpoint:** - category, new arrivals, search and similar products don't load `continue_selling`, so their `in_stock` is stock only; - `release_date` is only on category, search and brand products; - search results have no `total_rating` / `review_count`, and add `score` (relevance); - similar products have no `channel`. - **Rating:** `total_rating` is a sum. Average = `total_rating / review_count`. - **`in_stock`** is described under [Stock](#stock). ### The paged list `GET /product/p/public/`: | Param | Default | Values | |---|---|---| | `page` | 1 | ≥ 1 | | `per_page` | 30 | ≥ 1; values above 100 are treated as 100 | | `sort_by` | `created_at` | `created_at`, `price`, `compare_at_price`, `name`, `total_rating`, `review_count`, `quantity` | | `sort_order` | `desc` | `asc`, `desc` | ```json { "success": true, "meta": { "page": 1, "per_page": 30, "total": 212, "total_pages": 8, "sort_by": "created_at", "sort_order": "desc" }, "products": [ ... ] } ``` `sort_by=price` sorts on the stored top-level price, which is 0 for many products with variants. For a correct price sort, load all products and sort in the site by `price ?? min_price`. ### Search - `q` must be at least 3 characters; shorter queries return `[]` (no error). - It is an autocomplete search over a key built from the product name, its category names, colours, sizes and tags (tags containing `:` are left out). Best match first, Active and non-POS only. - The limit of 50 is applied **before** the Active/non-POS filter, so a search can return fewer than 50 even when more products match. (One store has a higher limit.) ### Sale `/product/public/sale/` returns Active, non-POS products with a `compare_at_price` above 0 on the product or on any variant. (Before the BlanxerAPI backend release it returned `[]` on stores with advanced inventory.) ## One product `GET /product/public//` returns the stored product plus a few extras: | Field | Notes | |---|---| | `_id`, `name`, `slug`, `status`, `channel` | See the warning below | | `description`, `long_description` | **HTML** from Blanxer's editor. Sanitize before rendering. | | `price`, `compare_at_price`, `quantity`, `weight`, `sku`, `barcode`, `continue_selling` | Product-level values. For a product with variants, use the variant's. | | `colors`, `sizes` | Empty when the product has no variants | | `variants[]` | `_id`, `option_name`, `price`, `compare_at_price`, `quantity`, `sku`, `weight`, `image_url`, `barcode`, and also `alt_barcode`, `exim_code`, `tigg_id`, `inventory_summary` | | `image_urls[]` | Absolute image URLs | | `categories[]` | Category **ids** (match them against `check_domain.categories`) | | `brand` | `{_id, name, slug, logo}` or missing | | `tags[]` | See [Tags](#tags) | | `custom_fields` | Inputs the shopper fills; see [Checkout → Custom fields](https://developers.blanxer.com/api-docs/checkout.md#custom-fields) | | `seo_title`, `seo_description`, `seo_image` | See [SEO and tracking](https://developers.blanxer.com/api-docs/seo-and-tracking.md) | | `total_rating`, `review_count`, `reviews[]` | Reviews: `rating`, `review`, `images`, `customer_name`, `verified_purchase`, `created_at`, newest first | | `similar_products[]` | List items: Active only, but may include POS-only products; no `channel` | | `csrf_token` | Send with custom-field image uploads (accepted, but not checked by the server today) | | `release_date`, `created_at`, `updated_at` | | > **Warning: this endpoint also returns non-Active and POS-only products.** `status` is `Active`, `Draft` or `Archived` (`''` when never set). Your product page must show a 404 unless `status === 'Active'` and `channel !== 3`. The package's `getProduct()` already returns `null` for those: > > ```ts > import { notFound } from 'next/navigation'; > const product = await getProduct(params.slug); // @blanxer-dev/storefront/server > if (!product || product.status !== 'Active' || product.channel === 3) notFound(); > ``` > > An unknown slug is HTTP **400** `{"message":"Product not found"}`, not 404. Treat it as not found. `channel`: 1 = website and POS, 2 = website only, 3 = POS only. ## Variants A product has variants when `variants.length > 0`. Its options are `colors` and/or `sizes`. ### Option names Each variant's `option_name` is built from the options: | Product has | `option_name` | |---|---| | Colours and sizes | `"/"`, e.g. `"Red/XL"` | | Sizes only | `""` | | Colours only | `""` | - A colour may carry a swatch code after the **first** colon: `"Maroon:#7a1f2b"`, or a two-tone `"Black:#111111|#e8a200"`. The display name is the part before the colon. The `option_name` uses the display name (`"Maroon/Large"`). - A colour or size name can contain `/`. Split an `option_name` against the known colour and size lists (longest colour first), not on the first slash. - Not every colour/size pair has to exist. Offer only sizes that have a variant for the chosen colour; when the colour changes and the size isn't valid any more, pick the first valid size. ### From the shopper's choice to the order ```ts function findVariant(product: Product, color: string, size: string) { const colors = product.colors.map((c) => c.split(':')[0]); const name = colors.length && product.sizes.length ? `${color}/${size}` : product.sizes.length ? size : colors.length ? color : ''; return product.variants.find((v) => v.option_name === name); // undefined = not sold } ``` - Put the variant's **`_id`** in the cart line as `variant`. The order request needs the id, not the name (`Variant selection required for this product` otherwise). - Show the variant's `price` (when set), `compare_at_price`, `image_url` (switch the gallery to it) and `sku`. - The cart line's `weight` is the variant's `weight` when it is a number, else the product's. - A product's top-level `price` is often 0 when it has variants. Never show it alone. ## Stock ### Simple stores - Out of stock for a quantity `q`: `!continue_selling && stock < q`, where `stock` is the variant's `quantity` (or the product's for a simple product). - `continue_selling` is `true` by default: the product sells even at 0 stock. Many stores keep it on. - List items have `in_stock` = `continue_selling || quantity > 0` (or any variant > 0), except where `continue_selling` isn't loaded (category, new arrivals, search, similar products): there it is stock only. See [List items](#list-items) for `quantity`. ### Stores with advanced inventory When `check_domain.use_advanced_inventory` is true and the store has a website outlet: - every `quantity` the API returns is the **website outlet's** stock, not the total; - list items' `in_stock` ignores `continue_selling` (`quantity > 0`), and the Blanxer storefront's product page does the same (out of stock = `stock < q`). Do the same. (The order check on the server still lets a `continue_selling` product through; showing it as out of stock is the safe side.) - the brand-products endpoint does not apply the outlet's stock (it returns the global quantity). Prefer the product page's own check. ### What to show - Out of stock: disable Add to cart and show the back-in-stock form when the store has it on. See [Forms and extras](https://developers.blanxer.com/api-docs/forms-and-extras.md#back-in-stock-alerts). - The `product_custom_options` plugin (in `check_domain.plugins`) can ask for stock display: `details.showProductStock` (show "N in stock") and `details.outOfStockBaseValue` (show a low-stock warning at or below that number). - The server makes the final check at order time, including units held in other shoppers' checkouts. ## Tags `tags` is free text the merchant sets. A few have meaning in the Blanxer storefront: | Tag | Meaning | |---|---| | `coming_soon` | Shown, but can't be bought yet; no back-in-stock form | | `no_price` | The price is hidden | | `team_order` | A team/bulk order product (Blanxer-specific form; not part of v1) | | `rd_