Skip to main content

get_item_options

Batch-fetches the full spec candidates of several items (sku_options), each sku entry nesting its own ingredient / preparation candidates (sku_options[].ingredient_options), with selected: true on every entry that would actually ride along on the order under the current selection. Read-only, mutates nothing, and fully decoupled from preview_order. Every item in a single call must belong to the same cart_id (call once per cart / shop). Typical scenarios:
  • Adjusting an order at preview time, in bulk: after preview_order, the user wants a different cup size or topping on several items in the cart — one call returns every item’s skus along with each sku’s own ingredient candidates; swap IDs, then simply re-run preview_order (quote_cart is optional).
  • Checking the options of several cart items at once, without calling per-item or re-reading the whole menu response (fewer nodes to wire in an orchestration).
  • Recovering options after menu paging / filtering: when the menu response has been pushed out of the conversation context, one call with item_ids brings back every option and ID for those items.
  • Enriching menu-outline items: items from get_shop_menu with full_menu=true come without specs / ingredients and are not directly orderable — pass their item_ids to this tool to enrich on demand; it returns the full specs / ingredients and the same ids become orderable right away.
Modification loop: every sku_id / option_id returned here is identical to the IDs get_shop_menu minted for this cart (never re-minted), so any entry can be passed straight back into quote_cart / preview_order. After changing the selection, call preview_order again for a fresh preview_id + confirmation_token before ordering (the old tokens are single-use with a 10-minute TTL).

Parameters

Returns

The top level is { "items": [...] }, where items is 1:1 and in the same order as the requested items (the i-th result corresponds to the i-th request entry). Each result is either a success entry (full spec / ingredient candidates) or a failure entry ({item_id, error}) — distinguished by the presence of an error field.
A per-item failure does not affect the others. If an item’s item_id does not belong to the cart, or its ingredient_option_ids pick the same single-select required group twice, only that entry comes back as {item_id, error: {code, message}}; the remaining items are returned normally and the whole call still succeeds. An invalid cart_id / consent, however, fails the whole call (see Errors).
All money fields (price / original_price / price_delta) are in cents (integer), not yuan. For example price: 1600 means ¥16.00.
selected vs selected_by_default. selected_by_default says which entry is the menu default; selected says which entry the order would actually carry. It is computed by the same logic as the ordering path: explicit picks count, and every required group you did not touch is auto-completed with that group’s default. With neither sku_id nor ingredient_option_ids supplied, selected shows exactly what a fully-default order carries — so you can render selected: true entries as “currently chosen” without missing auto-completed defaults.
Ingredients are per-sku and nested per sku. Ingredients are bound to a specific sku (different cup sizes may offer different options), so every sku_options[] entry nests that sku’s own ingredient_options — to switch sku, just read the target sku’s nested list; no second call needed. selected: true only appears inside the selected sku’s list (sku_options[].selected: true); other skus’ candidates carry selected_by_default / available only and are always selected: false, since that sku would not be ordered. For items with no sku dimension (sku_options is an empty array), the single candidate set is returned as a top-level ingredient_options field on that result entry — present only in that case.
Options can exclude each other, and candidates narrow as you choose. Some items layer their options — pick a coffee bean first, and only the extraction methods belonging to that bean remain selectable. Candidates that clash with the current selection are omitted from the list, so everything listed is pickable and you do not have to work out the compatible combinations yourself. Change the selection and call again; the selectable set follows.Swapping within a group is unaffected. Switching options inside a single-choice group is a normal move: send the new option_id and drop the old one from that group — the group’s other options always stay listed. Each sku is judged against its own selection, so switching sku only means reading that sku’s list.
To narrow step by step, resend the current selection every time. Availability is computed from the sku_id / ingredient_option_ids / ingredient_quantities supplied on this call; nothing is remembered between calls. Pick an option, then call again carrying it, to see the narrowed candidate set.⚠️ Same name, different option: options behind different upper-level choices may share a name (e.g. each of three coffee beans has its own “Signature Espresso”), so always distinguish by option_id. Comparing by name leads to the wrong conclusion that “the upper choice changed but the candidates did not” — measured across three beans, the selectable extraction methods were three completely disjoint sets of option_id with identical names.
Quoting only errors when you yourself pick a clashing pair. Send two mutually exclusive options together and quote_cart returns can_checkout: false, naming both sides in blocking_reason.But sending just one option that clashes with a default succeeds: your explicit pick is honoured, while the groups you did not specify get swapped for options compatible with it. To confirm what the order actually carries, read items[].selected_ingredients from quote_cart / preview_order — never assume “what I sent is what I get”.
Portion-style options use ingredient_quantities. Options whose max_quantity exceeds 1 (e.g. Starbucks espresso shots) render as a stepper in the app, so you can add several portions. Set the count via items[].ingredient_quantities on quote_cart / preview_order; omit it and each option uses its own default_quantity (the merchant-recommended count).Do not multiply price_delta by the portion count. Merchants price extra portions in tiers: espresso shots are free for 1-2 portions and ¥4 at the 3rd; mocha sauce is ¥3 for 1-4 portions and ¥6 total for 5-8. Options that carry price_steps must be read from that table. Options without it must not be multiplied either — for those, price_delta is what you pay no matter how many portions you add. In short: never multiply by the portion count.
Success entry fields (same structure as a single-item result): Failure entry fields:
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.

Errors

Example

Request (the user chose a large cup at preview time and wants to see the alternatives for that item plus another item):
Response (excerpt: the first item succeeds, the second item_id is invalid):