Skip to main content

Overview

Food delivery item customization is divided into three layers:

Specs — Specifications

Each item may have one or multiple specification dimensions:
Selecting different specs changes the item_id and sku_id. Just pass the corresponding spec ID when ordering.

Attrs — Attributes

Customization options not affecting price:

Ingredients — Extra Ingredients

Ingredients are per-SKU. For one item, different SKUs (e.g. cup sizes) can offer different ingredients and different internal IDs — e.g. an item’s “Extra Large” may only allow “Iced” while “Venti” only allows “Hot”. The top-level ingredient_options from get_shop_menu is the default SKU’s; for any other SKU, use the option_ids under that SKU’s sku_options[].ingredient_options (see Menu API). If you reuse a default-SKU option_id on another SKU, the gateway maps it by (group name + option name); an ingredient absent on the chosen SKU is rejected at quote_cart / preview_order.
Most complex customization layer, including exclusion rules:

Exclusion Rules

Each option’s excludes array lists which option IDs are incompatible with it:
ID format is ig_{group_index}_{option_index}, stable and predictable.
Exclusion rules are one-way: A excluding B doesn’t mean B excludes A. Need to check each selected option’s exclusion list individually.

Typical Scenarios

Starbucks Chain Exclusion:
Mutual Exclusion:

Default Ingredients — Defaults

For AI Agent quick ordering convenience, menu returns pre-computed default_ingredients:
This is the result of system automatically selecting the first non-conflicting option from each required group. Agent can directly pass this field to order endpoint without understanding exclusion logic.
For most ordering scenarios, directly using default_ingredients is the simplest approach. Only need manual selection when user explicitly requests customization.

Two layers of “required groups”

“Required” appears at two distinct layers in a menu — don’t conflate them:
  • Per-item required ingredient groups: within one item you must pick enough options, e.g. a drink’s required “Temperature” / “Sugar”. These live in that item’s ingredient_options (upstream isRequired groups). Missing one is rejected at order time as “ingredients not fully selected”.
  • Store-level required item groups: the whole order must include another item of a certain kind, e.g. a malatang shop’s “Required Soup”. These live in the top-level required_groups[] of the get_shop_menu response (and categories[].required); pick from candidate_item_ids and add to items[].
The two are enforced at different times: store-level required groups are checked by the gateway at quote_cart (can_checkout=false + blocking_reason, a soft hint) and preview_order (MISSING_REQUIRED_SELECTION, HTTP 400 hard block); per-item required ingredient groups are validated by the upstream at order time.

Echoing selected specs

get_shop_menu returns the available options; quote_cart and preview_order echo back the user’s selected specs and ingredients on each item, so you can show “what was ordered”: The echo is translated by each item’s own sku_id / ingredient_option_ids, so multiple items (including the same item with different specs) never cross-contaminate; fields are [] when there are no specs / ingredients.