Merch · storefront API

Build your own
ecommerce.

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-three 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.

Start from working code

A whole shop front you can clone, in plain JavaScript.

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.

What it already does
  1. Catalogue with search, category, sort and in-stock filtering
  2. Product pages — gallery, variant options, stock, reviews
  3. Cart that re-prices against the store before anyone pays
  4. Coupons, gift cards and delivery quotes
  5. Checkout, order confirmation with timeline, and order tracking

Exercised against a real store: an order was placed from the browser and the total shown matched the amount charged, to the paisa.

Running it takes about two minutes
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 →

Before the endpoint list

Six things worth knowing first.

The base URL is your own storeEvery path below is served from the shop’s own domain — 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.
Authentication is a bearer tokenSend 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.
You can call it straight from the browserThe store answers every origin, method and header, on purpose — so a front end on a different domain to the store needs no proxy of its own. Because the credential is an Authorization header and not a cookie, nothing depends on the two sharing a site.
Every failure is the same JSON shapeA failed call answers {"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.
Sign-in, checkout and payment are rate limitedPer client IP, in fixed one-minute windows: 15 for sign-in, 25 for checkout and payment, 90 for cart previews like coupons and gift cards, 120 for product-by-id. Over the line answers 429 in the usual error shape. The catalogue, collections and blog are not limited, so a browse-heavy front end is not penalised.
What is not here, and will not beAdministering the shop stays with the bundled admin panel, and the API the store uses to reach your books is internal and never published — your front end never handles a Ragenaizer key, because the store holds it server-side. This is the shopfront contract, nothing wider.
The one flow you cannot guess

There are two ways to place an order. Pick one.

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.

A Paying later — cash on delivery, or an invoice to settle
  1. POST /api/checkout with the cart, the customer, and any coupon or gift card.
  2. That is the whole flow. The order exists, and the response carries its id, order reference and invoice PDF.

Do not touch the payment endpoints on this path.

B Paying now — card, UPI, net banking
  1. POST /api/payment/create-order with that same body. Nothing is ordered yet; you get back what the payment widget needs.
  2. Open the gateway with the keyId, amount and razorpayOrderId it returned. Note the amount is in paise, not rupees.
  3. On success the gateway hands you three values. 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.

The surface

Forty-three endpoints, all documented.

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.

Storefront 17 endpoints

GET/api/catalog

The product list, returned as a bare array — there is no envelope and no pagination, so one call gives you the whole visible catalogue and any paging is yours to do client-side. 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.

Try it
curl https://your-shop.example.com/api/catalog
Parameters
  • search query string
  • category query string
  • minPrice query number
  • maxPrice query number
  • inStock query boolean
  • sort query string
  • locale query string
Response
[
  {
    "id": "string",
    "name": "string",
    "description": "string",
    "imageUrls": [
      "string"
    ],
    "category": "string",
    "unit": "string",
    "price": 0.0,
    "mrp": 0.0,
    "taxNote": "string",
    "availability": "string",
    "qtyHint": 0,
    "featured": false,
    "variantGroupId": "string",
    "variantCount": 0,
    "isDigital": false
  }
]
GET/api/catalog/{id}

One product in full, including its description, images and live price and availability.

Try it
curl https://your-shop.example.com/api/catalog/ITM-001
Parameters
  • id path string required
  • locale query string
Response
{
  "id": "string",
  "name": "string",
  "description": "string",
  "imageUrls": [
    "string"
  ],
  "category": "string",
  "unit": "string",
  "price": 0.0,
  "mrp": 0.0,
  "taxNote": "string",
  "availability": "string",
  "qtyHint": 0,
  "featured": false,
  "variantGroupId": "string",
  "variantCount": 0,
  "isDigital": false
}
GET/api/categories

The category facets present in the catalogue, for building navigation or a filter rail.

Try it
curl https://your-shop.example.com/api/categories
Response
[
  "string"
]
GET/api/collections

Merchandising collections — manual picks and rule-based ones — as configured by the merchant.

Try it
curl https://your-shop.example.com/api/collections
Response
[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "handle": "string",
    "title": "string",
    "description": "string",
    "imageUrl": "string"
  }
]
GET/api/collections/{handle}

One collection and the products currently in it, resolved at request time so a smart collection stays current.

Try it
curl https://your-shop.example.com/api/collections/coffee-essentials
Parameters
  • handle path string required
  • locale query string
