Menu sync
Your channel needs to know what a store sells, what it costs, what questions to ask, and when it is available. All four come from the menu endpoints.
Endpoints
Section titled “Endpoints”| Method | Path | Scope |
|---|---|---|
GET | /v1/stores | stores.read |
GET | /v1/menus | menus.read |
GET | /v1/menus/:id/full | menus.read |
GET | /v1/items | items.read |
GET | /v1/categories | categories.read |
GET | /v1/modifiers | modifiers.read |
Step 1 — stores
Section titled “Step 1 — stores”curl -s "$API_BASE_URL/stores" \ -H "Authorization: Bearer $ACCESS_TOKEN"{ "data": [ { "id": 1, "name": "Brigade Road", "address": "12 Brigade Rd, Bengaluru", "status": 1, "type": 0 }, { "id": 2, "name": "Central warehouse", "address": "Peenya", "status": 1, "type": 1 } ], "meta": { "page": 1, "limit": 10, "total": 2 }}Only offer stores with status: 1 and type: 0. Type 1 is a warehouse — it holds stock
but never serves customers.
Step 2 — the menu
Section titled “Step 2 — the menu”curl -s "$API_BASE_URL/menus?storeId=1" \ -H "Authorization: Bearer $ACCESS_TOKEN"A merchant can publish several menus — all day, breakfast, delivery-only. Each carries channel flags and timings:
| Field | Meaning |
|---|---|
Menu | Display name |
Online, Delivery, Takeaway | Which channels the menu serves |
Status | Inactive menus are not orderable |
StoreIds | Empty means every store |
Timings[] | { days, fromTime, toTime } in store-local time |
Pick the menus whose channel flag matches how your customer is ordering, then expand the one you need:
curl -s "$API_BASE_URL/menus/44/full" \ -H "Authorization: Bearer $ACCESS_TOKEN":id/full returns the menu with its item selection and timings expanded — one call instead
of one per item.
Step 3 — item detail
Section titled “Step 3 — item detail”The menu tells you which items; /items tells you everything about them.
curl -s "$API_BASE_URL/items?status=1&limit=100&page=1" \ -H "Authorization: Bearer $ACCESS_TOKEN"| Field | Use it for |
|---|---|
id | The value you send as items[].id when creating an order |
item | Display name |
item1 | Secondary-language name, when the merchant maintains one |
rate | Price to charge |
Description | Long copy for your product page |
CanOnlineSale | Hide the item when this is 0, whatever the menu says |
IsVeg | Dietary badge |
IsAlcahol | Age-restricted line — apply your own checks |
ItemCode | Your SKU, if the merchant maintains it. The best key to match on |
groupId | Category for navigation |
status | 0 means withdrawn — drop it from your channel |
Page with limit=100; the default of 10 will make a full catalogue sync needlessly
expensive.
Step 4 — modifiers
Section titled “Step 4 — modifiers”curl -s "$API_BASE_URL/modifiers?limit=100" \ -H "Authorization: Bearer $ACCESS_TOKEN"Each group carries Type (0 single choice, 1 multiple), Min/Max bounds, the item
ids it applies to, and its options. Options carry the price delta for each channel:
| Option field | Meaning |
|---|---|
rate | Delta for a dine-in or default sale |
Rate1 | Delta for takeaway |
Rate2 | Delta for delivery |
PreDefault | Pre-selected option |
Max, Qty | Per-option quantity cap and default |
Charge the delta that matches the order type you are about to send, and enforce Min/Max
in your own UI — an order that violates them is rejected at creation.
Keeping in step
Section titled “Keeping in step”There are no webhooks in this version, so sync on a schedule.
| What | Cadence | Why |
|---|---|---|
| Stores | Daily | Rarely changes |
| Menus and their timings | Every 15–30 minutes | Merchants switch menus during the day |
| Items and prices | Every 15–30 minutes | Price changes must not go stale |
| Modifiers | Hourly | Changes rarely, but the price deltas matter |
// A full sync for one store: four calls, paged.async function syncStore(storeId) { const menus = await getAll(`menus?storeId=${storeId}`); const live = menus.filter((menu) => menu.Status && menu.Online);
const [items, categories, modifiers] = await Promise.all([ getAll('items?status=1'), getAll('categories?status=1'), getAll('modifiers'), ]);
return buildCatalogue({ live, items, categories, modifiers });}
async function getAll(path) { const rows = []; for (let page = 1; ; page++) { const response = await partnerGet(`${path}&page=${page}&limit=100`); const { data, meta } = await response.json(); rows.push(...data); if (rows.length >= meta.total || data.length === 0) return rows; }}