> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mealops.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Menus & restaurant detail

> Full canonical menus with modifiers, restaurant profiles with live timing, and the pre-checkout courier probe.

Everything an agent inspects between picking a restaurant and building an order.

## get\_menu

Return the full canonical menu for a restaurant.

REST equivalent: `GET /api/agent/restaurants/:restaurantId/menu`.

Input: `{ "restaurant_id": "rest_lb_steakhouse" }`

Output:

```json theme={null}
{
  "menus": [
    {
      "id": "menu_lunch",
      "restaurantId": "rest_lb_steakhouse",
      "providerMenuId": "dlv_menu_123",
      "name": "Lunch",
      "description": "",
      "sortOrder": 0,
      "availabilityWindows": [{ "day": "mon", "startTime": "11:00", "endTime": "15:00" }],
      "menuVersionId": "ver_9"
    }
  ],
  "items": [
    {
      "id": "item_filet",
      "restaurantId": "rest_lb_steakhouse",
      "menuId": "menu_lunch",
      "menuName": "Lunch",
      "menuSortOrder": 0,
      "category": "Mains",
      "categorySortOrder": 1,
      "sortOrder": 3,
      "name": "Filet Mignon",
      "description": "8oz",
      "imageUrl": null,
      "priceCents": 4200,
      "availability": "available | unavailable",
      "availabilityWindows": [],
      "mappingStatus": "mapped | needs_review",
      "modifierGroupIds": ["mg_temp"],
      "calories": 620,
      "productTags": [{ "code": 104, "label": "Eggs", "group": "allergen" }],
      "isCombo": false,
      "menuVersionId": "ver_9",
      "posRef": { "provider": "deliverect", "externalId": "PLU-123" }
    }
  ],
  "modifierGroups": [],
  "modifiers": [],
  "mappings": []
}
```

Multi-menu rules a client must follow:

* A restaurant serves **all** of its `menus` as siblings, not as versions of each other. Each has its own `availabilityWindows` (empty/absent = always available). Render one menu at a time.
* Sort by `menuSortOrder` **before** `categorySortOrder` **before** `sortOrder`. Two menus can each send a category with `sortOrder: 0`, so category order is only meaningful within a menu.
* Items with no `menuId` are legacy rows imported before menus were tracked — treat them as belonging to the restaurant's only/first menu.
* An item's `availabilityWindows` are the **effective** windows (menu opening hours, unless the category or the product overrides them). Combined with the restaurant's `openingHours`, this is what lets a client grey out an item that exists but is not orderable at the selected fulfillment time. Enforced server-side too (`item_unavailable_at_time`).
* Modifiers can nest: a modifier may own `childModifierGroupIds` pointing back into the same flat top-level collections. `defaultQuantity >= 1` means it comes pre-selected. Groups with `isBundleSection: true` belong to a meal deal (`isCombo` items); `maxPerModifier` caps repeats of a single modifier independently of the group total.

## get\_restaurant\_detail

Return restaurant profile, location, timing, busy state, and menu structure.

REST equivalent: `GET /api/agent/restaurants/:restaurantId`.

Input: `{ "restaurant_id": "rest_lb_steakhouse" }`

This call also refreshes opening hours from the POS (the restaurant may have edited hours there); the response waits only briefly on the refresh and falls back to stored hours.

Output:

```json theme={null}
{
  "restaurant": {
    "id": "rest_lb_steakhouse",
    "name": "LB Steakhouse",
    "location": "Redwood City, CA",
    "timezone": "America/Los_Angeles",
    "imageUrl": null,
    "cuisineType": "steakhouse",
    "description": null,
    "rating": 4.6,
    "deliveryFee": null,
    "minimumOrder": null,
    "supportsCatering": true,
    "prepTimeMinutes": 20,
    "minimumLeadTimeMinutes": 0,
    "deliveryMinLeadTimeMinutes": 95,
    "deliveryCourier": "deliverthat",
    "resolvedMinLeadTimeMinutes": { "pickup": 20, "delivery": 95 },
    "busyState": { "status": "busy", "delayMinutes": 20, "receivedAt": "2026-08-06T18:00:00.000Z" },
    "openingHours": [{ "day": "mon", "startTime": "11:00", "endTime": "21:00" }],
    "fulfillmentTypesSupported": ["pickup", "catering"],
    "selfDeliversEnabled": false,
    "deliverThatCoverage": "unknown",
    "defaultApprovalMode": "threshold_review",
    "agentOrderingEnabled": true
  },
  "location": {
    "id": "loc_1",
    "name": "Redwood City",
    "address1": "1 Main St",
    "city": "Redwood City",
    "state": "CA",
    "postalCode": "94063",
    "latitude": 37.4852,
    "longitude": -122.2364
  },
  "menus": [],
  "categories": [{ "name": "Mains", "itemCount": 12, "menuId": "menu_lunch", "menuName": "Lunch" }]
}
```

Notes:

* The tool returns `restaurant`, `location`, `menus`, `categories` only. Timing fields are mirrored onto `restaurant` so they survive that trim.
* `categories` are scoped per menu (`menuId`/`menuName`): two menus can each own a "Sides", and merging them would report one oversized category no single menu actually has.
* `selfDeliversEnabled` is already reflected in `fulfillmentTypesSupported` (delivery is stripped when a store neither self-delivers nor has a courier); it is surfaced explicitly so a client can explain **why** a store is pickup-only. For self-delivery orders the pickup and delivery times are the same instant — the 15-minute courier collection lead only applies when a real courier collects.

## check\_delivery\_availability

Pre-checkout courier probe: is delivery possible from this restaurant to this dropoff at this time, before any order exists?

Input:

```json theme={null}
{
  "restaurant_id": "rest_lb_steakhouse",
  "dropoff": {
    "address1": "500 Broadway",
    "city": "Redwood City",
    "state": "CA",
    "postal_code": "94063",
    "latitude": 37.4852,
    "longitude": -122.2364
  },
  "requested_fulfillment_time": "2026-08-11T19:00:00.000Z",
  "order_cost_cents": 4200
}
```

Output:

```json theme={null}
{
  "available": true,
  "checked": true,
  "reason": null,
  "feeCents": 899,
  "coverage": "supported | unsupported | unknown",
  "estimatedPickupTime": "2026-08-11T18:45:00.000Z",
  "estimatedDropoffTime": "2026-08-11T19:05:00.000Z",
  "quoteId": "dt_quote_1",
  "quoteExpiresAt": "2026-08-11T18:15:00.000Z"
}
```

Deliberately **optimistic**: only an explicit courier "not available" returns `available: false`. Transient failures return `available: true` with `checked: false`. The ETA/quote fields are `null` whenever no usable quote came back (probe disabled, no location, or an explicit no).