Response
{
  "collection": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "handle": "string",
    "title": "string",
    "description": "string",
    "imageUrl": "string"
  },
  "products": [
    {
      "id": "string",
      "name": "string",
      "description": "string",
      "imageUrls": [
        "string"
      ],
      "category": "string",
      "unit": "string",
      "price": 0.0,
      "mrp": 0.0,
      "taxNote": "string",
      "availability": "string",
      "qtyHint": 0,
      "featured": false,
      "variantGroupId": "string",
      "variantCount": 0,
      "isDigital": false
    }
  ]
}
POST/api/coupon/validate

Preview 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.

Try it
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}]}'
Request body
{
  "code": "SAVE10",
  "lines": [
    {
      "itemId": "ITM-001",
      "qty": 2
    }
  ]
}
Response
{
  "valid": false,
  "reason": "string",
  "code": "string",
  "discountAmount": 0.0,
  "newSubtotal": 0.0
}
GET/api/currencies

The display currencies the merchant has enabled, with their rates, for a currency switcher.

Try it
curl https://your-shop.example.com/api/currencies
Response
{
  "base": {
    "code": "string",
    "symbol": "string"
  },
  "currencies": [
    {
      "code": "string",
      "symbol": "string",
      "rate": 0.0
    }
  ]
}
POST/api/discounts/auto

Preview the best automatic (no-code) discount for the current basket, so the saving can be shown before checkout.

Try it
curl -X POST https://your-shop.example.com/api/discounts/auto \
  -H "Content-Type: application/json" \
  -d '{"lines":[{"itemId":"ITM-001","qty":2}]}'
Request body
{
  "lines": [
    {
      "itemId": "ITM-001",
      "qty": 2
    }
  ]
}
Response
{
  "applies": false,
  "title": "string",
  "discountAmount": 0.0,
  "newSubtotal": 0.0,
  "freeShipping": false
}
POST/api/discounts/bxgy

Preview any buy-X-get-Y saving that applies to the current basket.

Try it
curl -X POST https://your-shop.example.com/api/discounts/bxgy \
  -H "Content-Type: application/json" \
  -d '{"lines":[{"itemId":"ITM-001","qty":2}]}'
Request body
{
  "lines": [
    {
      "itemId": "ITM-001",
      "qty": 2
    }
  ]
}
Response
{
  "applies": false,
  "title": "string",
  "discountAmount": 0.0
}
POST/api/events

Report 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.

Try it
curl -X POST https://your-shop.example.com/api/events \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"string","events":[{"type":"view","itemId":"ITM-001"}]}'
Request body
{
  "sessionId": "string",
  "events": [
    {
      "type": "view",
      "itemId": "ITM-001"
    }
  ]
}
Response
{
  "ok": false,
  "recorded": 0
}
POST/api/giftcard/check

Check a gift card's balance before offering it at checkout. This does not redeem it.

Try it
curl -X POST https://your-shop.example.com/api/giftcard/check \
  -H "Content-Type: application/json" \
  -d '{"code":"GIFT500"}'
Request body
{
  "code": "GIFT500"
}
Response
{
  "valid": false,
  "reason": "string",
  "code": "string",
  "balance": 0.0
}
GET/api/media/ph/{seed}.svg

A deterministic placeholder image, generated locally. Useful while building against a demo store with no product photography.

Try it
curl https://your-shop.example.com/api/media/ph/ITM-001-a.svg
Parameters
  • seed path string required

Answers with an image (image/svg+xml), not JSON.

GET/api/products/{id}/variants

The sibling options of a product (size, colour and so on) for a variant selector on the product page.

Try it
curl https://your-shop.example.com/api/products/ITM-001/variants
Parameters
  • id path string required
  • locale query string
Response
{
  "groupId": "string",
  "title": "string",
  "options": [
    {
      "id": "string",
      "name": "string",
      "label": "string",
      "price": 0.0,
      "availability": "string",
      "current": false
    }
  ]
}
GET/api/products/{itemId}/reviews

Published reviews for a product, with the rating summary used for stars on a listing.

Try it
curl https://your-shop.example.com/api/products/ITM-001/reviews
Parameters
  • itemId path string required
Response
{
  "summary": {
    "average": 0.0,
    "count": 0
  },
  "reviews": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "author": "string",
      "rating": 0,
      "title": "string",
      "body": "string",
      "verified": false,
      "createdAt": "2026-08-09T00:00:00Z"
    }
  ]
}
POST/api/products/{itemId}/reviewstoken

