Merch ships with thirty-two designs, and if none of them is what you want, the front end is replaceable. The shop that ships with it is just one consumer of this HTTP API and holds no privileged access — so anything it does, your own front end can do, in React, Vue, Svelte, Flutter or a native app, while the invoices, the FIFO stock and the books stay exactly as they are.
Forty-six endpoints, on your own domain. Your store serves its own OpenAPI document at /api/openapi.json and a browsable page at /api/docs. This page is the same surface, so you can read it before installing anything.
The store ships as prebuilt container images — nothing to compile, and almost nothing to configure on the command line. One image runs the backend, its admin panel and Redis together; you point it at a database and paste your Ragenaizer key from inside the admin, not from a wall of flags. Then a front end sits in front — the bundled thirty-two-design storefront, or your own.
docker run postgres of your own (Redis is already inside the image). And a domain, for HTTPS.
rzc-allinone is the backend, admin panel and Redis in one — the only image you run for the store itself. rzc-frontend is the thirty-two-design storefront, portable to any backend at run time. (A leaner rzc-backend without Redis exists too, if you would rather run your own.)
Three steps — and the store itself is one command.
The backend, its admin panel and Redis, from one image. Three flags — a name, a data volume, a port — and nothing about the database or your key on the line, because those are set in the admin.
docker run -d --name store-api \ -v store-data:/data \ -p 8080:8080 abhishekanand0586/rzc-allinone:latest
Open http://your-server:8080/admin, sign in with the demo login (admin@demo / demo), go to Integrations, and paste your Postgres connection string and your Ragenaizer storefront key. They are verified together; the moment both pass, the demo login switches off and your setup saves to the store-data volume, so it survives restarts. That is the store running — if you are building your own front end, you are done here.
Want the thirty-two-design shop rather than your own front end? Run its image and tell it one thing — the address your backend answers on. That single line is the whole configuration, and the same image works against any backend:
docker run -d --name store-web \ -e MERCH_BACKEND_URL="https://api.your-store.com" \ -p 3000:3000 abhishekanand0586/rzc-frontend:latest
MERCH_BACKEND_URL is simply wherever the backend from step 1 is reachable — the domain you give it in step 3, or http://SERVER-IP:8080 for a quick look before that. Building your own front end instead? Point it at the API and set Settings → Storefront mode → Headless in the admin, so the theme controls get out of your way.
Point two DNS A records at the server — your-store.com for shoppers, api.your-store.com for the backend (its admin panel is at api.your-store.com/admin). Caddy gives each one HTTPS and renews it itself. Save this as Caddyfile…
your-store.com {
reverse_proxy localhost:3000
}
api.your-store.com {
reverse_proxy localhost:8080
}
…then run Caddy, the only thing that opens a port to the internet:
docker run -d --name store-proxy --network host \ -v $PWD/Caddyfile:/etc/caddy/Caddyfile -v caddy-data:/data caddy:2
Firewall 8080 and 3000 so only Caddy is reachable from outside — the backend trusts X-Forwarded-For for rate limiting and must always sit behind the proxy.
Want even that done for you? The installer stands up the backend plus a Postgres, a Redis and HTTPS from a single command — it asks for your domain and key and wires the rest: curl -fsSL https://raw.githubusercontent.com/Abhi-hyperscripts/ragenaizer-commerce/main/install.sh | bash
Updates are a docker pull of the newer image and a restart — your database connection and keys live in the store-data volume, encrypted, so they survive it. Set RZC_MASTER_KEY once and keep a copy if you want a restore on a fresh machine to still decrypt them; otherwise a key is generated into that volume for you.
Everything on this page, already built. No framework, no build step, no dependencies — separate .html, .css and .js files, so a team that knows HTML, CSS and JavaScript can read all of it and change any of it. Point one line at your shop and it runs.
Exercised against a real store: an order was placed from the browser and the total shown matched the amount charged, to the paisa.
git clone https://github.com/Abhi-hyperscripts/merch-reference-storefront cd merch-reference-storefront # edit ONE line in config.js: # export const API_BASE = 'https://shop.mybrand.com'; python3 -m http.server 5610
It is a folder of static files, so it deploys to GitHub Pages, Netlify, Vercel or your own nginx with nothing to build. And it does not have to live on the same domain as your shop.
MIT licensed — fork it, strip it, rewrite it. The README calls out the handful of things that are genuinely easy to get wrong, including the two checkout paths below and a shipping flag whose name means the opposite of what you would assume. Read the code on GitHub →
https://shop.example.com/api/…. There is no Ragenaizer-hosted API to sign up for and no extra host or port; the store you installed is the API.
Authorization: Bearer <token>. A shopper token comes from the shopper sign-in endpoint; the browsing endpoints need no token at all. It is a stateless JWT, so there is no session affinity and no cookie handling — a mobile app authenticates exactly as a browser does.
Authorization header and not a cookie, nothing depends on the two sharing a site.
{"error": "…"}, with a message written to be shown to a shopper as-is. 400 is a rejected request, 401 a missing or expired token, 404 an unknown id, 429 rate limiting, and 503 the store being temporarily unreachable — that last one adds "storeUnavailable": true so you can tell it apart from an ordinary refusal and retry rather than blame the shopper.
429 in the usual error shape. The catalogue, collections and blog are not limited, so a browse-heavy front end is not penalised.
2026-08-09T00:00:00Z — always UTC with the trailing Z, never the store’s local time, so convert to the shopper’s zone in your own front end. new Date(value) parses them directly. The field name is not a reliable signal: alongside createdAt, updatedAt, fulfilledAt, cancelledAt and resolvedAt there are nextRun on a subscription and at on an order event, which carry the same format without the suffix.
There is one kind of credential on this surface: a shopper token, sent as Authorization: Bearer <token>. Everything else is anonymous. Worth reading before you design your sign-in flow, because a shopper can browse, fill a cart and complete a purchase without ever making an account — only the things that belong to a person need a token.
GET /api/auth/config first. It answers { google: { enabled, clientId } }.enabled is false the merchant has not set Google up. Render a guest-only shop — do not show a sign-in button that cannot work.clientId, then POST the resulting ID token to /api/shopper/google. You get a shopper token back.Authorization: Bearer <token> on the calls listed below.Google is the only way in. There is no email/password registration, no OTP and no magic link. If your merchant's customers do not use Google, plan a guest-only experience — every money path still works.
Seventeen of the forty-six. They are exactly the calls that read or change something belonging to a person, and each is marked security: bearer in the OpenAPI document, so a generated client attaches the token for you.
/api/shopper/me, /orders, /addresses (all four verbs), /wishlist./api/subscriptions (list, create, pause/resume/cancel) and /api/returns (list, create).POST /api/orders/{id}/cancel and POST /api/products/{itemId}/reviews.Everything else — the catalogue, search, collections, blog, reviews (reading), shipping quotes, coupon and gift-card previews, checkout, payment and order lookup — is anonymous.
POST /api/checkout and the whole payment flow take no token. A guest finds their order afterwards with POST /api/orders/lookup (email + order reference) or by the order's own id. Sending a token where one is optional is still useful — the order is then linked to that shopper and appears in their history — but nothing refuses you for omitting it.
POST /api/shopper/google is where a token comes from, so it does not require one. It is the single exception to the /api/shopper/* rule, and it is marked correctly in the spec: a generated client will call it without credentials.
Browsing is one endpoint — GET /api/catalog — and it takes query parameters. Knowing exactly where the line falls saves you hunting for a brand-filter endpoint that does not exist: the server narrows the catalogue by category, price, stock, text and order; every other facet you have seen on a shop — brand, colour, size, rating, on-offer — is derived on the client from the products it returns. That is how the bundled storefront does it too, so it is a supported pattern, not a workaround.
| Parameter | Type | Notes |
|---|---|---|
search | string | Matches name, description and category, so a category tile can link to ?search=Coffee and still find things. |
category | string | One category name, as returned by GET /api/categories. |
minPrice / maxPrice | decimal | Inclusive bounds, in the store's base currency. |
inStock | boolean | true drops anything not currently sellable. |
sort | string | price_asc, price_desc or name. Anything else — including omitting it — keeps the natural order. |
locale | string | Returns translated name and description where the merchant has supplied them. |
ids | string | Comma-separated ids. Use this to refresh a cart — see the note below; it behaves differently from the browse filters on purpose. |
All parameters are optional and combine. GET /api/catalog with none of them is the whole sellable catalogue.
These are not query parameters and there is no endpoint for them — but every product already carries the data, so you build them from the list you just fetched:
brandSlug and label with brandName. A live grocery tenant yields 639 distinct brands this way.attributes is a free-form list of {key, value}. Group by key to discover which facets a catalogue even has, then by value for the options. Deliberately not fixed columns: a pharmacy's strength matters as much as an apparel store's size, and both arrive the same way.categoryPath is root→leaf including the leaf; categorySlug is the leaf on its own. The flat category string cannot express a hierarchy, which is why both exist.price against mrp.Every one of these is nullable. A merchant who has not filled in their item master sends null, and the honest rendering of that is no facet at all — never a blank or placeholder option.
Facet counts need the full set; the product grid does not. Count facets from an unpaged call, then fetch the grid with page and pageSize — the server applies your filters before it pages, so X-Total-Count is the number of matches, not the number on the page. On a large catalogue that keeps the first paint small without making the counts wrong.
Rating is the exception, and it is not cheap yet. Ratings live on GET /api/products/{itemId}/reviews, one product at a time — there is no bulk source, so a rating filter over a listing means one call per product. Use it on the product page, and tell us if you need it as a facet; a batch endpoint is the fix and we would rather add it than have you fan out.
ids ignores the browse filters, deliberatelyWhen you pass ids, the browse facets (search, category, price, stock) are not applied, and variants are returned individually rather than folded to one representative per group. That is what makes it safe for refreshing a cart: a line the shopper already chose must not vanish because a filter happens to be in the URL, and a specific variant must come back as itself. A product that has genuinely been removed, hidden or unpriced is still absent — which is exactly the signal you want when reconciling a stale cart.
GET /api/categories returns the category names present in the catalogue — a flat list of strings, derived from the products themselves, for building a nav or filter rail. GET /api/collections returns merchandising groups the merchant curates by hand or by rule; a smart collection is resolved at request time, so it stays current without anyone re-saving it. Use categories for navigation, collections for campaigns.
Both take the same request body, so nothing about their shapes tells you which to call. Choosing wrong is the one mistake here that costs money, so it is worth thirty seconds: an order is created by exactly one call, and which call it is depends on whether you are collecting payment up front.
POST /api/checkout with the cart, the customer, and any coupon or gift card.Do not touch the payment endpoints on this path.
POST /api/payment/create-order with that same body. Nothing is ordered yet; you get back what the payment widget needs.keyId, amount and razorpayOrderId it returned. Note the amount is in paise, not rupees. The gateway window is Razorpay’s own script, not something this API serves — load https://checkout.razorpay.com/v1/checkout.js, then call new Razorpay(options).open(). Its options and callback are documented by Razorpay; the reference storefront lazy-loads it in js/checkout.js if you want a worked example.POST /api/payment/verify with them. This is the call that creates the order, and it answers with the same shape /api/checkout would have.Never call /api/checkout as well. It would place a second, unpaid order for the same basket.
Two things that will save you writing code you do not need. Verifying twice is safe — a repeated verify returns the order already created, never a second one — so a retry after a dropped connection needs no guard of your own. And if the shopper pays and closes the tab before your verify fires, the store still receives the gateway’s own notification and creates the order anyway, so that basket is not lost.
Every one of these is documented per-operation in the OpenAPI document, so a generated client can branch on them. The distinction worth internalising: a 4xx is an answer about your request; a 5xx or a timeout is the absence of an answer. Rendering the second as though it were the first is the single most common way a storefront lies to a shopper.
| Status | Means | What your front end should do |
|---|---|---|
400 | The request is malformed or the basket is not sellable — an unpriced item, a bad pincode, an empty cart. | Show the message. It is written for the shopper, not for you. |
401 | No token, or one that has expired. | Send them back through sign-in and retry. Never treat it as "you have nothing". |
404 | Genuinely absent — no such product, no such order. | Render an empty state. This is a definite answer. |
409 | The state moved under you: an order already shipped, a gift card already spent, a duplicate submission. | Re-read and show the current state. Do not blindly retry. |
429 | Rate limited. Login, checkout and payment are throttled per client. | Back off and retry. This is not a failure — treating it as one turns a slow moment into a lost sale. |
503 + storeUnavailable | The shop could not reach its own books — a lapsed licence, an upstream blip. Nothing is wrong with your request. | Say the shop is temporarily unavailable and offer a retry. Do not show "no products" or "no orders". |
There is no Retry-After header today, so choose your own back-off — a couple of seconds, doubling, is plenty. If you want the header, ask and we will add it.
/api/shopper/orders tells a customer their orders are gone; on /api/returns it tells them they cannot file one. Keep "we could not load this" and "there is none" as different states in your UI — it is one boolean, and it is the difference between a retry and a support ticket.
The store serves your front end and the API from one domain, so the ordinary case involves no CORS at all: you call /api/… as a relative path and the browser never asks a cross-origin question. That is the shape the bundled storefront uses, and the one we would recommend — it also means the merchant's Ragenaizer key stays server-side and never touches a browser.
Run your own front end on the store's domain and call /api/* relatively. Nothing to configure, no preflight, no origin list to maintain. The shipped admin panel keeps working alongside it.
Point it at https://the-store-domain and call the same paths absolutely. CORS is open: the store reflects your origin, and preflight requests for Content-Type: application/json and Authorization are answered correctly, so writes and authenticated reads work from any origin.
If you are hitting an older store image and a POST fails while a GET succeeds, that is the symptom of a preflight that was not being forwarded. It is fixed — update the storefront image.
/api/webhooks/* exists for Ragenaizer and Razorpay to call the store, authenticated by signature. It is deliberately absent from the OpenAPI document, is never called by a browser, and needs nothing from your front end. If you are looking for "how do I know an order was paid", the answer is to read the order, not to receive a webhook.
Grouped the way a front end consumes them. Open any one for a runnable curl, what it does, its parameters, and the JSON going in and out. The shapes are illustrative — field names and types are real, the values are placeholders.
Searches the path, the method and the description. Press / to jump here, Esc to clear.
No endpoint matches that. The full reference is /api/openapi.json if you would rather search the machine-readable spec.
/api/catalogThe product list, returned as a bare array — there is no envelope, and there is none when you page either. Omit page and pageSize and one call gives you the whole visible catalogue, exactly as it always has. Add them and you get that page, still as a plain array, with the counts in the X-Total-Count and X-Total-Pages response headers — both CORS-exposed, so a front end on another origin can read them. pageSize defaults to 24 and is capped at 200; a page below 1 is treated as the first, and a page past the end is an empty array rather than the last page, so an infinite scroll stops instead of repeating. Paging is applied after variant folding, so pages never overlap or skip a card. It does not apply to the ids form below, which resolves cart lines and would silently drop items if it were sliced. Optional query: search, category, minPrice, maxPrice, inStock, locale, and sort, which accepts exactly price_asc, price_desc or name — any other value is ignored rather than rejected, so a typo silently returns catalogue order. Products that share a variant group fold to one card here; use the variants endpoint to expand it. Prices and stock are live from the merchant's books, and hidden products are already excluded.
curl https://your-shop.example.com/api/catalog
# one page, with the totals in the headers
curl -D - -o /dev/null \
"https://your-shop.example.com/api/catalog?page=2&pageSize=24"
# → X-Total-Count: 7829
# → X-Total-Pages: 327
search query stringcategory query stringminPrice query numbermaxPrice query numberinStock query booleansort query stringlocale query stringpage query integerpageSize query integer[
{
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"description": "Pure, unrefined oil extracted from premium organic peanuts using traditional cold-pressing methods.",
"imageUrls": [
"https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png"
],
"category": "Edible Oils",
"unit": "pcs",
"price": 468.0,
"mrp": 520.0,
"taxNote": "incl. GST",
"availability": "in_stock",
"qtyHint": 12,
"featured": true,
"variantGroupId": null,
"variantCount": 0,
"isDigital": false,
"brandId": "41b34130-6ecb-4a0b-ac7d-96b0ffb41913",
"brandName": "24 Mantra",
"brandSlug": "24-mantra",
"categoryId": "29d835f4-92f8-4308-b04d-d2c4b81616c1",
"categorySlug": "edible-oils",
"categoryPath": [
{
"id": "60834fd0-e7f6-456c-a4a9-88bec7e4b472",
"name": "Masalas, Oils & Dry Fruits",
"slug": "masalas-oils-dry-fruits"
}
],
"attributes": [
{
"key": "size",
"value": "1 ltr"
}
]
}
]
/api/catalog/{id}One product in full, including its description, images and live price and availability.
curl https://your-shop.example.com/api/catalog/ITM-001
id path string requiredlocale query string{
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"description": "Pure, unrefined oil extracted from premium organic peanuts using traditional cold-pressing methods.",
"imageUrls": [
"https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png"
],
"category": "Edible Oils",
"unit": "pcs",
"price": 468.0,
"mrp": 520.0,
"taxNote": "incl. GST",
"availability": "in_stock",
"qtyHint": 12,
"featured": true,
"variantGroupId": "8f2c1a90-5d33-4e77-9a10-6b0d2f4c7e51",
"variantCount": 3,
"isDigital": false,
"brandId": "41b34130-6ecb-4a0b-ac7d-96b0ffb41913",
"brandName": "24 Mantra",
"brandSlug": "24-mantra",
"categoryId": "29d835f4-92f8-4308-b04d-d2c4b81616c1",
"categorySlug": "edible-oils",
"categoryPath": [
{
"id": "60834fd0-e7f6-456c-a4a9-88bec7e4b472",
"name": "Masalas, Oils & Dry Fruits",
"slug": "masalas-oils-dry-fruits"
}
],
"attributes": [
{
"key": "size",
"value": "1 ltr"
}
]
}
/api/categoriesThe category facets present in the catalogue, for building navigation or a filter rail.
curl https://your-shop.example.com/api/categories
[ "All Purpose Cleaners" ]
/api/collectionsMerchandising collections — manual picks and rule-based ones — as configured by the merchant.
curl https://your-shop.example.com/api/collections
[
{
"id": "c8a472e1-5b09-4f63-a71d-0e26b3f95d47",
"handle": "cold-pressed-oils",
"title": "Cold Pressed Oils",
"description": "Pure, unrefined oil extracted from premium organic peanuts using traditional cold-pressing methods.",
"imageUrl": "https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png"
}
]
/api/collections/{handle}One collection and the products currently in it, resolved at request time so a smart collection stays current.
curl https://your-shop.example.com/api/collections/coffee-essentials
handle path string requiredlocale query string{
"collection": {
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"handle": "cold-pressed-oils",
"title": "Cold Pressed Oils",
"description": "Pure, unrefined oil extracted from premium organic peanuts using traditional cold-pressing methods.",
"imageUrl": "https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png"
},
"products": [
{
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"description": "Pure, unrefined oil extracted from premium organic peanuts using traditional cold-pressing methods.",
"imageUrls": [
"https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png"
],
"category": "Edible Oils",
"unit": "pcs",
"price": 468.0,
"mrp": 520.0,
"taxNote": "incl. GST",
"availability": "in_stock",
"qtyHint": 12,
"featured": true,
"variantGroupId": "8f2c1a90-5d33-4e77-9a10-6b0d2f4c7e51",
"variantCount": 3,
"isDigital": false,
"brandId": "41b34130-6ecb-4a0b-ac7d-96b0ffb41913",
"brandName": "24 Mantra",
"brandSlug": "24-mantra",
"categoryId": "29d835f4-92f8-4308-b04d-d2c4b81616c1",
"categorySlug": "edible-oils",
"categoryPath": [
{
"id": "60834fd0-e7f6-456c-a4a9-88bec7e4b472",
"name": "Masalas, Oils & Dry Fruits",
"slug": "masalas-oils-dry-fruits"
}
],
"attributes": [
{
"key": "size",
"value": "1 ltr"
}
]
}
]
}
/api/coupon/validatePreview a coupon code against the current basket without applying it. The subtotal is computed server-side from live prices, so a client cannot claim a total.
curl -X POST https://your-shop.example.com/api/coupon/validate \
-H "Content-Type: application/json" \
-d '{"code":"SAVE10","lines":[{"itemId":"ITM-001","qty":2}]}'
{
"code": "SAVE10",
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"qty": 2
}
]
}
{
"valid": true,
"reason": "Applied.",
"code": "SAVE10",
"discountAmount": 93.6,
"newSubtotal": 842.4
}
/api/currenciesThe display currencies the merchant has enabled, with their rates, for a currency switcher.
curl https://your-shop.example.com/api/currencies
{
"base": {
"code": "SAVE10",
"symbol": "₹"
},
"currencies": [
{
"code": "SAVE10",
"symbol": "₹",
"rate": 1.0
}
]
}
/api/discounts/autoPreview the best automatic (no-code) discount for the current basket, so the saving can be shown before checkout.
curl -X POST https://your-shop.example.com/api/discounts/auto \
-H "Content-Type: application/json" \
-d '{"lines":[{"itemId":"ITM-001","qty":2}]}'
{
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"qty": 2
}
]
}
{
"applies": true,
"title": "Cold Pressed Oils",
"discountAmount": 93.6,
"newSubtotal": 842.4,
"freeShipping": false
}
/api/discounts/bxgyPreview any buy-X-get-Y saving that applies to the current basket.
curl -X POST https://your-shop.example.com/api/discounts/bxgy \
-H "Content-Type: application/json" \
-d '{"lines":[{"itemId":"ITM-001","qty":2}]}'
{
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"qty": 2
}
]
}
{
"applies": true,
"title": "Cold Pressed Oils",
"discountAmount": 93.6
}
/api/eventsReport a small batch of funnel events (viewed, added to cart, and so on) for the merchant's analytics. Unknown event types are ignored rather than rejected.
curl -X POST https://your-shop.example.com/api/events \
-H "Content-Type: application/json" \
-d '{"sessionId":"string","events":[{"type":"view","itemId":"ITM-001"}]}'
{
"sessionId": "sess_7f3a91c2b8d4",
"events": [
{
"type": "view",
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4"
}
]
}
{
"ok": true,
"recorded": 1
}
/api/giftcard/checkCheck a gift card's balance before offering it at checkout. This does not redeem it.
curl -X POST https://your-shop.example.com/api/giftcard/check \
-H "Content-Type: application/json" \
-d '{"code":"GIFT500"}'
{
"code": "GIFT500"
}
{
"valid": true,
"reason": "Applied.",
"code": "SAVE10",
"balance": 500.0
}
/api/media/ph/{seed}.svgA deterministic placeholder image, generated locally. Useful while building against a demo store with no product photography.
curl https://your-shop.example.com/api/media/ph/ITM-001-a.svg
seed path string requiredAnswers with an image (image/svg+xml), not JSON.
/api/products/{id}/variantsThe sibling options of a product (size, colour and so on) for a variant selector on the product page.
curl https://your-shop.example.com/api/products/ITM-001/variants
id path string requiredlocale query string{
"groupId": "8f2c1a90-5d33-4e77-9a10-6b0d2f4c7e51",
"title": "Size",
"options": [
{
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"label": "1 ltr",
"price": 468.0,
"availability": "in_stock",
"current": true
}
]
}
/api/products/{itemId}/reviewsPublished reviews for a product, with the rating summary used for stars on a listing.
curl https://your-shop.example.com/api/products/ITM-001/reviews
itemId path string required{
"summary": {
"average": 4.6,
"count": 38
},
"reviews": [
{
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"author": "Priya S.",
"rating": 5,
"title": "Exactly as described",
"body": "Genuinely cold pressed — the aroma is unmistakable. Repeat purchase.",
"verified": true,
"createdAt": "2026-08-21T09:14:03Z"
}
]
}
/api/products/{itemId}/reviewstokenWrite or update the signed-in shopper's review. Requires a shopper token. Whether the review counts as verified is decided server-side from that shopper's order history — a client cannot claim it.
curl -X POST https://your-shop.example.com/api/products/ITM-001/reviews \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"rating":5,"title":"string","body":"string"}'
Requires Authorization: Bearer <shopper token>.
itemId path string required{
"rating": 5,
"title": "Exactly as described",
"body": "Genuinely cold pressed — the aroma is unmistakable. Repeat purchase."
}
{
"ok": true,
"verified": true
}
/api/shipping/quoteThe delivery cost for a destination pincode and basket. This is the FINAL figure checkout will charge, resolved the same way the order is priced, so the number shown is the number billed. READ options, NOT available. Despite the name, available reports only whether a LIVE COURIER RATE was found — a store on flat-rate shipping, or one that has not connected a courier account, answers available: false on every quote while shipping perfectly happily. Wiring that flag to “we cannot deliver to your pincode” tells most shoppers of most stores that the shop does not serve them. options is always populated and is the real answer; shipping is the cheapest option’s amount.
curl -X POST https://your-shop.example.com/api/shipping/quote \
-H "Content-Type: application/json" \
-d '{"pincode":"110001","lines":[{"itemId":"ITM-001","qty":2}],"paymentMethod":"cod"}'
{
"pincode": "110001",
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"qty": 2
}
],
"paymentMethod": "cod"
}
{
"available": true,
"shipping": 49.0,
"courier": "Delhivery",
"etaDays": 3,
"options": [
{
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"amount": 49.0,
"etaMin": 2,
"etaMax": 4,
"live": true
}
]
}
/api/themeThe shop's branding and layout — colours, fonts, logo, banners, legal pages and the chosen design preset. Fetch this first; it is what makes the storefront look like this particular business.
curl https://your-shop.example.com/api/theme
{
"brandName": "Your Store",
"tagline": "Powered by Ragenaizer",
"logoUrl": "https://cdn.yourshop.com/brand/logo.svg",
"faviconUrl": "https://cdn.yourshop.com/brand/favicon.png",
"layout": "editorial-modern",
"colors": {
"primary": "#1F2937",
"primaryHover": "#111827",
"accent": "#F97316",
"bg": "#FCFCFB",
"surface": "#FFFFFF",
"text": "#141821",
"muted": "#6B7280",
"border": "#EAEBEE"
},
"radius": "10px",
"fontHeading": "Space Grotesk",
"fontBody": "Inter",
"customCss": "",
"hero": {
"headline": "Everything your shop makes, now online.",
"subtext": "Browse the full catalog. Every order books straight into the accounts.",
"ctaText": "Shop the catalog",
"imageUrl": ""
},
"banners": [],
"featuredItemIds": [],
"hiddenItemIds": [],
"navLinks": [
{
"label": "Shop",
"href": "/shop",
"labels": null
}
],
"footerLinks": [],
"currency": "INR",
"currencySymbol": "₹",
"footer": {
"about": "",
"phone": "9999999999",
"email": "shopper@example.com",
"address": ""
},
"shipping": {
"flatRate": 0,
"freeAbove": 0,
"useLiveRates": true,
"zones": []
},
"payment": {
"codEnabled": true,
"onlineEnabled": false
},
"pages": {
"privacy": "## Privacy Policy\nWe collect the information you provide when you crea …",
"terms": "## Terms of Service\nBy placing an order with us you agree to these ter …",
"refund": "## Returns & Refunds\nIf something isn't right, contact us within 7 day …",
"shipping": "## Shipping Policy\nWe ship across India. Orders are typically dispatch …",
"about": "## About Us\nWe're a small team who care about the things we make and s …"
},
"customPages": [],
"priceListId": null,
"storefrontMode": "auto",
"homeSections": []
}
/api/storefront/registerA themed storefront announces its identity and theme catalogue here, on boot and periodically. It lets the admin panel render its theme picker from the connected storefront's own themes, and lets the shop tell an official storefront apart from a custom headless front end. The bundled storefront image calls this for you — a custom front end does not need it, and ignoring it costs you nothing but the admin theme picker.
curl -X POST https://your-shop.example.com/api/storefront/register \
-H "Content-Type: application/json" \
-d '{"storefront":"my-frontend","version":"1.0.0","themes":[]}'
{
"storefront": "acme-storefront",
"version": "1.4.0",
"themes": [
{
"key": "editorial-modern",
"name": "Priya Sharma",
"vertical": "grocery",
"emoji": "🛒",
"blurb": "Editorial layout with a large hero and category rail.",
"archetype": "editorial",
"colors": {
"primary": "#1F2937",
"primaryHover": "#111827",
"accent": "#F97316",
"bg": "#FCFCFB",
"surface": "#FFFFFF",
"text": "#141821",
"muted": "#6B7280",
"border": "#EAEBEE"
},
"radius": "12px",
"fontHeading": "Bricolage Grotesque",
"fontBody": "Inter",
"dark": false
}
]
}
{
"ok": true
}
/api/cart/savePersist the in-progress basket against an email address. This is what makes abandoned-cart recovery possible; guests are included deliberately.
curl -X POST https://your-shop.example.com/api/cart/save \
-H "Content-Type: application/json" \
-d '{"email":"shopper@example.com","lines":[{"itemId":"ITM-001","name":"string","qty":2,"price":0.0}]}'
{
"email": "shopper@example.com",
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "Priya Sharma",
"qty": 2,
"price": 468.0
}
]
}
{
"ok": true
}
/api/checkoutPlace a Cash-on-Delivery order. Works for a guest with inline customer details or for a signed-in shopper. Online payment uses the payment endpoints instead, because there the order must not exist until the money is verified.
curl -X POST https://your-shop.example.com/api/checkout \
-H "Content-Type: application/json" \
-d '{"lines":[{"itemId":"ITM-001","qty":2}],"customer":{"name":"string","email":"shopper@example.com","phone":"9999999999","address":"string","city":"New Delhi","state":"DL","pincode":"110001"},"paymentMethod":"cod","idempotencyKey":"string","couponCode":"SAVE10","giftCardCode":"GIFT500","shippingRateId":"string"}'
{
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"qty": 2
}
],
"customer": {
"name": "Priya Sharma",
"email": "shopper@example.com",
"phone": "9999999999",
"address": "12 Nehru Place, New Delhi 110001",
"city": "New Delhi",
"state": "DL",
"pincode": "110001"
},
"paymentMethod": "cod",
"idempotencyKey": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"couponCode": "SAVE10",
"giftCardCode": "GIFT500",
"shippingRateId": "standard"
}
{
"id": "7c4d1e88-9a02-4f31-b6c5-2d81e0aa93f4",
"invoiceId": "INV-2026-00142",
"orderRef": "ORD-2026-00142",
"status": "paid",
"total": 985.6,
"invoicePdfUrl": "https://yourshop.com/invoices/INV-2026-00142.pdf",
"giftCardApplied": 0.0,
"amountDue": 985.6
}
/api/orders/lookupGuest order lookup by email plus an order reference. Both must match, and it is rate limited, so it cannot be used to enumerate other people's orders.
curl -X POST https://your-shop.example.com/api/orders/lookup \
-H "Content-Type: application/json" \
-d '{"email":"shopper@example.com","reference":"SO-1A2B3C4D"}'
{
"email": "shopper@example.com",
"reference": "SO-1A2B3C4D"
}
{
"id": "7c4d1e88-9a02-4f31-b6c5-2d81e0aa93f4",
"invoiceId": "INV-2026-00142",
"orderRef": "ORD-2026-00142",
"status": "paid",
"total": 985.6,
"invoicePdfUrl": "https://yourshop.com/invoices/INV-2026-00142.pdf",
"createdAt": "2026-08-21T09:14:03Z",
"awb": "SRTP0012345678",
"trackingUrl": "https://www.delhivery.com/track/package/SRTP0012345678",
"fulfillmentStatus": "unfulfilled",
"carrier": "Delhivery",
"trackingNumber": "SRTP0012345678",
"fulfilledAt": "2026-08-23T06:30:00Z",
"cancelledAt": "2026-08-22T17:45:12Z"
}
/api/orders/{id}An order's current status and timeline, for the confirmation page.
curl https://your-shop.example.com/api/orders/ITM-001
id path string required{
"order": {
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"invoiceId": "INV-2026-00142",
"orderRef": "ORD-2026-00142",
"status": "paid",
"total": 985.6,
"invoicePdfUrl": "https://yourshop.com/invoices/INV-2026-00142.pdf",
"createdAt": "2026-08-21T09:14:03Z",
"awb": "SRTP0012345678",
"trackingUrl": "https://www.delhivery.com/track/package/SRTP0012345678",
"fulfillmentStatus": "unfulfilled",
"carrier": "Delhivery",
"trackingNumber": "SRTP0012345678",
"fulfilledAt": "2026-08-23T06:30:00Z",
"cancelledAt": "2026-08-22T17:45:12Z"
},
"events": [
{
"type": "payment_captured",
"at": "2026-08-21T09:14:03Z"
}
],
"access": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"url": "https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png"
}
]
}
/api/orders/{id}/canceltokenLet a signed-in shopper cancel their own order while it is still unfulfilled. Reverses the invoice with a credit note and emails a confirmation. A shipped order cannot be cancelled — it must be returned instead — and only the order's owner may cancel it.
curl -X POST https://your-shop.example.com/api/orders/3fa85f64-5717-4562-b3fc-2c963f66afa6/cancel \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason":"Ordered the wrong size"}'
Requires Authorization: Bearer <shopper token>. Returns 409 if the order has already shipped, and 404 if it is not yours.
{
"reason": "Seal was broken on arrival."
}
{
"ok": true
}
/api/auth/configWhich sign-in methods this shop offers, so the front end knows whether to render a Google button. The client id it returns is public by design.
curl https://your-shop.example.com/api/auth/config
{
"google": {
"enabled": true,
"clientId": "812345678901-ab1cd2ef3gh4ij5kl6mn7op8qr9st0uv.apps.googleusercontent.com"
}
}
/api/shopper/addressestokenThe shopper's saved delivery addresses. Requires a shopper token.
curl https://your-shop.example.com/api/shopper/addresses \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
[
{
"id": "d5810c37-2f96-4ab4-85e3-1c740b9fd268",
"name": "Priya Sharma",
"phone": "9999999999",
"line": "12 Nehru Place",
"city": "New Delhi",
"state": "DL",
"pincode": "110001",
"isDefault": true
}
]
/api/shopper/addressestokenSave a new delivery address for the signed-in shopper.
curl -X POST https://your-shop.example.com/api/shopper/addresses \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"string","phone":"9999999999","line":"12 Nehru Place","city":"New Delhi","state":"DL","pincode":"110001","makeDefault":false}'
Requires Authorization: Bearer <shopper token>.
{
"name": "Priya Sharma",
"phone": "9999999999",
"line": "12 Nehru Place",
"city": "New Delhi",
"state": "DL",
"pincode": "110001",
"makeDefault": false
}
{
"id": "d5810c37-2f96-4ab4-85e3-1c740b9fd268",
"name": "Priya Sharma",
"phone": "9999999999",
"line": "12 Nehru Place",
"city": "New Delhi",
"state": "DL",
"pincode": "110001",
"isDefault": true
}
/api/shopper/addresses/{id}tokenRemove one of the shopper's saved addresses.
curl -X DELETE https://your-shop.example.com/api/shopper/addresses/ITM-001 \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
id path string required{
"ok": true
}
/api/shopper/addresses/{id}tokenUpdate one of the shopper's saved addresses.
curl -X PUT https://your-shop.example.com/api/shopper/addresses/ITM-001 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"string","phone":"9999999999","line":"12 Nehru Place","city":"New Delhi","state":"DL","pincode":"110001","makeDefault":false}'
Requires Authorization: Bearer <shopper token>.
id path string required{
"name": "Priya Sharma",
"phone": "9999999999",
"line": "12 Nehru Place",
"city": "New Delhi",
"state": "DL",
"pincode": "110001",
"makeDefault": false
}
{
"ok": true
}
/api/shopper/addresses/{id}/defaulttokenMark a saved address as the shopper's default for checkout.
curl -X POST https://your-shop.example.com/api/shopper/addresses/ITM-001/default \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
id path string required{
"ok": true
}
/api/shopper/googletokenSign in with a Google ID token. The token is verified server-side and the shopper is found or created from the verified email. Returns a shopper JWT.
curl -X POST https://your-shop.example.com/api/shopper/google \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"credential":"string"}'
Requires Authorization: Bearer <shopper token>.
{
"credential": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjB"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzaG9wcGVyIn0.7Qf1m0Zt3xR",
"shopper": {
"id": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"email": "shopper@example.com",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"phone": "9999999999"
}
}
/api/shopper/metokenThe signed-in shopper's profile. Requires a shopper token.
curl https://your-shop.example.com/api/shopper/me \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
{
"id": "4e6b0a92-8d17-42f5-b3c9-7a05e2d81f34",
"email": "shopper@example.com",
"name": "Priya Sharma",
"phone": "9999999999",
"linked": true
}
/api/shopper/orderstokenThe signed-in shopper's order history. Requires a shopper token.
curl https://your-shop.example.com/api/shopper/orders \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
[
{
"id": "7c4d1e88-9a02-4f31-b6c5-2d81e0aa93f4",
"invoiceId": "INV-2026-00142",
"orderRef": "ORD-2026-00142",
"status": "paid",
"total": 985.6,
"invoicePdfUrl": "https://yourshop.com/invoices/INV-2026-00142.pdf",
"createdAt": "2026-08-21T09:14:03Z",
"awb": "SRTP0012345678",
"trackingUrl": "https://www.delhivery.com/track/package/SRTP0012345678",
"fulfillmentStatus": "unfulfilled",
"carrier": "Delhivery",
"trackingNumber": "SRTP0012345678",
"fulfilledAt": "2026-08-23T06:30:00Z",
"cancelledAt": "2026-08-22T17:45:12Z"
}
]
/api/shopper/wishlisttokenThe signed-in shopper's wishlist. Requires a shopper token.
curl https://your-shop.example.com/api/shopper/wishlist \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
[ "16a8357b-4fa1-40ff-a70c-43094daee1d4" ]
/api/shopper/wishlisttokenAdd a product to the shopper's wishlist.
curl -X POST https://your-shop.example.com/api/shopper/wishlist \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"itemId":"ITM-001"}'
Requires Authorization: Bearer <shopper token>.
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4"
}
{
"ok": true
}
/api/shopper/wishlist/{itemId}tokenRemove a product from the shopper's wishlist.
curl -X DELETE https://your-shop.example.com/api/shopper/wishlist/ITM-001 \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
itemId path string required{
"ok": true
}
/api/payment/configWhether online payment is switched on, and the public key needed to open the payment widget. Never returns the secret.
curl https://your-shop.example.com/api/payment/config
{
"enabled": true,
"keyId": "rzp_live_A1b2C3d4E5f6G7",
"currency": "INR",
"symbol": "₹",
"name": "Your Store"
}
/api/payment/create-orderPrice the basket server-side and open a payment against it. The amount is computed from live prices, coupons and gift cards — the client never states what it owes.
curl -X POST https://your-shop.example.com/api/payment/create-order \
-H "Content-Type: application/json" \
-d '{"lines":[{"itemId":"ITM-001","qty":2}],"customer":{"name":"string","email":"shopper@example.com","phone":"9999999999","address":"string","city":"New Delhi","state":"DL","pincode":"110001"},"paymentMethod":"cod","idempotencyKey":"string","couponCode":"SAVE10","giftCardCode":"GIFT500","shippingRateId":"string"}'
{
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"qty": 2
}
],
"customer": {
"name": "Priya Sharma",
"email": "shopper@example.com",
"phone": "9999999999",
"address": "12 Nehru Place, New Delhi 110001",
"city": "New Delhi",
"state": "DL",
"pincode": "110001"
},
"paymentMethod": "cod",
"idempotencyKey": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"couponCode": "SAVE10",
"giftCardCode": "GIFT500",
"shippingRateId": "standard"
}
{
"razorpayOrderId": "order_PjK2mNqR8sT1vX",
"amount": 98560,
"currency": "INR",
"keyId": "rzp_live_A1b2C3d4E5f6G7",
"name": "Your Store",
"prefill": {
"name": "Priya Sharma",
"email": "shopper@example.com",
"contact": "9999999999"
}
}
/api/payment/verifyVerify the signed payment result and create the order. Idempotent per payment, so a retried or duplicated callback cannot produce two orders.
curl -X POST https://your-shop.example.com/api/payment/verify \
-H "Content-Type: application/json" \
-d '{"razorpayOrderId":"string","razorpayPaymentId":"string","razorpaySignature":"string"}'
{
"razorpayOrderId": "order_PjK2mNqR8sT1vX",
"razorpayPaymentId": "pay_PjK2nOqS9tU2wY",
"razorpaySignature": "9a1f0c5e7b3d2a8f4c6e0b1d5a9f3c7e2b8d4a6f0c1e5b9d3a7f2c8e4b0d6a1f"
}
{
"id": "7c4d1e88-9a02-4f31-b6c5-2d81e0aa93f4",
"invoiceId": "INV-2026-00142",
"orderRef": "ORD-2026-00142",
"status": "paid",
"total": 985.6,
"invoicePdfUrl": "https://yourshop.com/invoices/INV-2026-00142.pdf",
"giftCardApplied": 0.0,
"amountDue": 985.6
}
/api/payment/abandonTell the shop the payment window was closed or failed, so any gift-card balance reserved for that attempt is handed straight back instead of staying locked until the reservation lapses. Moves no money and is safe to call more than once — call it whenever the Razorpay modal is dismissed.
curl -X POST https://your-shop.example.com/api/payment/abandon \
-H "Content-Type: application/json" \
-d '{"razorpayOrderId":"order_QxYz123"}'
{
"razorpayOrderId": "order_PjK2mNqR8sT1vX"
}
{
"ok": true
}
/api/returnstokenThe signed-in shopper's own return requests and their status.
curl https://your-shop.example.com/api/returns \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
[
{
"id": "b3e5f721-6c48-4d90-8a12-5f7c0d92ea36",
"orderId": "7c4d1e88-9a02-4f31-b6c5-2d81e0aa93f4",
"orderRef": "ORD-2026-00142",
"invoiceId": "INV-2026-00142",
"reason": "Applied.",
"status": "requested",
"creditNoteNumber": "CN-2026-00031",
"refundAmount": 468.0,
"adminNote": "Seal broken on arrival — approved for a full refund.",
"orderTotal": 985.6,
"createdAt": "2026-08-21T09:14:03Z",
"resolvedAt": "2026-08-23T10:05:00Z"
}
]
/api/returnstokenRequest a return against one of the shopper's own orders. The merchant's books issue a credit note when it is accepted.
curl -X POST https://your-shop.example.com/api/returns \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"orderId":"3fa85f64-5717-4562-b3fc-2c963f66afa6","reason":"string"}'
Requires Authorization: Bearer <shopper token>.
{
"orderId": "7c4d1e88-9a02-4f31-b6c5-2d81e0aa93f4",
"reason": "Seal was broken on arrival."
}
{
"id": "b3e5f721-6c48-4d90-8a12-5f7c0d92ea36",
"orderId": "7c4d1e88-9a02-4f31-b6c5-2d81e0aa93f4",
"orderRef": "ORD-2026-00142",
"invoiceId": "INV-2026-00142",
"reason": "Seal was broken on arrival.",
"status": "requested",
"creditNoteNumber": "CN-2026-00031",
"refundAmount": 468.0,
"adminNote": "Seal broken on arrival — approved for a full refund.",
"orderTotal": 985.6,
"createdAt": "2026-08-21T09:14:03Z",
"resolvedAt": "2026-08-23T10:05:00Z"
}
/api/subscriptionstokenThe signed-in shopper's recurring orders.
curl https://your-shop.example.com/api/subscriptions \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
[
{
"id": "2a9d6f14-7b53-4c81-9e07-3d5a8b1f6c42",
"title": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"frequency": "monthly",
"status": "active",
"nextRun": "2026-09-21T00:00:00Z",
"createdAt": "2026-08-21T09:14:03Z"
}
]
/api/subscriptionstokenStart a recurring order for the signed-in shopper.
curl -X POST https://your-shop.example.com/api/subscriptions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"lines":[{"itemId":"ITM-001","qty":2}],"customer":{"name":"string","email":"shopper@example.com","phone":"9999999999","address":"string","city":"New Delhi","state":"DL","pincode":"110001"},"frequency":"string","title":"string"}'
Requires Authorization: Bearer <shopper token>.
{
"lines": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"qty": 2
}
],
"customer": {
"name": "Priya Sharma",
"email": "shopper@example.com",
"phone": "9999999999",
"address": "12 Nehru Place, New Delhi 110001",
"city": "New Delhi",
"state": "DL",
"pincode": "110001"
},
"frequency": "monthly",
"title": "Exactly as described"
}
{
"id": "2a9d6f14-7b53-4c81-9e07-3d5a8b1f6c42",
"title": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"frequency": "monthly",
"status": "active",
"nextRun": "2026-09-21T00:00:00Z",
"createdAt": "2026-08-21T09:14:03Z",
"access": [
{
"itemId": "16a8357b-4fa1-40ff-a70c-43094daee1d4",
"name": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"url": "https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png"
}
],
"accessPendingPayment": false
}
/api/subscriptions/{id}/{op}tokenChange a subscription's state. The operation is one of pause, resume or cancel.
curl -X POST https://your-shop.example.com/api/subscriptions/ITM-001/pause \ -H "Authorization: Bearer $TOKEN"
Requires Authorization: Bearer <shopper token>.
id path string requiredop path string required{
"id": "2a9d6f14-7b53-4c81-9e07-3d5a8b1f6c42",
"title": "24 Mantra Cold Pressed Organic Groundnut Oil 1 ltr",
"frequency": "monthly",
"status": "paused",
"nextRun": "2026-09-21T00:00:00Z",
"createdAt": "2026-08-21T09:14:03Z"
}
/api/blogPublished posts, newest first, without bodies — for an index page.
curl https://your-shop.example.com/api/blog
[
{
"slug": "cold-pressed-oils",
"title": "Why cold pressed matters",
"excerpt": "How cold pressing keeps more of what the peanut started with.",
"coverUrl": "https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png",
"createdAt": "2026-08-21T09:14:03Z"
}
]
/api/blog/{slug}One published post in full, by slug.
curl https://your-shop.example.com/api/blog/coffee-essentials
slug path string required{
"id": "f27b9d54-3e61-48ca-90b2-6d1af80c35e9",
"slug": "cold-pressed-oils",
"title": "Why cold pressed matters",
"excerpt": "How cold pressing keeps more of what the peanut started with.",
"coverUrl": "https://cdn.yourshop.com/product/6476/groundnut-oil-1l.png",
"body": "Cold pressing keeps the oil below 50°C, which preserves the aroma compounds heat would strip out.",
"published": true,
"createdAt": "2026-08-21T09:14:03Z",
"updatedAt": "2026-08-22T11:02:47Z"
}
Generated from a live store’s OpenAPI document. If your store is a newer build, trust /api/openapi.json on it over this page.
| The OpenAPI document | |
| Spec | https://your-shop.example.com/api/openapi.json |
| Browsable | https://your-shop.example.com/api/docs |
| What to do with it | |
| Explore | Import the spec into Postman, Insomnia or Bruno |
| Generate a client | Any OpenAPI generator — TypeScript, Dart, Swift, Kotlin, Python |
| Try it before installing | Leave the API key blank on first run and the store boots a demo with sample products, so the API answers with real data immediately |
The store is free and self-hosted. It still needs a Ragenaizer tenant to post into, because the invoices, the stock and the returns belong to the Accounts module — that is the trade: you build the front end, the business OS keeps the books.
Thirty-two designs if you want one, and a documented API if you do not.
This does not make Merch open source — the application ships as prebuilt container images and its source is not published. What is yours is the shopfront: the API is a contract you can build anything against, without inheriting the part that is genuinely hard to get right, which is GST-correct invoicing and stock that agrees with the books.