get_shop_menu
Fetches a shop’s menu. Each returned item carries a gateway-minteditem_id (plus optional sku_id / option_id); these IDs are the only valid order references for the downstream quote_cart, preview_order, and create_order calls. Menus can be large (100+ items), so it supports keyword filtering and limit / offset paging (progressive disclosure).
Order flow:
search_shops → shop_id + cart_id (carries delivery coordinates) → get_shop_menu to pick items and get item_id / sku_id → quote_cart to price and get quote_id → select_address to get address_id → preview_order → preview_id + confirmation_token → create_order. The menu must be based on an existing cart_id, and shop_id must match that cart_id (both returned by the same search_shops).Parameters
address_id / lat / lng resolve the delivery coordinates and are usually unnecessary: the cart_id already carries the coordinates from search. When all three are omitted, the cart_id coordinates are used.Menu outline (
full_menu=true): returns every category with all its item cards (item_id / name / price / available / image_url plus marketing fields) in a single call, with sku_options / ingredient_options always empty arrays — a fast way to skim a large menu without fetching per-item specs. Outline item_ids are stable (same id as the enriched view within the same cart_id) but NOT directly orderable: outline ids are not written to the cart, so passing them to quote_cart / preview_order / create_order fails with PUBLIC_REFERENCE_INVALID. To order, first enrich the target items — call get_shop_menu again with keyword / limit to locate them, or batch-fetch specs via get_item_options — then order with the same item_id. full_menu=true means complete and never paged: limit / offset are ignored and has_more is always false; keyword still applies — it decides whether that complete result is the whole menu or every match, and search_match_level is returned as usual. full_menu=true + keyword is the fastest way to locate a dish in a large menu.The outline price is the item’s own price; for a multi-size dish that is the first tier, not a range (a latte listed at 21.9 can cost 24.9 in a larger cup). Read the real per-size prices from get_item_options.Returns
Ingredients are per-SKU. Ingredients are bound to a specific SKU — which ingredients are available, and their internal IDs, can differ per SKU (e.g. cup size). The top-level
items[].ingredient_options belongs to the default SKU; for any other SKU, use the option_ids under that SKU’s items[].sku_options[].ingredient_options. If you reuse a default-SKU option_id on a non-default SKU, the gateway maps it to that SKU’s matching ingredient by (group name + option name); an ingredient that doesn’t exist on the chosen SKU is rejected at quote_cart / preview_order (not at create_order).Store-level required item groups. Some shops (malatang, certain combo shops) require the whole order to include an item of a certain kind — e.g. a malatang shop’s “Required Soup”; without a soup the upstream rejects the order. This constraint is exposed via
required_groups: before ordering you must pick at least min_select item(s) from each required_groups[].candidate_item_ids and add them as normal entries to items[] (candidate details are in required_groups[].candidates — with name/price/available, always complete; with keyword or paging the required candidates may not appear in the current page’s items[]). When unmet, quote_cart returns can_checkout=false + blocking_reason, and preview_order returns MISSING_REQUIRED_SELECTION (HTTP 400). This is a different layer from a product’s required ingredient groups (the required option groups inside ingredient_options, e.g. a drink’s required temperature / sugar): the former is “must add another item”, the latter is “must pick enough options inside one item”.available in the menu does not account for exclusions. Some items layer their options — pick a coffee bean first, and only the extraction methods belonging to that bean remain selectable. That kind of availability depends on what is currently selected, and this endpoint takes no parameter expressing a selection, so the menu just lists the candidates and available reflects only sold-out / delisted.To learn whether an option is selectable under the current selection, use get_item_options (it accepts the current selection and narrows step by step). Option IDs from the menu remain directly usable for ordering.required_groups | array | Store-level required item groups (e.g. a malatang shop’s “Required Soup”); the order must include at least min_select item(s) from each group’s candidate_item_ids. [] when the shop has no such constraint |
| required_groups[].name | string | Required-group name (e.g. “Required Soup”) |
| required_groups[].required | boolean | Always true |
| required_groups[].min_select | integer | Minimum items to pick from this group (currently always 1) |
| required_groups[].candidate_item_ids | array | item_ids of candidate items; pick at least min_select of them and add as normal items to the order items[] |
| required_groups[].candidates | array | Candidate details [{item_id, name, price, available}], one-to-one with candidate_item_ids and always complete (decoupled from keyword/paging); agent renders candidates directly without re-checking items[] |
How
keyword matches, and how results are ordered.Matching runs in five tiers, and a stricter tier that returns anything stops the search — looser results never dilute exact ones. search_match_level tells you which tier produced the results:On
abbrev / fuzzy / partial, confirm the returned dish really is what the user asked for before ordering. partial is the loosest tier and may return something close rather than the same thing (searching 「牛肉面」 in a shop with no noodles returns 「牛肉汉堡」). none does not mean the shop lacks the dish — character matching cannot bridge synonyms (「鸡肉汉堡」 vs 「香辣鸡腿汉堡」); re-fetch without keyword and scan the list yourself (add full_menu=true to make that fast). Note: keeping the keyword and merely adding full_menu=true still returns the same empty result — full_menu does not disable filtering, it only skips paging and specs.Ordering of items[]: items whose name contains the keyword come first, followed by those matched only through their category name or description; items from add-on / condiment / cutlery / packaging categories come last (searching 「珍珠」 gives you 「珍珠奶茶」 first and 「珍珠(分装)」 last). Within one tier the menu’s own order is preserved. categories[] is never reordered — it always follows the menu, so you can render it as a category nav directly.grouped=true returns ingredients in a different shape. With grouped=true, ingredients come back as ingredient_groups instead of the flat ingredient_options: group-level info (group_name / required / multi_select / max_select) appears once on the group rather than repeating on every option, and the options themselves sit in options[] with field-for-field identical values. The two shapes are mutually exclusive — you get one or the other, never both — and carry exactly the same content.Error Codes
See Error Handling for the full list.

