{
  "$schema": "./_mapping.schema.json",
  "provider": "toast",
  "name": "Toast",
  "vertical": "pos",
  "verified_on": "2026-10-13",
  "docs": [
    { "title": "API overview", "url": "https://doc.toasttab.com/doc/devguide/apiOverview.html" },
    { "title": "Authentication and restaurant access", "url": "https://doc.toasttab.com/doc/devguide/authentication.html" },
    { "title": "Environments", "url": "https://doc.toasttab.com/doc/devguide/apiEnvironments.html" },
    { "title": "Menus API overview", "url": "https://doc.toasttab.com/doc/devguide/apiGettingMenuInformationFromTheMenusAPI.html" },
    { "title": "Menus V2: Menu", "url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/Menu/" },
    { "title": "Menus V2: MenuGroup", "url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuGroup/" },
    { "title": "Menus V2: MenuItem", "url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/" },
    { "title": "Menus V2: PricingRules", "url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/PricingRules/" },
    { "title": "Menus V2: TimeSpecificPrice", "url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/TimeSpecificPrice/" },
    { "title": "Menus V2: Schedule", "url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/Schedule/" },
    { "title": "Menus V2: TimeRange", "url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/TimeRange/" },
    { "title": "Restaurants: General", "url": "https://doc.toasttab.com/openapi/restaurants/tag/Data-definitions/schema/General/", "note": "timeZone (IANA) and currencyCode (ISO 4217)." },
    { "title": "Orders: Order", "url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Order/" },
    { "title": "Orders: Check", "url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Check/" },
    { "title": "Orders: Selection", "url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Selection/" },
    { "title": "Order prices", "url": "https://doc.toasttab.com/doc/devguide/apiOrderPrices.html", "note": "The orders API rounds all currency amounts to two decimal places: amounts are decimal currency units, not minor units." },
    { "title": "Webhook basics", "url": "https://doc.toasttab.com/doc/devguide/apiWebhookBasics.html" },
    { "title": "Webhook subscriptions", "url": "https://doc.toasttab.com/doc/devguide/apiSubscriptions.html" },
    { "title": "Standard API access webhook subscriptions", "url": "https://doc.toasttab.com/doc/devguide/devApiAccessWebhookSubscriptions.html" },
    { "title": "Message data schema", "url": "https://doc.toasttab.com/doc/devguide/apiMessageDataSchema.html" },
    { "title": "Webhook HTTP headers", "url": "https://doc.toasttab.com/doc/devguide/apiHttpHeaders.html" },
    { "title": "Message signing", "url": "https://doc.toasttab.com/doc/devguide/apiMessageSigning.html" },
    { "title": "Retry support", "url": "https://doc.toasttab.com/doc/devguide/apiRetrySupport.html" },
    { "title": "Menus webhook", "url": "https://doc.toasttab.com/doc/devguide/apiMenusWebhook.html" },
    { "title": "Orders webhook", "url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html" }
  ],
  "access": {
    "public_docs": true,
    "how_to_get_access": "Two routes. Partner integration: Toast's integrations team reviews the integration, issues clientId/clientSecret and the sandbox hostname, then the production hostname at go-live, and creates the webhook subscriptions. Standard API access: the restaurant creates credentials in Toast Web and, with the 8.4 Manage Integrations permission, its own webhook subscriptions for the Menus and Orders webhooks (among others). The second route fits SplitClub's per-venue hook URL.",
    "auth": "POST https://[toast-api-hostname]/authentication/v1/authentication/login with { clientId, clientSecret, userAccessType: \"TOAST_MACHINE_CLIENT\" } → accessToken (JWT). Send it as a Bearer token, with Toast-Restaurant-External-ID: <restaurant GUID> on every request.",
    "base_url": "https://[toast-api-hostname]: issued per environment (sandbox, production) by Toast; not a fixed public host in the docs read."
  },
  "splitclub": {
    "integration_provider": "toast",
    "recommended_path": "webhook",
    "note": "POST /v1/integrations with provider \"toast\" mints https://splitclubapp.com/api/v1/hooks/toast/sc_hook_…; the restaurant adds it as a webhook subscription (menus, order_updated) in Toast Web under standard API access. Toast's order_updated carries the full Order, so Checkout mapping needs no follow-up call; menus_updated carries only the restaurant GUID and publish time, so Offerings need GET /menus/v2/menus with the restaurant's credentials. Today partner-api neither verifies Toast-Signature nor reads Toast's eventType/guid for dedupe (see gaps)."
  },
  "objects": {
    "Offering": {
      "source": "GET /menus/v2/menus → menus[].menuGroups[].menuItems[] (MenuItem)",
      "kind": "dining",
      "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/",
      "note": "Most Toast menu items (a burger) are not group-bookable offerings. Map only the items the venue picks: set menus, bottle service, tasting menus, large-format items. kind is dining; table when the item is a table minimum or reservation package (venue's choice). The menus API returns the same item once under each menu it appears on, with that menu's resolved price: dedupe on guid and keep one tier per menu.",
      "fields": [
        { "to": "/external_id", "from": "menus[].menuGroups[].menuItems[].guid", "transform": "Use multiLocationId instead for a multi-location group that shares one item across restaurants.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/" },
        { "to": "/title", "from": "menus[].menuGroups[].menuItems[].name", "transform": "Skip items named \"Missing name\" (the menus API's placeholder for an empty name). Truncate to 120.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/" },
        { "to": "/description", "from": "menus[].menuGroups[].menuItems[].description", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/" },
        { "to": "/media/0/url", "from": "menus[].menuGroups[].menuItems[].images[]", "transform": "Each URL becomes one media entry (max 10); fall back to the older single image value.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/" },
        { "to": "/availability/timezone", "from": "restaurants general.timeZone", "transform": "IANA id from the restaurants API for the same restaurant GUID.", "source_url": "https://doc.toasttab.com/openapi/restaurants/tag/Data-definitions/schema/General/" },
        { "to": "/availability/recurring/0/rrule", "from": "menus[].availability.schedule[].days + timeRanges[]", "transform": "days (SUNDAY…SATURDAY) → FREQ=WEEKLY;BYDAY=SU,…; timeRanges[].start (HH:MM restaurant local) → BYHOUR/BYMINUTE and starts_at. These are the hours the menu is served, not seats: they bound when a party can be booked but give no capacity.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/Schedule/" },
        { "to": "/metadata/toast_menu_last_updated", "from": "lastUpdated", "transform": "Restaurant-level publish time, kept for staleness checks.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/Restaurant/" }
      ]
    },
    "PriceTier": {
      "source": "MenuItem.price, MenuItem.pricingStrategy and MenuItem.pricingRules.timeSpecificPricingRules[]",
      "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/",
      "note": "Sent inline as the Offering's /tiers. price is the item's fully resolved price on that menu. OPEN_PRICE items (price entered at sale) and SIZE_PRICE items (price lives in a Size modifier group) are not mapped automatically. TIME_SPECIFIC_PRICE items give one extra tier per schedule.",
      "fields": [
        { "to": "/tiers/0/external_id", "from": "menus[].guid + menuItems[].guid", "transform": "<menu guid>:<item guid>, since the same item may carry a different price on each menu.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/" },
        { "to": "/tiers/0/label", "from": "menus[].name", "transform": "e.g. \"Dinner\"; the item name when it appears on one menu only.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/Menu/" },
        { "to": "/tiers/0/amount", "from": "menus[].menuGroups[].menuItems[].price", "transform": "decimal dollars → integer cents (round half up; ×100). null price (open price) → not mapped.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/MenuItem/" },
        { "to": "/tiers/0/currency", "from": "restaurants general.currencyCode", "transform": "ISO 4217 → lower case. The menus API itself carries no currency.", "source_url": "https://doc.toasttab.com/openapi/restaurants/tag/Data-definitions/schema/General/" },
        { "to": "/tiers/0/unit", "from": "const:per_unit", "transform": "A Toast price is per item. per_person for a per-cover set menu is the venue's call; Toast does not say." },
        { "to": "/tiers/1/amount", "from": "pricingRules.timeSpecificPricingRules[].timeSpecificPrice", "transform": "decimal dollars → integer cents. Only when pricingStrategy is TIME_SPECIFIC_PRICE; basePrice becomes the always-on tier.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/TimeSpecificPrice/" },
        { "to": "/tiers/1/days", "from": "pricingRules.timeSpecificPricingRules[].schedule[].days", "transform": "MONDAY → mon … SUNDAY → sun.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/Schedule/" },
        { "to": "/tiers/1/time_window/from", "from": "pricingRules.timeSpecificPricingRules[].schedule[].timeRanges[].start", "transform": "Restaurant local HH:MM, copied. One tier per time range.", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/TimeRange/" },
        { "to": "/tiers/1/time_window/to", "from": "pricingRules.timeSpecificPricingRules[].schedule[].timeRanges[].end", "source_url": "https://doc.toasttab.com/openapi/menus/tag/Data-definitions/schema/TimeRange/" }
      ]
    },
    "Checkout": {
      "source": "Order (order_updated webhook details.order, or GET /orders/v2/orders/{orderGuid})",
      "source_url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Order/",
      "note": "This only makes sense for splitting an open check: a table's bill or a pre-ordered group menu that the venue sends to SplitClub before it is paid. Map orders whose checks are paymentStatus OPEN and that are not voided; PAID/CLOSED checks are already settled at the POS. Toast has no reservation object in these APIs, so a Checkout cannot be created ahead of the visit from Toast data alone.",
      "fields": [
        { "to": "/external_booking_id", "from": "order.guid", "transform": "toast_order_<guid>.", "source_url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Order/" },
        { "to": "/amount", "from": "order.checks[].totalAmount", "transform": "Sum over non-voided checks; decimal dollars → integer cents (round half up). totalAmount includes discounts, taxes and tip; use checks[].amount for the pre-tax, pre-tip figure.", "source_url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Check/" },
        { "to": "/currency", "from": "restaurants general.currencyCode", "transform": "Lower case. Orders carry no currency field.", "source_url": "https://doc.toasttab.com/openapi/restaurants/tag/Data-definitions/schema/General/" },
        { "to": "/party_size", "from": "order.numberOfGuests", "transform": "SplitClub needs 2–50; orders with fewer than 2 guests are not split.", "source_url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Order/" },
        { "to": "/offering_external_id", "from": "order.checks[].selections[].item.guid", "transform": "Set only when a selection is an item mapped as an Offering.", "source_url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Selection/" },
        { "to": "/pay_by", "from": "order.openedDate", "transform": "SplitClub rule: a short window (e.g. 2 h) after the order is sent. Toast dates look like 2024-04-01T15:09:30.223+0000; rewrite the offset as +00:00 and convert to the restaurant timeZone.", "source_url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Order/" },
        { "to": "/description", "from": "order.checks[].displayNumber", "transform": "e.g. \"Check 2, party of 6\"; max 200 characters.", "source_url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html" },
        { "to": "/metadata/toast_restaurant_guid", "from": "details.restaurantGuid", "source_url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html" }
      ]
    }
  },
  "webhooks": {
    "supported": true,
    "signature": {
      "header": "Toast-Signature",
      "algorithm": "HMAC-SHA256 with the subscription's webhook secret (unique per subscription and environment), base64-encoded (Toast's Java sample uses printBase64Binary)",
      "signed_payload": "The message body concatenated with the message timestamp (body + timestamp), UTF-8. The page does not say whether that timestamp is the body's timestamp field or a header; confirm against a real delivery before enforcing.",
      "source_url": "https://doc.toasttab.com/doc/devguide/apiMessageSigning.html",
      "unverified": true
    },
    "events": [
      {
        "event": "menus_updated",
        "maps_to": "re-sync Offerings and tiers: GET /menus/v2/menus for details.restaurantGuid, re-upsert the venue's chosen items",
        "source_url": "https://doc.toasttab.com/doc/devguide/apiMenusWebhook.html",
        "fields": [
          { "to": "/metadata/toast_restaurant_guid", "from": "details.restaurantGuid", "source_url": "https://doc.toasttab.com/doc/devguide/apiMenusWebhook.html" },
          { "to": "/metadata/toast_menu_last_updated", "from": "details.publishedDate", "transform": "UTC ISO 8601.", "source_url": "https://doc.toasttab.com/doc/devguide/apiMenusWebhook.html" }
        ]
      },
      {
        "event": "order_updated",
        "maps_to": "candidate Checkout when a check is OPEN and the venue flagged the order for SplitClub; voided → cancel the linked Checkout; paymentStatus PAID/CLOSED → paid at the POS, close it",
        "source_url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html",
        "fields": [
          { "to": "/external_booking_id", "from": "details.order.guid", "source_url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html" },
          { "to": "/amount", "from": "details.order.checks[].totalAmount", "transform": "decimal dollars → integer cents.", "source_url": "https://doc.toasttab.com/openapi/orders/tag/Data-definitions/schema/Check/" },
          { "to": "/party_size", "from": "details.order.numberOfGuests", "source_url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html" },
          { "to": "/metadata/toast_restaurant_guid", "from": "details.restaurantGuid", "source_url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html" }
        ]
      },
      {
        "event": "channel_order_updated",
        "maps_to": "treat as order_updated (the docs give it the same payload shape); ignore unless the venue routes group orders through a channel",
        "source_url": "https://doc.toasttab.com/doc/devguide/devOrdersWebhookRef.html"
      }
    ],
    "note": "Envelope for every category: { timestamp (UTC ISO 8601), eventCategory, eventType, guid (event GUID), details: {...} }, with headers Toast-Signature, Toast-Event-Type, Toast-Event-Category and Toast-Restaurant-External-ID. Orders webhook messages set eventCategory and eventType to order_updated (or channel_order_updated); menus use eventCategory menus. Order payloads can exceed 600 KB, which is over partner-api's 256 KB hook limit. Toast omits guest names, phone and address, and returns externalId as null in the webhook. It retries after 5 and then 10 minutes on a timeout, 404, 429 or 5xx, and never on other 4xx or 3xx."
  },
  "gaps": [
    "partner-api's hook reads the event type from type/event_type/event/topic and the id from id/event_id/eventId/data.id/external_id. Toast sends eventType and guid, so a Toast delivery is stored with neither, and Toast's retries are not deduplicated. Add eventType and guid to those lists.",
    "partner-api verifies only Square signatures. Toast-Signature is not checked, and the exact signed bytes (which timestamp) need confirming against a real delivery.",
    "Order webhook payloads can exceed 600 KB per Toast's docs, but partner-api refuses hook bodies over 256 KB (413), so large orders would be lost after Toast's two retries.",
    "No reservation, table-availability or capacity data in the Menus or Orders APIs: SplitClub Availability (slots, capacity) has no Toast source, only menu service hours.",
    "Menu prices carry no currency; it comes from the restaurants API (general.currencyCode). Amounts everywhere are decimal currency units → integer cents.",
    "OPEN_PRICE and SIZE_PRICE menu items cannot be turned into tiers without venue input.",
    "No Toast object maps to SplitClub Event or Offering.policy.",
    "Checkout fits only an open check sent to SplitClub before payment; return_url and the venue's choice of which orders to split have no Toast source.",
    "Partner-integration credentials, API hostnames and partner-level webhook subscriptions come from Toast's integrations team after review; only standard API access lets a restaurant set this up itself."
  ]
}
