> ## 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.

# Ordering & tracking

> Validate, price, submit, track, cancel, and update orders — with every issue code validation can emit.

The order lifecycle tools. `validate_order`, `quote_order`, and `submit_order` share one canonical order-intent shape; the by-id tools enforce ownership (an agent only touches orders it created).

## validate\_order

Run Phantom rule validation before quote or submit.

REST equivalent: `POST /api/agent/restaurants/:restaurantId/orders/validate`.

Input — `{ "restaurant_id": "…", "order": <canonical order intent> }`:

```json theme={null}
{
  "restaurant_id": "rest_lb_steakhouse",
  "order": {
    "restaurant_id": "rest_lb_steakhouse",
    "external_order_reference": "yourapp-order-1001",
    "customer": { "name": "Avery", "email": "avery@example.com", "phone": "+16505551234", "teamName": "U14 Blue" },
    "fulfillment_type": "pickup",
    "requested_fulfillment_time": "2026-08-11T19:00:00.000Z",
    "fulfillment_address": {
      "address1": "500 Broadway",
      "city": "Redwood City",
      "state": "CA",
      "postal_code": "94063",
      "notes": "Gate code 1234",
      "latitude": 37.4852,
      "longitude": -122.2364
    },
    "headcount": 2,
    "tip_cents": 500,
    "budget_constraints": { "max_total_cents": 20000 },
    "payment_policy": "required_before_submit | invoice_manual | stored_payment",
    "items": [
      {
        "item_id": "item_filet",
        "quantity": 1,
        "player_first_name": "Avery",
        "player_last_name": "Nguyen",
        "notes": "No butter",
        "modifiers": []
      }
    ],
    "dietary_constraints": [],
    "packaging_instructions": "Label each box",
    "include_cutlery": false,
    "substitution_policy": "strict | allow_equivalent | require_approval",
    "approval_requirements": { "manager_approval_required": false },
    "metadata": {}
  }
}
```

* `restaurant_id` at the tool level and inside `order` must match, or the call throws.
* `order.agent_id` is stamped with the authenticated agent; supplying a different one throws.
* `fulfillment_address` is required for delivery; send `latitude`/`longitude` when the client's address autocomplete has geometry (courier dispatch needs them).
* Per-player naming: a line with `player_first_name` must carry `quantity: 1` — two players means two item lines.

Output:

```json theme={null}
{
  "id": "ovr_123",
  "orderId": "order_123",
  "valid": false,
  "issues": [
    {
      "code": "lead_time_too_short",
      "message": "Minimum lead time is 95 minutes for delivery orders at this restaurant.",
      "field": "requested_fulfillment_time",
      "severity": "error"
    }
  ],
  "checkedAt": "2026-08-06T21:00:00.000Z",
  "idempotencyKey": "…"
}
```

Issue codes currently emitted:

`ordering_disabled`, `channel_deactivated`, `location_paused`, `lead_time_too_short`, `lead_time_too_long`, `headcount_too_large`, `menu_version_stale`, `player_name_incomplete`, `player_name_requires_separate_item_line`, `item_not_found`, `item_unavailable`, `item_unavailable_at_time`, `mapping_needs_review`, `modifier_group_invalid`, `modifier_invalid`, `modifier_unavailable`, `nested_modifier_quantity_invalid`, `modifier_not_nested`, `required_modifier_missing`, `too_many_modifiers`, `modifier_quantity_exceeded`, `too_many_items`, `order_value_too_large`, `delivery_unavailable`, `order_validation_failed`.

## quote\_order

Return pricing for a valid order.

REST equivalent: `POST /api/agent/restaurants/:restaurantId/orders/quote`.

Input: same `{ restaurant_id, order }` shape as `validate_order`.

Output:

```json theme={null}
{
  "id": "quote_123",
  "orderId": "order_123",
  "subtotalCents": 4200,
  "taxCents": 378,
  "feesCents": 0,
  "serviceFeeCents": 210,
  "deliveryFeeCents": 899,
  "tipCents": 500,
  "totalCents": 6187,
  "currency": "USD",
  "quotedAt": "2026-08-06T21:00:00.000Z",
  "idempotencyKey": "…"
}
```

`deliveryFeeCents` is the courier fee, already folded into `totalCents`; it is 0/absent on pickup or when no dispatch quote was available. `serviceFeeCents` is the agent's consumer fee (a configured share of subtotal).

Idempotency replays are re-shaped to this current schema before being returned.

## submit\_order

Create the order in Phantom and enter the normal approval and provider-submission lifecycle.

REST equivalent: `POST /api/agent/restaurants/:restaurantId/orders/submit`.

