Catalogue
The catalogue is what the point of sale shows and what every sale line refers back to. An item belongs to a category, a brand and a tax; a menu decides which items a channel sees; modifiers add the questions asked at the till.
Endpoints
Section titled “Endpoints”| Method | Path | Scope |
|---|---|---|
GET | /v1/items | items.read |
GET | /v1/items/:id | items.read |
POST | /v1/items | items.write |
PATCH | /v1/items/:id | items.write |
GET | /v1/categories | categories.read |
POST | /v1/categories | categories.write |
PATCH | /v1/categories/:id | categories.write |
GET | /v1/brands | brands.read |
POST | /v1/brands | brands.write |
PATCH | /v1/brands/:id | brands.write |
GET | /v1/modifiers | modifiers.read |
POST | /v1/modifiers | modifiers.write |
PATCH | /v1/modifiers/:id | modifiers.write |
GET | /v1/menus | menus.read |
GET | /v1/menus/:id/full | menus.read |
POST | /v1/menus/create-with-items | menus.write |
PATCH | /v1/menus/:id/update-with-items | menus.write |
GET | /v1/combo-slots | meal_slots.read |
POST | /v1/combo-slots | meal_slots.write |
PUT | /v1/items/:id/meal | meal_slots.write |
Order of operations
Section titled “Order of operations”An item cannot exist before the rows it points at. For a new catalogue, create in this order:
- Taxes —
POST /v1/taxes. See taxes. - Categories —
POST /v1/categories. - Brands —
POST /v1/brands. - Items —
POST /v1/items, referencing the ids above. - Modifiers —
POST /v1/modifiers, attaching them to items. - Menus —
POST /v1/menus/create-with-items, selecting items per channel.
Syncing an existing catalogue? Read the merchant’s current categories, brands and taxes first and map onto them. Creating duplicates of rows the merchant already curates is the most common review failure.
Categories
Section titled “Categories”curl -s "$API_BASE_URL/categories" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "groupName": "Hot drinks", "priority": 10, "kitchenId": 2 }'| Field | Type | Required | Notes |
|---|---|---|---|
groupName | string | yes | Up to 50 characters, unique in practice |
priority | number | no | Display order; lower sorts first. Default 0 |
kitchenId | number | no | Routes the category’s tickets to a kitchen printer or KDS |
status | number | no | 1 active (default), 0 hidden |
favourite | number | no | 1 pins the category on the till’s quick grid |
IsEBT | number | no | US only — marks the category as EBT-eligible |
Brands
Section titled “Brands”curl -s "$API_BASE_URL/brands" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "brand": "House", "priority": 1 }'brand (≤ 50 characters) is the only required field; priority and status behave as they
do on categories. Every item needs a brand — merchants that do not think in brands keep a
single House row.
curl -s "$API_BASE_URL/items" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "item": "Flat White", "groupId": 12, "brandId": 1, "uomId": 1, "taxId": 3, "rate": 3.75, "mrp": 4.00, "barcode": "8901234567890", "ItemCode": "COF-FW-REG", "Description": "Double ristretto, steamed milk", "stockable": 0, "CanOnlineSale": 1, "IsVeg": true, "kitchenId": 2 }'const response = await fetch(new URL('items', apiBaseUrl), { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ item: 'Flat White', groupId: 12, brandId: 1, uomId: 1, taxId: 3, rate: 3.75, barcode: '8901234567890', ItemCode: 'COF-FW-REG', CanOnlineSale: 1, }),});
if (!response.ok) { const body = await response.json(); throw new Error(`${body.code ?? response.status}: ${body.message}`);}
const { data: item } = await response.json();Fields
Section titled “Fields”| Field | Type | Required | Notes |
|---|---|---|---|
item | string | yes | Display name, up to 100 characters |
groupId | number | yes | Category id |
brandId | number | yes | Brand id |
uomId | number | yes | Unit of measure id |
taxId | number | yes | Sales tax row applied at the till |
item1 | string | no | Secondary name — used for a second language on receipts |
rate | number | no | Selling price |
mrp | number | no | Maximum retail price, where regulation requires it printed |
barcode | string | no | Single barcode. One value, not a list |
ItemCode | string | no | Your own SKU. The field to correlate on when syncing |
hsn | string | no | Tax classification code (HSN/SAC), up to 35 characters |
Description | string | no | Long description for online channels |
status | number | no | 1 active (default), 0 inactive |
stockable | number | no | 1 tracks stock, 0 does not |
canSale | number | no | 0 hides the item from the till while keeping it for recipes |
CanOnlineSale | number | no | 1 exposes the item to online ordering |
kitchenId | number | no | Overrides the category’s kitchen routing |
hasBatch | number | no | 1 enables batch tracking |
ExpiryTrack | number | no | 1 requires expiry dates on receipt |
weighingScale | number | no | 1 for scale-priced goods |
serialNo | number | no | 1 requires a serial number per unit |
favourite | number | no | 1 pins the item on the till’s quick grid |
IsVeg | boolean | no | Vegetarian marker used on menus |
IsService | boolean | no | Service line rather than a physical good |
IsAlcahol | boolean | no | Alcohol marker (spelling is part of the wire contract) |
PurchTaxId | number | no | Purchase-side tax, when it differs from the sales tax |
cessId, Tax4Id | number | no | Additional levies in jurisdictions that use them |
{ "data": { "id": 8841, "item": "Flat White", "groupId": 12, "brandId": 1, "uomId": 1, "taxId": 3, "rate": 3.75, "status": 1 }}Finding items
Section titled “Finding items”curl -s "$API_BASE_URL/items?search=flat&groupId=12&status=1&limit=100" \ -H "Authorization: Bearer $ACCESS_TOKEN"| Parameter | Notes |
|---|---|
search | Matches name, code and barcode |
groupId | One or more category ids, comma-separated |
brandId, taxId | Single id |
status | 1 or 0 |
stockStatus | inStock, low or out |
page, limit, sort, order | Standard paging |
Updating
Section titled “Updating”PATCH accepts any subset of the create fields:
curl -s -X PATCH "$API_BASE_URL/items/8841" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "rate": 3.95 }'Deactivate rather than delete — {"status": 0} keeps the item’s sales history intact while
removing it from the till.
Modifiers
Section titled “Modifiers”A modifier group is a question — “Milk?”, “Add extras” — with options, attached to the items that should ask it.
curl -s "$API_BASE_URL/modifiers" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "modifier": "Milk", "Type": 0, "Min": 1, "Max": 1, "DisplayOrder": 1, "Options": [ { "item": "Whole", "rate": 0, "PreDefault": true }, { "item": "Oat", "rate": 0.40 }, { "item": "Soy", "rate": 0.40 } ], "ItemIds": [8841] }'| Field | Type | Required | Notes |
|---|---|---|---|
modifier | string | yes | Group label, up to 45 characters |
Type | number | yes | 0 single choice, 1 multiple choice |
Min / Max | number | no | Selection bounds. Min: 1 makes the question mandatory |
HasMax | number | no | 1 enforces Max |
IncludeQty | number | no | 1 lets the cashier pick a quantity per option |
DisplayOrder | number | no | Order among an item’s modifier groups |
Options[].item | string | yes | Option label |
Options[].rate | number | no | Price delta at the till |
Options[].Rate1 / Rate2 | number | no | Takeaway and delivery price deltas |
Options[].PreDefault | boolean | no | Pre-selected option |
Options[].Max / Qty | number | no | Per-option quantity cap and default quantity |
Options[].ItemId | number | no | Links the option to a stocked item so it depletes inventory |
ItemIds | number[] | no | Items this group is attached to |
A menu is a named selection of items with optional timings and channel flags. Use it to give delivery a different list from the dine-in till.
curl -s "$API_BASE_URL/menus/create-with-items" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "Menu": "All day", "Description": "Everything except breakfast-only lines", "Status": true, "Online": true, "Delivery": true, "Takeaway": true, "StoreIds": [1, 2], "Timings": [{ "days": [1,2,3,4,5], "fromTime": "07:00", "toTime": "22:00" }], "Items": [ { "itemId": 8841, "variantId": 0 }, { "itemId": 8842, "variantId": 0 } ] }'| Field | Type | Required | Notes |
|---|---|---|---|
Menu | string | yes | Name, up to 45 characters |
Description | string | yes | |
Status | boolean | no | Default true |
Online, Delivery, Takeaway | boolean | no | Channel flags, default false |
StoreIds | number[] | no | Empty or omitted means every store |
Timings[].days | number[] | no | 0 Sunday through 6 Saturday |
Timings[].fromTime / toTime | string | no | HH:mm, store-local |
Items[].itemId | number | yes | |
Items[].variantId | number | yes | 0 when the item has no variants |
GET /v1/menus/:id/full returns the menu with its items and timings expanded —
this is the read the Online Order API uses for
menu sync.
Combo meals
Section titled “Combo meals”Combo meals are built from slots: “pick a main”, “pick a side”, “pick a drink”.
# 1. Define a slot with its optionscurl -s "$API_BASE_URL/combo-slots" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "name": "Choose a side", "minSelect": 1, "maxSelect": 1, "allowRepeat": 0, "options": [ { "childItemId": 8850, "qty": 1, "isDefault": 1, "displayOrder": 1 }, { "childItemId": 8851, "qty": 1, "extraPrice": 0.50, "displayOrder": 2 } ] }'
# 2. Attach slots to the combo itemcurl -s -X PUT "$API_BASE_URL/items/8900/meal" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "slots": [{ "slotId": 4, "displayOrder": 1 }] }'| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Slot label shown at the till |
minSelect / maxSelect | number | no | Selection bounds for the slot |
allowRepeat | number | no | 1 allows the same option more than once |
options[].childItemId | number | yes | The item offered in this slot |
options[].childVariantId | number | no | 0 when the item has no variants |
options[].qty | number | no | Units of the option included, default 1 |
options[].extraPrice | number | no | Surcharge when this option is chosen |
options[].isDefault | number | no | 1 pre-selects the option |
options[].displayOrder | number | no | Order within the slot |
On PUT /v1/items/:id/meal, slots attaches slot definitions (slotId, plus
optional minSelect, maxSelect, displayOrder), fixed lists items always included in
the combo (itemId, qty), and upsells offers paid additions triggered by a chosen item
(triggerItemId, slotId, label).