Menus
Publish a menu
Send a location’s full menu as one JSON document keyed by your own stable IDs, then read it back. Publishing is a diff, so re-publishing is safe.
A location’s menu is the catalog customers order from. You publish it as one JSON document that describes the whole menu — categories, items, variations, modifiers, and the tax and fee definitions that apply. Maple keys everything off the external IDs you assign, so the menu_entity_id on every order line item maps straight back to your own catalog.
Publishing requires the menus:write scope and an active connection to the location. Reading requires menus:read. See How Maple works for connections.
The shape of a menu
A menu is a tree with shared definitions alongside it:
menu├── categories[] (required)│ └── items[]│ └── variations[] (required: at least one — the price lives here)│ └── modifierGroups[] (inline, or a reference to a shared group)├── modifierGroups[] (shared definitions, referenced by groupId)├── taxCategories[] / taxRates[]├── fees[]└── discounts[] / promotions[] / coupons[]Two rules carry most of the model:
- Every object has an
externalId— your stable handle for it. You choose these; Maple never changes them. Re-publishing with the sameexternalIdupdates the same object. The response also mints Maple IDs (item_…,var_…,ctg_…) for reference, but you address objects by yourexternalId. - The price lives on the variation. Even a simple item has at least one variation (call it “Regular”). All prices are integer minor units —
450is $4.50.
A minimal menu
The smallest valid document is a currency and one category containing one item with one variation:
curl -X POST $MAPLE_BASE/locations/{locationId}/menu \ -H "Authorization: Bearer $MAPLE_KEY" \ -H "Content-Type: application/json" \ -d '{ "currency": "USD", "categories": [ { "externalId": "cat-drinks", "name": "Drinks", "items": [ { "externalId": "itm-latte", "name": "Latte", "variations": [ { "externalId": "var-latte-reg", "name": "Regular", "price": { "amount": { "amountMinor": 450, "currency": "USD" } } } ] } ] } ] }'A successful publish returns the resulting menu with every collection materialized and Maple IDs filled in. The externalIds you sent are echoed back unchanged.
Add shared modifiers
Define a modifier group once at the top level and reference it by groupId wherever it applies — so “Milk options” stays in one place across every drink:
{ "currency": "USD", "modifierGroups": [ { "externalId": "grp-milk", "name": "Milk", "minSelections": 0, "maxSelections": 1, "options": [ { "externalId": "mod-whole", "name": "Whole milk" }, { "externalId": "mod-oat", "name": "Oat milk", "price": { "amount": { "amountMinor": 75, "currency": "USD" } } } ] } ], "categories": [ { "externalId": "cat-drinks", "name": "Drinks", "items": [ { "externalId": "itm-latte", "name": "Latte", "variations": [{ "externalId": "var-latte-reg", "name": "Regular", "price": { "amount": { "amountMinor": 450, "currency": "USD" } } }], "modifierGroups": [{ "groupId": "grp-milk" }] } ] } ]}A modifier group entry is either an inline definition (carries name and options) or a reference ({ "groupId": "grp-milk" }). Don’t mix the two shapes in one entry — a payload that does is rejected rather than silently truncated. Items, variations, and individual options can each carry modifier groups, which lets you nest options where the menu calls for it.
Publishing semantics
Every publish sends the entire menu as its desired state. Maple diffs it against the current menu (keyed by externalId) and applies the result as a new version. A few rules follow from that:
- The document is the whole menu. Whatever you send becomes the menu. An object you published before but leave out of a later publish is removed — to keep something, include it every time.
stockStatusis the exception. It’s a field-level value: leaving it off an object you do include preserves that object’s current stock (new objects default toin_stock). So 86-ing an item is still a complete publish that includes every object — you just set one field.- Identity is your
externalId. The sameexternalIdupdates the same object across publishes; a new one creates a new object. Use IDs that are stable in your system (a SKU or primary key), not display names. - Order is preserved. Categories, items, variations, and options appear in the order you send them.
- Validation is all-or-nothing. If anything is invalid you get a
400listing every issue, and the menu is left exactly as it was. Fix them all and re-send. - Identical re-publishes are no-ops. Sending the same document changes nothing and creates no churn, so you can safely publish on every menu change.
- Each change is versioned. A content change cuts a new version; the location always serves the current one.
Because the document is the full desired state, the simplest integration re-publishes the entire menu whenever your source changes. You never compute diffs — Maple does.
Set the timezone for availability
Availability windows (dayparting and happy hours) are evaluated in the menu’s timezone. Set it with the top-level timezone field — an IANA name like America/New_York:
{ "currency": "USD", "timezone": "America/New_York", "categories": [ /* … */ ] }If you omit it, the menu’s timezone defaults to UTC, which is almost never right for time-based rules. The timezone is established on your first publish, so set it correctly up front.
Read it back
curl $MAPLE_BASE/locations/{locationId}/menu \ -H "Authorization: Bearer $MAPLE_KEY"Returns the current published menu — categories, items, and variations with prices, shared modifier groups, and tax, fee, and discount definitions — every object echoing the externalId you published it under. A location with no published menu returns a 404.
Know when a publish lands
Subscribe to the menu sync events to be notified as a published menu propagates:
| Event | Meaning |
|---|---|
menu.sync.completed |
A menu sync finished for a location |
menu.sync.failed |
A menu sync failed for a location |
See Webhooks to subscribe and verify these.