Input: same `{ restaurant_id, order }` shape as `validate_order`.

Output:

```json theme={null}
{
  "id": "order_123",
  "restaurantId": "rest_lb_steakhouse",
  "agentId": "agent_phantom",
  "externalOrderReference": "yourapp-order-1001",
  "customerName": "Avery",
  "customerEmail": "avery@example.com",
  "teamName": "U14 Blue",
  "fulfillmentType": "pickup",
  "requestedFulfillmentTime": "2026-08-11T19:00:00.000Z",
  "headcount": 2,
  "status": "needs_approval",
  "approvalRequired": true,
  "totalEstimateCents": 6187,
  "createdAt": "2026-08-06T21:00:00.000Z",
  "updatedAt": "2026-08-06T21:00:00.000Z",
  "notes": null,
  "packagingInstructions": null,
  "dietaryConstraints": [],
  "orderIntent": {}
}
```

`submit_order` never bypasses the restaurant's approval rules — a `needs_approval` order awaits a human decision.

## get\_order\_status

Phantom's last known order state, latest provider order identifier, and the payment snapshot.

REST equivalent: `GET /api/agent/orders/:orderId/status`.

Input: `{ "order_id": "order_123" }`

Output:

```json theme={null}
{
  "orderId": "order_123",
  "status": "accepted",
  "totalEstimateCents": 6187,
  "externalOrderId": "dlv_yourapp-order-1001",
  "updatedAt": "2026-08-06T21:05:00.000Z",
  "payment": {
    "status": "paid",
    "amountCents": 6187,
    "refundedAmountCents": 0,
    "updatedAt": "2026-08-06T21:05:00.000Z"
  }
}
```

`payment` is `null` when no payment record exists. `get_order_status` exposes Phantom's order lifecycle, not raw provider-only state.

## get\_order\_tracking

Full tracking snapshot: kitchen status + timestamped status-event timeline, and for delivery orders the courier leg.

Input: `{ "order_id": "order_123" }`

Output:

```json theme={null}
{
  "orderId": "order_123",
  "status": "preparing",
  "fulfillmentType": "delivery",
  "requestedFulfillmentTime": "2026-08-11T19:00:00.000Z",
  "fulfillmentAsap": true,
  "estimatedReadyTime": "2026-08-11T18:50:00.000Z",
  "createdAt": "2026-08-11T18:00:00.000Z",
  "statusEvents": [
    { "status": "accepted", "message": "Restaurant accepted the order.", "createdAt": "2026-08-11T18:02:00.000Z" }
  ],
  "delivery": {
    "status": "en_route_to_pickup",
    "driver": { "name": "Sam", "phoneNumber": "+16505550000", "trackingLink": "https://track.example/abc" },
    "estimatedPickupTime": "2026-08-11T18:50:00.000Z",
    "estimatedDropoffTime": "2026-08-11T19:05:00.000Z",
    "requestedDropoffTime": "2026-08-11T19:00:00.000Z",
    "events": [{ "status": "assigned", "receivedAt": "2026-08-11T18:10:00.000Z", "message": null }]
  }
}
```

Pickup orders return `delivery: null`.

## cancel\_order

Cancel a pending order the calling agent placed. Only possible **before** the restaurant accepts it. Cancels the POS ticket and any courier, and best-effort reverses the payment (refunds captured charges with payout clawback, voids held authorizations).

Input: `{ "order_id": "order_123", "reason": "Customer changed plans" }` (reason optional, ≤200 chars)

Output: `{ "ok": true, "status": "cancelled", "message": "…", "payment": "refunded" }`

There is deliberately **no** restaurant-access check here: an agent must be able to cancel its own pending order even if the restaurant has since disabled ordering or its channel was deactivated.

## update\_order

Update a live order's fulfillment details and re-point any courier (partial PATCH).

Input:

```json theme={null}
{
  "order_id": "order_123",
  "requested_fulfillment_time": "2026-08-11T19:30:00.000Z",
  "dropoff_notes": "Leave with the front desk",
  "dropoff_address": {
    "address1": "500 Broadway",
    "city": "Redwood City",
    "state": "CA",
    "postal_code": "94063"
  }
}
```

Output:

```json theme={null}
{ "ok": true, "status": "submitted", "message": "…", "deliverThatUpdated": true, "warnings": [] }
```

`status` echoes the order's current lifecycle status, not an update result.

**Menu items are not editable.** The restaurant's POS ticket cannot be amended post-submit — cancel and reorder instead. The editable surface is exactly what the courier's partial update accepts: time, dropoff address, dropoff notes.
