Skip to main content
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 same externalId updates the same object. The response also mints Maple IDs (item_…, var_…, ctg_…) for reference, but you address objects by your externalId.
  • The price lives on the variation. Even a simple item has at least one variation (call it “Regular”). All prices are integer minor units450 is $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.
Tax categories and rates, fees (like a bottle deposit), discounts, promotions, and coupons are all optional top-level collections, each addressed by its own externalId and attached to items by ID (taxCategoryId, feeIds, and so on). The endpoint reference documents every field.

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.
  • stockStatus is 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 to in_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 same externalId updates 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 400 listing 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:
EventMeaning
menu.sync.completedA menu sync finished for a location
menu.sync.failedA menu sync failed for a location
See Webhooks to subscribe and verify these.

Next

Receive and decide orders

Order line items reference your menu by the externalId you assign.

Full menu schema

Every field on every menu object, in the endpoint reference.