{"openapi": "3.1.0", "info": {"title": "Wemay POS Restaurants", "version": "1.0.0", "description": "Order pickup food from independent restaurants that run Wemay POS. The same six tools are available as an MCP server at /mcp. No account or API key is needed; the customer pays on a secure payment page and the kitchen receives the order only after payment.", "contact": {"name": "Wemay POS", "email": "info@ezpos.org", "url": "https://mcp.wemay.app/docs"}}, "externalDocs": {"description": "Human-readable documentation", "url": "https://mcp.wemay.app/docs"}, "servers": [{"url": "https://mcp.wemay.app"}], "tags": [{"name": "Restaurants", "description": "Find participating restaurants and read their details."}, {"name": "Menu", "description": "Live menu and per-item options."}, {"name": "Ordering", "description": "Price a cart and create the secure payment link. Nothing is charged until the customer pays on that page."}], "paths": {"/api/restaurants": {"get": {"operationId": "searchRestaurants", "summary": "Find restaurants", "description": "Find restaurants that take orders through Wemay POS. query can be a restaurant name or address word (city, street), a cuisine (Chinese) or a dish (noodles, dumplings, fish, spicy); filler words like 'food', 'restaurant' or 'near me' are ignored, so 'Chinese food near me' works. Leave query empty to list all. Each result has match_reason (name, cuisine or dish) and, for dish matches, matched_dishes (up to 3 dishes with item_id and price). If nothing matches, matched=false and the participating restaurants are listed anyway: check their menus with get_menu (search=...) instead of telling the user nothing exists. When the user says 'near me' / 'nearby' or names a place, pass latitude and longitude first: the device location if you have it, otherwise the approximate center of the city or ZIP code the user gave. With a location, results are sorted by how well they match, then by distance_miles, and limited to radius_miles when browsing or searching by cuisine or dish; if none is that close, the nearest ones come back with outside_radius=true and you must tell the user how far they are. A query matching a restaurant's name or address always finds it, however far.", "tags": ["Restaurants"], "x-mcp-tool": "search_restaurants", "parameters": [{"name": "query", "in": "query", "required": false, "description": "Restaurant name or address words, a cuisine (Chinese) or a dish (noodles, dumplings); filler like \"food\" or \"near me\" is ignored. Empty lists all.", "schema": {"type": "string"}}, {"name": "latitude", "in": "query", "required": false, "description": "Customer's latitude. Send together with longitude to get nearby restaurants with distance_miles.", "schema": {"type": "number", "minimum": -90, "maximum": 90}}, {"name": "longitude", "in": "query", "required": false, "description": "Customer's longitude. Send together with latitude.", "schema": {"type": "number", "minimum": -180, "maximum": 180}}, {"name": "radius_miles", "in": "query", "required": false, "description": "Search radius in miles (with latitude/longitude).", "schema": {"type": "number", "default": 25, "maximum": 100}}], "responses": {"200": {"description": "Result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SearchRestaurantsResult"}}}}, "4XX": {"$ref": "#/components/responses/Error"}, "5XX": {"$ref": "#/components/responses/Error"}}}}, "/api/restaurants/{restaurant_id}": {"get": {"operationId": "getRestaurant", "summary": "Restaurant details", "description": "Address, phone, opening hours, whether it is open now, and temporary closures. 'scheduling' tells whether pickup can be scheduled for later, whether ASAP is available now, how many days ahead, the slot size and the earliest pickup_time.", "tags": ["Restaurants"], "x-mcp-tool": "get_restaurant", "parameters": [{"name": "restaurant_id", "in": "path", "required": true, "description": "From searchRestaurants.", "schema": {"type": "integer"}}], "responses": {"200": {"description": "Result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Restaurant"}}}}, "4XX": {"$ref": "#/components/responses/Error"}, "5XX": {"$ref": "#/components/responses/Error"}}}}, "/api/restaurants/{restaurant_id}/menu": {"get": {"operationId": "getMenu", "summary": "Menu", "description": "Live online menu: categories and items with prices in USD. Sold-out items are already removed. 'options' tells whether get_item_options is needed before ordering. Filter with category_id or search for long menus.", "tags": ["Menu"], "x-mcp-tool": "get_menu", "parameters": [{"name": "restaurant_id", "in": "path", "required": true, "description": "From searchRestaurants.", "schema": {"type": "integer"}}, {"name": "category_id", "in": "query", "required": false, "description": "Only this category.", "schema": {"type": "integer"}}, {"name": "search", "in": "query", "required": false, "description": "Words to match in item names or descriptions.", "schema": {"type": "string"}}], "responses": {"200": {"description": "Result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MenuResult"}}}}, "4XX": {"$ref": "#/components/responses/Error"}, "5XX": {"$ref": "#/components/responses/Error"}}}}, "/api/restaurants/{restaurant_id}/items/{item_id}": {"get": {"operationId": "getItemOptions", "summary": "Item options", "description": "Variants (sizes) or modifier groups for one item, with min/max rules and prices. Call this for any item whose options are not 'none'.", "tags": ["Menu"], "x-mcp-tool": "get_item_options", "parameters": [{"name": "restaurant_id", "in": "path", "required": true, "description": "From searchRestaurants.", "schema": {"type": "integer"}}, {"name": "item_id", "in": "path", "required": true, "description": "From getMenu.", "schema": {"type": "integer"}}], "responses": {"200": {"description": "Result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ItemOptionsResult"}}}}, "4XX": {"$ref": "#/components/responses/Error"}, "5XX": {"$ref": "#/components/responses/Error"}}}}, "/api/restaurants/{restaurant_id}/quote": {"post": {"operationId": "quoteOrder", "summary": "Price a cart", "description": "Validate a pickup cart and get the restaurant's own price: subtotal, tax, any card surcharge, total before tip. Nothing is ordered or charged. Also returns the pickup restaurant and address: confirm with the user that they can pick up there (see confirm_with_user) before sharing any payment link. Pass the user's latitude/longitude if known to get distance_miles and far_from_user. For a later pickup (e.g. 'tomorrow at 12:30') pass pickup_time in the restaurant's local time; it must be within business hours. When the restaurant is closed (or has no pickup times left today) an ASAP order fails with pickup_time_required: ask the user what time they want, offer next_pickup_times, and retry with pickup_time. pickup_time_outside_hours / pickup_time_out_of_window: same, offer next_pickup_times.", "tags": ["Ordering"], "x-mcp-tool": "quote_order", "parameters": [{"name": "restaurant_id", "in": "path", "required": true, "description": "From searchRestaurants.", "schema": {"type": "integer"}}], "responses": {"200": {"description": "Result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/QuoteResult"}}}}, "4XX": {"$ref": "#/components/responses/Error"}, "5XX": {"$ref": "#/components/responses/Error"}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"items": {"type": "array", "minItems": 1, "description": "Cart lines. Get ids from get_menu / get_item_options.", "items": {"$ref": "#/components/schemas/CartItem"}}, "pickup_time": {"type": "string", "description": "When the customer will pick up, in the restaurant's LOCAL time (get_restaurant time_zone / local_time), as YYYY-MM-DDTHH:MM with no time zone, e.g. 2026-11-01T12:30. Omit or 'asap' for as soon as possible (only while the restaurant is open). Must be within business hours and one of the restaurant's pickup slots (get_restaurant scheduling); pass the same value to quote_order and create_checkout_link."}, "latitude": {"type": "number", "description": "Customer's latitude in decimal degrees. Send together with longitude. Optional; used only to compute distance.", "minimum": -90, "maximum": 90, "example": 37.5485}, "longitude": {"type": "number", "description": "Customer's longitude in decimal degrees. Send together with latitude.", "minimum": -180, "maximum": 180, "example": -121.9886}}, "required": ["items"]}, "example": {"items": [{"item_id": 201, "quantity": 2, "modifiers": [{"modifier_id": 101}], "note": "no scallions"}, {"item_id": 301, "quantity": 1, "sku_id": 3001}], "latitude": 37.5485, "longitude": -121.9886}}}}}}, "/api/restaurants/{restaurant_id}/checkout-link": {"post": {"operationId": "createCheckoutLink", "summary": "Create checkout link", "description": "Turn a validated pickup cart into a secure payment link. Ask the customer for the name and phone number to use for pickup and pass them: most restaurants then return a short pay page showing exactly this order (tip, card / Apple Pay / Google Pay). Without them, or where the restaurant does not support it, you get a cart page where the customer enters their details. Nothing is charged or sent to the kitchen until the customer pays. Asking again for the same order returns the same link (reused=true). Before sharing the link, tell the user the pickup restaurant name and address (pickup, confirm_with_user) and confirm they can pick up there. Never ask for card details in chat. For a scheduled pickup pass the same pickup_time you quoted: the pay page shows that pickup time (on a cart page, the customer should check that the page shows that pickup time before paying). Same pickup_time errors as quote_order.", "tags": ["Ordering"], "x-mcp-tool": "create_checkout_link", "parameters": [{"name": "restaurant_id", "in": "path", "required": true, "description": "From searchRestaurants.", "schema": {"type": "integer"}}], "responses": {"200": {"description": "Result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CheckoutLinkResult"}}}}, "4XX": {"$ref": "#/components/responses/Error"}, "5XX": {"$ref": "#/components/responses/Error"}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"items": {"type": "array", "minItems": 1, "description": "Cart lines. Get ids from get_menu / get_item_options.", "items": {"$ref": "#/components/schemas/CartItem"}}, "pickup_time": {"type": "string", "description": "When the customer will pick up, in the restaurant's LOCAL time (get_restaurant time_zone / local_time), as YYYY-MM-DDTHH:MM with no time zone, e.g. 2026-11-01T12:30. Omit or 'asap' for as soon as possible (only while the restaurant is open). Must be within business hours and one of the restaurant's pickup slots (get_restaurant scheduling); pass the same value to quote_order and create_checkout_link."}, "customer_name": {"type": "string", "maxLength": 40, "description": "Name the restaurant calls out at pickup."}, "customer_phone": {"type": "string", "description": "10-digit US phone for this pickup order."}, "latitude": {"type": "number", "description": "Customer's latitude in decimal degrees. Send together with longitude. Optional; used only to compute distance.", "minimum": -90, "maximum": 90, "example": 37.5485}, "longitude": {"type": "number", "description": "Customer's longitude in decimal degrees. Send together with latitude.", "minimum": -180, "maximum": 180, "example": -121.9886}}, "required": ["items"]}, "example": {"items": [{"item_id": 201, "quantity": 2, "modifiers": [{"modifier_id": 101}], "note": "no scallions"}, {"item_id": 301, "quantity": 1, "sku_id": 3001}], "customer_name": "Alex", "customer_phone": "(510) 555-0100", "latitude": 37.5485, "longitude": -121.9886}}}}}}}, "components": {"schemas": {"Error": {"type": "object", "properties": {"error": {"type": "string", "description": "Machine-readable code, e.g. bad_arguments, not_enabled, item_not_found, invalid_cart, not_taking_orders, payment_locked, pickup_time_required, pickup_time_outside_hours, pickup_time_out_of_window, upstream_error, or the restaurant's own rejection reason."}, "message": {"type": "string", "description": "Explanation the assistant can act on or relay."}, "details": {"description": "invalid_cart: per-line problems (array). payment_locked: {pickup} with the restaurant's phone so the user can call them (object). pickup_time_required (ASAP is not possible now: closed or no pickup times left today) and pickup_time_outside_hours: {next_pickup_times, hours} (object). pickup_time_out_of_window: {next_pickup_times, max_days_ahead} (object).", "oneOf": [{"type": "array", "items": {"$ref": "#/components/schemas/CartLineError"}}, {"type": "object", "properties": {"pickup": {"$ref": "#/components/schemas/Pickup"}, "next_pickup_times": {"type": "array", "description": "Nearest pickup slots, soonest first (at most 3).", "items": {"$ref": "#/components/schemas/PickupSlot"}}, "hours": {"type": "array", "description": "Business hours to tell the user: today and the next business day (pickup_time_required) or the chosen day (pickup_time_outside_hours).", "items": {"$ref": "#/components/schemas/DayHours"}}, "max_days_ahead": {"anyOf": [{"type": "integer", "description": "How many days ahead pickup can be scheduled (0 = today only)."}, {"type": "null"}]}}}]}}, "required": ["error", "message"]}, "CartLineError": {"type": "object", "properties": {"line": {"anyOf": [{"type": "integer", "description": "1-based cart line; null when the problem is with the cart as a whole."}, {"type": "null"}]}, "code": {"type": "string", "description": "e.g. choice_required, sold_out, not_available_now, not_available_at_pickup_time, bad_quantity."}, "message": {"type": "string"}}, "required": ["line", "code", "message"]}, "Pickup": {"type": "object", "properties": {"restaurant_name": {"type": "string"}, "address": {"type": "string", "description": "Where the customer picks the order up."}, "phone": {"type": "string"}, "time": {"type": "string", "description": "When: \"As soon as possible\" or the scheduled time in the restaurant's local time, e.g. \"Sun, Nov 1, 12:30 PM\"."}}, "required": ["restaurant_name", "address", "phone"]}, "PickupSlot": {"type": "object", "properties": {"pickup_time": {"type": "string", "description": "Value to pass as pickup_time: restaurant local time YYYY-MM-DDTHH:MM."}, "local": {"type": "string", "description": "Readable restaurant local time, e.g. \"Sun, Nov 1, 12:30 PM\"."}}, "required": ["pickup_time", "local"]}, "DayHours": {"type": "object", "properties": {"date": {"type": "string", "description": "Restaurant local date, YYYY-MM-DD.", "format": "date"}, "day": {"type": "string", "description": "Readable, e.g. \"Sun, Nov 1\"."}, "hours": {"type": "string", "description": "e.g. \"11:00 AM-9:00 PM\", \"Closed\", or hours plus temporary closures."}}, "required": ["date", "day", "hours"]}, "PickupTime": {"type": "object", "properties": {"asap": {"type": "boolean", "description": "True for as soon as possible."}, "value": {"type": "string", "description": "\"asap\" or the pickup_time that was used (restaurant local YYYY-MM-DDTHH:MM)."}, "local": {"type": "string", "description": "Readable: \"As soon as possible\" or e.g. \"Sun, Nov 1, 12:30 PM\" (restaurant local time)."}, "iso": {"anyOf": [{"type": "string", "description": "The same moment in UTC (ISO 8601); null for ASAP.", "format": "date-time"}, {"type": "null"}]}}, "required": ["asap", "value", "local", "iso"]}, "Scheduling": {"type": "object", "properties": {"accepts_scheduled_pickup": {"type": "boolean", "description": "Whether orders can be placed for a later pickup time."}, "asap_allowed": {"type": "boolean", "description": "False when the restaurant only takes orders for a specific time."}, "asap_available_now": {"type": "boolean", "description": "Whether an ASAP order can be placed right now (open and pickup times left today). False: ask the user for a pickup_time."}, "max_days_ahead": {"anyOf": [{"type": "integer", "description": "How many days ahead a pickup can be scheduled (0 = today only, restaurant's calendar). null = no limit."}, {"type": "null"}]}, "slot_minutes": {"type": "integer", "description": "Pickup slots are this many minutes apart."}, "lead_minutes": {"type": "integer", "description": "The first slot of each opening period is this many minutes after opening; today's slots are at least this far from now."}, "earliest_pickup_time": {"anyOf": [{"$ref": "#/components/schemas/PickupSlot"}, {"type": "null"}]}}, "required": ["accepts_scheduled_pickup", "asap_allowed", "asap_available_now", "max_days_ahead", "slot_minutes", "lead_minutes", "earliest_pickup_time"]}, "Near": {"type": "object", "properties": {"latitude": {"type": "number"}, "longitude": {"type": "number"}, "radius_miles": {"type": "number"}}, "description": "The location filter that was applied."}, "CartItem": {"type": "object", "properties": {"item_id": {"type": "integer"}, "quantity": {"type": "integer", "minimum": 1, "default": 1}, "sku_id": {"type": "integer", "description": "Required for items whose options are 'choose_size_or_variant'."}, "modifiers": {"type": "array", "items": {"type": "object", "properties": {"modifier_id": {"type": "integer"}, "quantity": {"type": "integer", "minimum": 1, "default": 1}}, "required": ["modifier_id"]}}, "note": {"type": "string", "maxLength": 200, "description": "Special request for the kitchen, e.g. 'no onions'."}}, "required": ["item_id"]}, "RestaurantSummary": {"type": "object", "properties": {"restaurant_id": {"type": "integer", "description": "Use in every other call."}, "name": {"type": "string"}, "address": {"type": "string"}, "phone": {"type": "string"}, "open_now": {"type": "boolean"}, "ordering_page": {"type": "string", "description": "The restaurant's own web ordering page.", "format": "uri"}, "distance_miles": {"anyOf": [{"type": "number", "description": "Straight-line miles from the supplied location; null when no location was sent or the restaurant's position is unknown."}, {"type": "null"}]}, "outside_radius": {"type": "boolean", "description": "True when this restaurant is farther than radius_miles. Set only on the nearest few returned when none is inside the radius."}, "match_reason": {"anyOf": [{"type": "string", "description": "Why this restaurant matched the query: its name or address, its cuisine, or a dish on its menu. null when there was no query or nothing matched.", "enum": ["name", "cuisine", "dish"]}, {"type": "null"}]}, "matched_dishes": {"type": "array", "description": "Up to 3 menu dishes that fit the query (empty unless a dish name or menu category matched). Full menu: getMenu.", "items": {"$ref": "#/components/schemas/MatchedDish"}}}, "required": ["restaurant_id", "name", "address", "phone", "open_now", "ordering_page", "distance_miles", "outside_radius", "match_reason", "matched_dishes"]}, "MatchedDish": {"type": "object", "properties": {"item_id": {"type": "integer", "description": "Use in getMenu / getItemOptions / quote."}, "name": {"type": "string"}, "price": {"type": "string", "description": "Decimal string in USD; a range such as \"4.50-5.50\" when sizes differ."}}, "required": ["item_id", "name", "price"]}, "SearchRestaurantsResult": {"type": "object", "properties": {"restaurants": {"type": "array", "items": {"$ref": "#/components/schemas/RestaurantSummary"}}, "near": {"anyOf": [{"$ref": "#/components/schemas/Near"}, {"type": "null"}]}, "matched": {"anyOf": [{"type": "boolean", "description": "null when there was no query (or it held only filler words such as \"food near me\"); true when some restaurant matched by name, cuisine or dish; false when nothing matched and the participating restaurants are listed anyway."}, {"type": "null"}]}, "note": {"type": "string", "description": "Plain-language remark about the result, e.g. no match or nothing nearby."}}, "required": ["restaurants", "near", "matched"]}, "SpecialClosure": {"type": "object", "properties": {"date": {"anyOf": [{"type": "string", "description": "YYYY-MM-DD; null = every day in the window."}, {"type": "null"}]}, "all_day": {"type": "boolean"}, "from": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "to": {"anyOf": [{"type": "string"}, {"type": "null"}]}}}, "Restaurant": {"type": "object", "properties": {"restaurant_id": {"type": "integer"}, "name": {"type": "string"}, "address": {"type": "string"}, "phone": {"type": "string"}, "time_zone": {"type": "string", "description": "IANA time zone, e.g. America/Los_Angeles."}, "local_time": {"type": "string", "description": "Current local time at the restaurant."}, "open_now": {"type": "boolean"}, "closed_reason": {"anyOf": [{"type": "string", "description": "Why it is closed: no_hours, temporarily_closed or closed_now. null when open."}, {"type": "null"}]}, "hours": {"type": "array", "description": "Weekly opening hours, Monday first, e.g. \"Mon: 11:00 AM-9:00 PM\".", "items": {"type": "string"}}, "special_closures": {"type": "array", "description": "Upcoming or current temporary closures (at most 10).", "items": {"$ref": "#/components/schemas/SpecialClosure"}}, "sales_tax_percent": {"anyOf": [{"type": ["string", "number"], "description": "Sales tax rate in percent, as the restaurant configured it."}, {"type": "null"}]}, "order_types": {"type": "array", "description": "Always [\"pickup\"].", "items": {"type": "string"}}, "ordering_page": {"type": "string", "format": "uri"}, "scheduling": {"$ref": "#/components/schemas/Scheduling"}, "note": {"type": "string", "description": "Present when ASAP is not possible now: ask the user for a pickup time."}}, "required": ["restaurant_id", "name", "address", "phone", "time_zone", "local_time", "open_now", "closed_reason", "hours", "special_closures", "sales_tax_percent", "order_types", "ordering_page", "scheduling"]}, "MenuItem": {"type": "object", "properties": {"item_id": {"type": "integer"}, "name": {"type": "string"}, "name_zh": {"type": "string", "description": "Chinese name, only when it differs from name."}, "description": {"type": "string", "description": "Shortened; present only when the menu is small or filtered."}, "price": {"type": "string", "description": "USD. A range such as \"4.50-5.50\" when sizes differ.", "example": "13.95"}, "options": {"type": "string", "description": "Whether get_item_options is needed before ordering.", "enum": ["none", "optional_add_ons", "required_choices", "choose_size_or_variant"]}}, "required": ["item_id", "name", "price", "options"]}, "MenuCategory": {"type": "object", "properties": {"category_id": {"type": "integer"}, "name": {"type": "string"}, "name_zh": {"type": "string"}, "available_now": {"type": "boolean"}, "available_time": {"type": "string", "description": "When the category is served; present only when not available now."}, "items": {"type": "array", "items": {"$ref": "#/components/schemas/MenuItem"}}}, "required": ["category_id", "name", "available_now", "items"]}, "MenuResult": {"type": "object", "properties": {"restaurant_id": {"type": "integer"}, "currency": {"type": "string", "enum": ["USD"]}, "item_count": {"type": "integer"}, "descriptions_included": {"type": "boolean"}, "categories": {"type": "array", "items": {"$ref": "#/components/schemas/MenuCategory"}}, "open_now": {"type": "boolean"}, "note": {"type": "string", "description": "Present when the restaurant is closed."}}, "required": ["restaurant_id", "currency", "item_count", "descriptions_included", "categories", "open_now"]}, "Variant": {"type": "object", "properties": {"sku_id": {"type": "integer", "description": "Pass as sku_id in the cart line."}, "name": {"type": "string"}, "price": {"type": "string", "description": "Price of this variant. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "sold_out": {"type": "boolean"}}, "required": ["sku_id", "name", "price", "sold_out"]}, "Modifier": {"type": "object", "properties": {"modifier_id": {"type": "integer"}, "name": {"type": "string"}, "name_zh": {"type": "string"}, "price": {"type": "string", "description": "Extra price per unit. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "max_quantity": {"type": "integer", "description": "Most units of this modifier per line."}, "means_none": {"type": "boolean", "description": "True for a \"no add-ons\" choice; it excludes the group's other modifiers."}}, "required": ["modifier_id", "name", "price"]}, "ModifierGroup": {"type": "object", "properties": {"group_id": {"type": "integer"}, "name": {"type": "string"}, "rule": {"type": "string", "description": "Selection rule in words, e.g. \"choose exactly 1\"."}, "min": {"type": "integer"}, "max": {"anyOf": [{"type": "integer", "description": "null = no upper limit."}, {"type": "null"}]}, "modifiers": {"type": "array", "items": {"$ref": "#/components/schemas/Modifier"}}}, "required": ["group_id", "name", "rule", "min", "max", "modifiers"]}, "ItemOptionsResult": {"type": "object", "properties": {"item_id": {"type": "integer"}, "name": {"type": "string"}, "name_zh": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "price": {"type": "string", "description": "Base price. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "available_now": {"type": "boolean"}, "image_url": {"anyOf": [{"type": "string", "format": "uri"}, {"type": "null"}]}, "how_to_order": {"type": "string", "description": "How to put this item in a cart line."}, "variants": {"type": "array", "description": "Sizes / variants: pick exactly one sku_id. Present only for items with variants (such items take no modifiers).", "items": {"$ref": "#/components/schemas/Variant"}}, "modifier_groups": {"type": "array", "description": "Present for items without variants; empty when no choices are needed.", "items": {"$ref": "#/components/schemas/ModifierGroup"}}}, "required": ["item_id", "name", "name_zh", "description", "price", "available_now", "image_url", "how_to_order"]}, "QuoteResult": {"type": "object", "properties": {"restaurant_id": {"type": "integer"}, "items": {"type": "array", "description": "One readable line per cart line, e.g. \"2 x Beef Noodle + Mild\".", "items": {"type": "string"}}, "currency": {"type": "string", "enum": ["USD"]}, "subtotal": {"type": "string", "description": "Items before tax. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "discount": {"type": "string", "description": "Discount applied by the restaurant. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "tax": {"type": "string", "description": "Sales tax. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "card_surcharge": {"anyOf": [{"type": "string", "description": "Card surcharge, null when the restaurant charges none. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, {"type": "null"}]}, "total_before_tip": {"type": "string", "description": "Final price before tip. Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "open_now": {"type": "boolean"}, "pickup_time": {"$ref": "#/components/schemas/PickupTime"}, "note": {"type": "string"}, "pickup": {"$ref": "#/components/schemas/Pickup"}, "confirm_with_user": {"type": "string", "description": "What the assistant should confirm with the user (the pickup address) before sharing a checkout link."}, "distance_miles": {"anyOf": [{"type": "number", "description": "Straight-line miles from the supplied latitude/longitude to the restaurant. Present only when a location was sent; null if the restaurant's position is unknown."}, {"type": "null"}]}, "far_from_user": {"type": "boolean", "description": "True when the restaurant is far from the supplied location. Present only when a location was sent. Tell the user before they pay."}}, "required": ["restaurant_id", "items", "currency", "subtotal", "discount", "tax", "card_surcharge", "total_before_tip", "open_now", "pickup_time", "pickup", "confirm_with_user", "note"]}, "CheckoutLinkResult": {"type": "object", "properties": {"restaurant_id": {"type": "integer"}, "restaurant_name": {"type": "string"}, "items": {"type": "array", "items": {"type": "string"}}, "currency": {"type": "string", "enum": ["USD"]}, "open_now": {"type": "boolean"}, "pickup_time": {"$ref": "#/components/schemas/PickupTime"}, "checkout_url": {"type": "string", "description": "Give this link to the customer.", "format": "uri"}, "link_type": {"type": "string", "description": "pay_page = a payment page for exactly this order (name and phone were given); cart_page = the restaurant's page with the cart loaded.", "enum": ["pay_page", "cart_page"]}, "expires_at": {"anyOf": [{"type": "string", "description": "ISO 8601 time the link stops working.", "format": "date-time"}, {"type": "null"}]}, "total_before_tip": {"type": "string", "description": "Exact total on the payment page (link_type = pay_page). Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "estimated_total_before_tip": {"type": "string", "description": "Estimate; the checkout page confirms it (link_type = cart_page). Decimal string in USD, e.g. \"12.50\".", "example": "12.50"}, "next_step": {"type": "string", "description": "What to tell the customer next."}, "reused": {"type": "boolean", "description": "True when an identical earlier link was returned instead of a new one."}, "pickup": {"$ref": "#/components/schemas/Pickup"}, "confirm_with_user": {"type": "string", "description": "What the assistant should confirm with the user (the pickup address) before sharing a checkout link."}, "distance_miles": {"anyOf": [{"type": "number", "description": "Straight-line miles from the supplied latitude/longitude to the restaurant. Present only when a location was sent; null if the restaurant's position is unknown."}, {"type": "null"}]}, "far_from_user": {"type": "boolean", "description": "True when the restaurant is far from the supplied location. Present only when a location was sent. Tell the user before they pay."}}, "required": ["restaurant_id", "restaurant_name", "items", "currency", "open_now", "pickup_time", "checkout_url", "link_type", "pickup", "confirm_with_user", "next_step"]}}, "responses": {"Error": {"description": "Error with a machine code and a message the assistant can act on. HTTP status by code: bad_arguments (400), item_not_found (404), not_enabled (404), not_taking_orders (409), pickup_time_required (409), pickup_time_outside_hours (409), pickup_time_out_of_window (409), payment_locked (409), invalid_cart (422), upstream_error (502); other codes (the restaurant's own rejection reasons) are 422.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}