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.
- 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):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 atPOST /mcp. A typical client configuration:
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:- Discover —
search_restaurants(orsearch_menu_itemsfor “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?”. - Inspect —
get_menufor the chosen restaurant. Item search results never carry modifier data; always read the full menu before building an order. - Pick a time —
get_availability_calendarreturns the concrete windows an order may be scheduled inside (hours ∩ lead-time floor ∩ scheduling horizon). Use it instead of guess-and-check validate loops. - Validate —
validate_orderruns the restaurant’s full rule set and returns machine-readable issue codes. - Price —
quote_orderreturns the authoritative total (subtotal, tax, fees, courier fee, tip). - Pay —
start_payment(see payment branches below) when the restaurant requires prepaid checkout. - Submit —
submit_ordercreates the order and enters the restaurant’s approval flow. - Track —
get_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:
restaurant_idat the tool level and insideordermust match, or the call throws.order.agent_idis stamped with the authenticated agent automatically; supplying a different one throws.fulfillment_addressis required for delivery. Sendlatitude/longitudewhen your address autocomplete has geometry — courier dispatch needs them.- Per-person naming: an item line with
player_first_namemust havequantity: 1— two named people means two item lines. fulfillment_typeis one ofpickup | 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_orderworks 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_ordercan 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, andcheck_delivery_availabilityare authoritative. - Use
get_availability_calendarto pick fulfillment times instead of loopingvalidate_order. - Use the
fieldsprojection and pagination (offset,total_matches,next_offset) onsearch_restaurantsfor cheap disambiguation before pulling full entries. - When a search returns empty, read
no_results_reasonand 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_ordernever bypasses the restaurant’s approval rules; an order may come backneeds_approvaland be accepted or declined by a human. Track it.