Write 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.

Try 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>.

Parameters
  • itemId path string required
Request body
{
  "rating": 5,
  "title": "string",
  "body": "string"
}
Response
{
  "ok": false,
  "verified": false
}
POST/api/shipping/quote

The 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.

Try it
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"}'
Request body
{
  "pincode": "110001",
  "lines": [
    {
      "itemId": "ITM-001",
      "qty": 2
    }
  ],
  "paymentMethod": "cod"
}
Response
{
  "available": false,
  "shipping": 0.0,
  "courier": "string",
  "etaDays": 0,
  "options": [
    {
      "id": "string",
      "name": "string",
      "amount": 0.0,
      "etaMin": 0,
      "etaMax": 0,
      "live": false
    }
  ]
}
GET/api/theme

The 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.

Try it
curl https://your-shop.example.com/api/theme
Response
{
  "brandName": "string",
  "tagline": "string",
  "logoUrl": "string",
  "faviconUrl": "string",
  "layout": "string",
  "colors": {
    "primary": "string",
    "primaryHover": "string",
    "accent": "string",
    "bg": "string",
    "surface": "string",
    "text": "string",
    "muted": "string",
    "border": "string"
  },
  "radius": "string",
  "fontHeading": "string",
  "fontBody": "string",
  "customCss": "string",
  "hero": {
    "headline": "string",
    "subtext": "string",
    "ctaText": "string",
    "imageUrl": "string"
  },
  "banners": [
    {
      "imageUrl": "string",
      "headline": "string",
      "subtext": "string",
      "link": "string"
    }
  ],
  "featuredItemIds": [
    "string"
  ],
  "hiddenItemIds": [
    "string"
  ],
  "navLinks": [
    {
      "label": "string",
      "href": "string"
    }
  ],
  "currency": "string"
}

Cart and checkout 4 endpoints

POST/api/cart/save

Persist the in-progress basket against an email address. This is what makes abandoned-cart recovery possible; guests are included deliberately.

Try it
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}]}'
Request body
{
  "email": "shopper@example.com",
  "lines": [
    {
      "itemId": "ITM-001",
      "name": "string",
      "qty": 2,
      "price": 0.0
    }
  ]
}
Response
{
  "ok": false
}
POST/api/checkout

Place 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.

Try it
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"}'
Request body
{
  "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"
}
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "invoiceId": "string",
  "orderRef": "string",
  "status": "string",
  "total": 0.0,
  "invoicePdfUrl": "string",
  "giftCardApplied": 0.0,
  "amountDue": 0.0
}
POST/api/orders/lookup

Guest 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.

Try it
curl -X POST https://your-shop.example.com/api/orders/lookup \
  -H "Content-Type: application/json" \
  -d '{"email":"shopper@example.com","reference":"SO-1A2B3C4D"}'
Request body
{
  "email": "shopper@example.com",
  "reference": "SO-1A2B3C4D"
}
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "invoiceId": "string",
  "orderRef": "string",
  "status": "string",
  "total": 0.0,
  "invoicePdfUrl": "string",
  "createdAt": "2026-08-09T00:00:00Z",
  "awb": "string",
  "trackingUrl": "string",
  "fulfillmentStatus": "string",
  "carrier": "string",
  "trackingNumber": "string",
  "fulfilledAt": "2026-08-09T00:00:00Z",
  "cancelledAt": "2026-08-09T00:00:00Z"
}
GET/api/orders/{id}

An order's current status and timeline, for the confirmation page.

Try it
curl https://your-shop.example.com/api/orders/ITM-001
Parameters
  • id path string required
Response
{
  "order": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "invoiceId": "string",
    "orderRef": "string",
    "status": "string",
    "total": 0.0,
    "invoicePdfUrl": "string",
    "createdAt": "2026-08-09T00:00:00Z",
    "awb": "string",
    "trackingUrl": "string",
    "fulfillmentStatus": "string",
    "carrier": "string",
    "trackingNumber": "string",
    "fulfilledAt": "2026-08-09T00:00:00Z",
    "cancelledAt": "2026-08-09T00:00:00Z"
  },
  "events": [
    {
      "type": "string",
      "at": "2026-08-09T00:00:00Z"
    }
  ],
  "access": [
    {
      "itemId": "string",
      "name": "string",
      "url": "string"
    }
  ]
}

Shopper accounts 12 endpoints

GET/api/auth/config

Which 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.

