Skip to main content
Phantom is MealOps’ ordering infrastructure for AI agents. It gives your agent a single, safe interface to real restaurants: discovery, live menus, price quotes, payments, order submission, and tracking — with the restaurant’s own rules (hours, lead times, order caps, approval flow) enforced server-side. This guide takes an agent team from zero to a first completed order. The full tool-by-tool contract lives in the companion document: MCP Tool Reference.

Endpoints

MCP is the recommended surface: it is a thin wrapper over the same REST services (no separate ordering logic), but adds relevance-ranked search envelopes, cross-restaurant dish search, the availability calendar, and server-published flow instructions. Everything your key can do over MCP it can also do over REST, minus the MCP-only discovery tools.

Getting credentials

Access is by partnership. Email support@mealops.ai to set up a partner account. You receive:
  • A partner identity (your company) and one or more agent identities (a specific integration or app surface).
  • An API key per agent. The raw key is shown exactly once at creation — Phantom stores only a prefix and hash and can never show it again. Keys can be rotated (new raw key, old one stops working) and revoked instantly; every lifecycle action is audit-logged.
  • A set of scopes on the key (see below). Ask for the narrowest set you need; scopes can be upgraded later and apply on your next request with no reconnect.
Restaurant access is per-restaurant: a restaurant (or its brand) must allow your agent before it appears in your search results. Approving your agent for one restaurant never implies access to others. New Deliverect-connected restaurants can grant access to specific agents by default via their brand settings. Operational guidance:
  • Use one key per integration surface; do not share a key across systems.
  • Rotate keys when integration owners change; revoke immediately on any suspected exposure.

Authentication

Send the key as a bearer token on every request (MCP and REST):
The legacy header x-agent-api-key: <key> is also accepted and is treated identically.

Scopes

The MCP tool list is scope-filtered per credential. Tools your key cannot call are not registered at all — a discovery-only key (restaurants:read + menus:read) sees 7 tools instead of 20. Consequence: calling a tool your key can’t see returns the MCP “tool not found” error, not a scope message. If a tool seems missing from your list, check your key’s scopes first.

Connecting over MCP

Phantom speaks MCP over Streamable HTTP at POST /mcp. A typical client configuration:
On initialize the server publishes instructions — a short script of the canonical flow (discover → inspect → price → pay → submit → track) so agents sequence tools and pick the right payment branch without trial and error. A REST equivalent for quick checks:

The canonical order flow

A complete order is about six calls:
  1. Discoversearch_restaurants (or search_menu_items for “who has pad thai near me”). A location is required for restaurant results; a name query without a location returns brand-level summaries so you can ask the user “which area?”.
  2. Inspectget_menu for the chosen restaurant. Item search results never carry modifier data; always read the full menu before building an order.
  3. Pick a timeget_availability_calendar returns the concrete windows an order may be scheduled inside (hours ∩ lead-time floor ∩ scheduling horizon). Use it instead of guess-and-check validate loops.
  4. Validatevalidate_order runs the restaurant’s full rule set and returns machine-readable issue codes.
  5. Pricequote_order returns the authoritative total (subtotal, tax, fees, courier fee, tip).
  6. Paystart_payment (see payment branches below) when the restaurant requires prepaid checkout.
  7. Submitsubmit_order creates the order and enters the restaurant’s approval flow.
  8. Trackget_order_status / get_order_tracking.

The ordering-time model

Several tools return timing fields. They are components of one formula — do not invent your own:
The 25-minute delivery transit allowance is currently a flat constant, not per-restaurant. Timezones: all times on the wire are ISO-8601 UTC. Scheduled fulfillment times are restaurant-local in meaning — use the restaurant’s timezone to render them to users. For self-delivery orders the pickup and delivery times are the same instant; the 15-minute courier collection lead applies only when a real courier collects.

The order intent

validate_order, quote_order, start_payment, and submit_order all take the same { restaurant_id, order } shape:
Rules:
  • restaurant_id at the tool level and inside order must match, or the call throws.
  • order.agent_id is stamped with the authenticated agent automatically; supplying a different one throws.
  • fulfillment_address is required for delivery. Send latitude/longitude when your address autocomplete has geometry — courier dispatch needs them.
  • Per-person naming: an item line with player_first_name must have quantity: 1 — two named people means two item lines.
  • fulfillment_type is one of pickup | delivery | catering | eat_in | curbside.

Validation issue codes

validate_order returns { valid, issues: [{ code, message, field, severity }] }. 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.

Payments

Phantom is charged-first merchant of record: the eater’s card is charged (or authorized) before submit, and the restaurant is paid out on acceptance. start_payment picks its branch from what you supply: Supporting tools: charge_saved_card (pre-submit charge), charge_payment (charge for an already-submitted order), void_payment (release a held authorization when a submit fails), get_payment_status (reconciliation by your external_order_reference), create_payout (reconcile fallback — payout normally runs automatically when the restaurant accepts).

Rate limits

All limits are per credential on a fixed 60-second window. Exceeding one returns HTTP 429 with { "error": "…" }; every response carries x-ratelimit-remaining and x-ratelimit-reset (epoch ms). Treat 429 as retryable after a short backoff — x-ratelimit-reset gives the exact boundary. MCP (POST /mcp): REST (/api/agent/*), per route:

Visibility and ownership rules

  • A restaurant is omitted from your results entirely when its permission for your agent is not allowed, agent ordering is off, the restaurant is not payout-ready, or its POS channel is deactivated (re-activation restores it automatically).
  • Order tools enforce ownership: your agent can only read, track, cancel, or update orders it created. Foreign and missing order ids both return Order <id> not found. — ids are not enumerable across agents.
  • cancel_order works only before the restaurant accepts. It cancels the POS ticket and any courier, and best-effort reverses payment (refund with payout clawback, or void of a held authorization).
  • update_order can change fulfillment time, dropoff address, and dropoff notes on a live order. Menu items are not editable post-submit — the restaurant’s POS ticket cannot be amended; cancel and reorder instead.

Best practices

  • Prefer server-resolved values over recomputing: resolvedMinLeadTimeMinutes, earliest_orderable_time, the quote total, and check_delivery_availability are authoritative.
  • Use get_availability_calendar to pick fulfillment times instead of looping validate_order.
  • Use the fields projection and pagination (offset, total_matches, next_offset) on search_restaurants for cheap disambiguation before pulling full entries.
  • When a search returns empty, read no_results_reason and recover (location_required → ask for a location; all_closed_at_requested_time → offer a later time) instead of guessing.
  • Respect menu_version_stale: re-read the menu and rebuild the order if validation says your menu snapshot is out of date.
  • submit_order never bypasses the restaurant’s approval rules; an order may come back needs_approval and be accepted or declined by a human. Track it.

Support

Partnership, credentials, scope changes, and rate-limit increases: support@mealops.ai.