Skip to content

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.

MethodPathScope
GET/v1/storesstores.read
GET/v1/menusmenus.read
GET/v1/menus/:id/fullmenus.read
GET/v1/itemsitems.read
GET/v1/categoriescategories.read
GET/v1/modifiersmodifiers.read
Terminal window
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.

Terminal window
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:

FieldMeaning
MenuDisplay name
Online, Delivery, TakeawayWhich channels the menu serves
StatusInactive menus are not orderable
StoreIdsEmpty 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:

Terminal window
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.

The menu tells you which items; /items tells you everything about them.

Terminal window
curl -s "$API_BASE_URL/items?status=1&limit=100&page=1" \
-H "Authorization: Bearer $ACCESS_TOKEN"
FieldUse it for
idThe value you send as items[].id when creating an order
itemDisplay name
item1Secondary-language name, when the merchant maintains one
ratePrice to charge
DescriptionLong copy for your product page
CanOnlineSaleHide the item when this is 0, whatever the menu says
IsVegDietary badge
IsAlcaholAge-restricted line — apply your own checks
ItemCodeYour SKU, if the merchant maintains it. The best key to match on
groupIdCategory for navigation
status0 means withdrawn — drop it from your channel

Page with limit=100; the default of 10 will make a full catalogue sync needlessly expensive.

Terminal window
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 fieldMeaning
rateDelta for a dine-in or default sale
Rate1Delta for takeaway
Rate2Delta for delivery
PreDefaultPre-selected option
Max, QtyPer-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.

There are no webhooks in this version, so sync on a schedule.

WhatCadenceWhy
StoresDailyRarely changes
Menus and their timingsEvery 15–30 minutesMerchants switch menus during the day
Items and pricesEvery 15–30 minutesPrice changes must not go stale
ModifiersHourlyChanges 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;
}
}