Try it
curl https://your-shop.example.com/api/auth/config
Response
{
  "google": {
    "enabled": false,
    "clientId": "string"
  }
}
GET/api/shopper/addressestoken

The shopper's saved delivery addresses. Requires a shopper token.

Try it
curl https://your-shop.example.com/api/shopper/addresses \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Response
[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "string",
    "phone": "string",
    "line": "string",
    "city": "string",
    "state": "string",
    "pincode": "string",
    "isDefault": false
  }
]
POST/api/shopper/addressestoken

Save a new delivery address for the signed-in shopper.

Try it
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>.

Request body
{
  "name": "string",
  "phone": "9999999999",
  "line": "12 Nehru Place",
  "city": "New Delhi",
  "state": "DL",
  "pincode": "110001",
  "makeDefault": false
}
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "phone": "string",
  "line": "string",
  "city": "string",
  "state": "string",
  "pincode": "string",
  "isDefault": false
}
DELETE/api/shopper/addresses/{id}token

Remove one of the shopper's saved addresses.

Try it
curl -X DELETE https://your-shop.example.com/api/shopper/addresses/ITM-001 \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Parameters
  • id path string required
Response
{
  "ok": false
}
PUT/api/shopper/addresses/{id}token

Update one of the shopper's saved addresses.

Try it
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>.

Parameters
  • id path string required
Request body
{
  "name": "string",
  "phone": "9999999999",
  "line": "12 Nehru Place",
  "city": "New Delhi",
  "state": "DL",
  "pincode": "110001",
  "makeDefault": false
}
Response
{
  "ok": false
}
POST/api/shopper/addresses/{id}/defaulttoken

Mark a saved address as the shopper's default for checkout.

Try it
curl -X POST https://your-shop.example.com/api/shopper/addresses/ITM-001/default \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Parameters
  • id path string required
Response
{
  "ok": false
}
POST/api/shopper/googletoken

Sign 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.

Try it
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>.

Request body
{
  "credential": "string"
}
Response
{
  "token": "string",
  "shopper": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "string",
    "name": "string",
    "phone": "string"
  }
}
GET/api/shopper/metoken

The signed-in shopper's profile. Requires a shopper token.

Try it
curl https://your-shop.example.com/api/shopper/me \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "email": "string",
  "name": "string",
  "phone": "string",
  "linked": false
}
GET/api/shopper/orderstoken

The signed-in shopper's order history. Requires a shopper token.

Try it
curl https://your-shop.example.com/api/shopper/orders \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Response
[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "invoiceId": "string",
    "orderRef": "string",
    "status": "string",
    "total": 0.0,
    "invoicePdfUrl": "string",
    "createdAt": "2026-08-09T00:00:00Z",
    "awb": "string",
    "trackingUrl": "string",
    "fulfillmentStatus": "string",
    "carrier": "string",
    "trackingNumber": "string",
    "fulfilledAt": "2026-08-09T00:00:00Z",
    "cancelledAt": "2026-08-09T00:00:00Z"
  }
]
GET/api/shopper/wishlisttoken

The signed-in shopper's wishlist. Requires a shopper token.

Try it
curl https://your-shop.example.com/api/shopper/wishlist \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Response
[
  "string"
]
POST/api/shopper/wishlisttoken

Add a product to the shopper's wishlist.

Try it
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>.

Request body
{
  "itemId": "ITM-001"
}
Response
{
  "ok": false
}
DELETE/api/shopper/wishlist/{itemId}token

Remove a product from the shopper's wishlist.

Try it
curl -X DELETE https://your-shop.example.com/api/shopper/wishlist/ITM-001 \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Parameters
  • itemId path string required
Response
{
  "ok": false
}

Payments 3 endpoints

GET/api/payment/config

Whether online payment is switched on, and the public key needed to open the payment widget. Never returns the secret.

Try it
curl https://your-shop.example.com/api/payment/config
Response
{
  "enabled": false,
  "keyId": "string",
  "currency": "string",
  "symbol": "string",
  "name": "string"
}
POST/api/payment/create-order

Price 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.

Try it
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"}'
Request body
{
  "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"
}
Response
{
  "razorpayOrderId": "string",
  "amount": 0,
  "currency": "string",
  "keyId": "string",
  "name": "string",
  "prefill": {
    "name": "string",
    "email": "string",
    "contact": "string"
  }
}
POST/api/payment/verify

Verify the signed payment result and create the order. Idempotent per payment, so a retried or duplicated callback cannot produce two orders.

Try it
curl -X POST https://your-shop.example.com/api/payment/verify \
  -H "Content-Type: application/json" \
  -d '{"razorpayOrderId":"string","razorpayPaymentId":"string","razorpaySignature":"string"}'
Request body
{
  "razorpayOrderId": "string",
  "razorpayPaymentId": "string",
  "razorpaySignature": "string"
}
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "invoiceId": "string",
  "orderRef": "string",
  "status": "string",
  "total": 0.0,
  "invoicePdfUrl": "string",
  "giftCardApplied": 0.0,
  "amountDue": 0.0
}

Returns 2 endpoints

GET/api/returnstoken

The signed-in shopper's own return requests and their status.

Try it
curl https://your-shop.example.com/api/returns \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Response
[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "orderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "orderRef": "string",
    "invoiceId": "string",
    "reason": "string",
    "status": "string",
    "creditNoteNumber": "string",
    "refundAmount": 0.0,
    "adminNote": "string",
    "orderTotal": 0.0,
    "createdAt": "2026-08-09T00:00:00Z",
    "resolvedAt": "2026-08-09T00:00:00Z"
  }
]
POST/api/returnstoken

Request a return against one of the shopper's own orders. The merchant's books issue a credit note when it is accepted.

Try it
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>.

Request body
{
  "orderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "reason": "string"
}
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "orderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "orderRef": "string",
  "invoiceId": "string",
  "reason": "string",
  "status": "string",
  "creditNoteNumber": "string",
  "refundAmount": 0.0,
  "adminNote": "string",
  "orderTotal": 0.0,
  "createdAt": "2026-08-09T00:00:00Z",
  "resolvedAt": "2026-08-09T00:00:00Z"
}

Subscriptions 3 endpoints

GET/api/subscriptionstoken

The signed-in shopper's recurring orders.

Try it
curl https://your-shop.example.com/api/subscriptions \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Response
[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "title": "string",
    "frequency": "string",
    "status": "string",
    "nextRun": "2026-08-09T00:00:00Z",
    "createdAt": "2026-08-09T00:00:00Z"
  }
]
POST/api/subscriptionstoken

Start a recurring order for the signed-in shopper.

Try it
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>.

Request body
{
  "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"
}
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "title": "string",
  "frequency": "string",
  "status": "string",
  "nextRun": "2026-08-09T00:00:00Z",
  "createdAt": "2026-08-09T00:00:00Z",
  "access": [
    {
      "itemId": "string",
      "name": "string",
      "url": "string"
    }
  ],
  "accessPendingPayment": false
}
POST/api/subscriptions/{id}/{op}token

Change a subscription's state. The operation is one of pause, resume or cancel.

Try it
curl -X POST https://your-shop.example.com/api/subscriptions/ITM-001/pause \
  -H "Authorization: Bearer $TOKEN"

Requires Authorization: Bearer <shopper token>.

Parameters
  • id path string required
  • op path string required
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "title": "string",
  "frequency": "string",
  "status": "string",
  "nextRun": "2026-08-09T00:00:00Z",
  "createdAt": "2026-08-09T00:00:00Z"
}

Blog 2 endpoints

GET/api/blog

Published posts, newest first, without bodies — for an index page.

Try it
curl https://your-shop.example.com/api/blog
Response
[
  {
    "slug": "string",
    "title": "string",
    "excerpt": "string",
    "coverUrl": "string",
    "createdAt": "2026-08-09T00:00:00Z"
  }
]
GET/api/blog/{slug}

One published post in full, by slug.

Try it
curl https://your-shop.example.com/api/blog/coffee-essentials
Parameters
  • slug path string required
Response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "slug": "string",
  "title": "string",
  "excerpt": "string",
  "coverUrl": "string",
  "body": "string",
  "published": false,
  "createdAt": "2026-08-09T00:00:00Z",
  "updatedAt": "2026-08-09T00:00:00Z"
}

Generated from a live store’s OpenAPI document. If your store is a newer build, trust /api/openapi.json on it over this page.

Getting the document

Point your tooling at your store.

The OpenAPI document
Spechttps://your-shop.example.com/api/openapi.json
Browsablehttps://your-shop.example.com/api/docs
What to do with it
ExploreImport the spec into Postman, Insomnia or Bruno
Generate a clientAny OpenAPI generator — TypeScript, Dart, Swift, Kotlin, Python
Try it before installingLeave 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.

Your front end. Our ledger.

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.