{
  "$schema": "./_mapping.schema.json",
  "provider": "tock",
  "name": "Tock (Exploretock)",
  "vertical": "restaurants",
  "verified_on": "2026-10-13",
  "docs": [
    {
      "title": "Tock Reservation Data Model (v2.7)",
      "url": "https://api.exploretock.com/docs/latest/reservation.html",
      "note": "Public. The field-level contract for every reservation Tock sends by API or webhook. Rendered from reservation.swagger.json."
    },
    {
      "title": "Tock Reservation Model swagger (definitions only, no paths)",
      "url": "https://api.exploretock.com/docs/latest/reservation.swagger.json",
      "note": "Public. Types and descriptions quoted below come from here. It defines objects only: there are no endpoint paths in it."
    },
    {
      "title": "Tock Guest Data Model",
      "url": "https://api.exploretock.com/docs/latest/guest_profile.html",
      "note": "Public. Guest profiles; SplitClub does not need them."
    },
    {
      "title": "Tock Help Center: API FAQ (updated 2026-09-22)",
      "url": "https://tock.zendesk.com/hc/en-us/articles/25447494175508-API-FAQ",
      "note": "Lists the four APIs/webhooks, says reservations cannot be created or cancelled by API, there is no availability API, payloads are JSON, and webhook auth is a header the business supplies. The page returns 403 to bots; read through https://tock.zendesk.com/api/v2/help_center/en-us/articles/25447494175508.json."
    }
  ],
  "access": {
    "public_docs": true,
    "how_to_get_access": "Only on eligible Tock plans (Control Panel → Plan & Billing; ask hospitality@resy.com). An Account Owner on the venue's Tock Dashboard Team page emails api-integration@resy.com to request an API key, or to request a webhook (naming which webhooks: reservations, walk-ins, guest profile; the endpoint URL for each; and any authorization header as headername:headervalue).",
    "auth": "API: an API key issued by email. How it is sent (header name) is not in the public docs. Webhook: Tock sends the static header the business supplied when requesting it.",
    "unverified": true
  },
  "splitclub": {
    "integration_provider": "tock",
    "recommended_path": "webhook",
    "note": "The venue's Account Owner asks api-integration@resy.com for the Real-time Reservation Webhook pointed at SplitClub's inbound hook, with a per-venue secret header SplitClub issues (e.g. x-splitclub-hook-token:<secret>, no spaces or special characters). Tock does not sign payloads, so SplitClub must add a constant-time comparison of that static header (today the hook only verifies Square signatures). Tock cannot create or cancel reservations by API, so the flow is booking-first: the guest books on Tock, the webhook arrives, SplitClub creates the Checkout for the reservation's total or amount due. There is no availability feed, so Offerings and Availability cannot be synced from Tock."
  },
  "objects": {
    "Offering": {
      "source": "Reservation.experience (PurchasedExperience) inside the reservation webhook / Data Exports payload. There is no endpoint that lists a venue's experiences.",
      "source_url": "https://api.exploretock.com/docs/latest/reservation.html",
      "note": "Offerings can only be learned from reservations as they arrive. Pointers are rooted at the SplitClub Offering (tiers and availability are inline).",
      "fields": [
        { "to": "/external_id", "from": "experience.id", "transform": "string (uint64) → keep as string, prefix tock_exp_", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/title", "from": "experience.name", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/kind", "from": "experience.variety", "transform": "EVENT → ticketed_event; RESERVATION, PREPAID, DEPOSIT, TABLESIDE → dining; PICKUP, DELIVERY → do not import. The mapping is SplitClub's choice; the enum is Tock's.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/booking_url", "from": "business.domainName", "transform": "https://exploretock.com/{domainName}/ (documented as the top-level URL form)", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/metadata/pos_sku", "from": "experience.posSku", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/availability/timezone", "from": "business.timeZone", "transform": "already IANA (e.g. America/Chicago)", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" }
      ]
    },
    "PriceTier": {
      "source": "Reservation.experience.amountCents plus reservation-level fee/tax/gratuity fields",
      "source_url": "https://api.exploretock.com/docs/latest/reservation.html",
      "note": "This is the price of one booked reservation, not a published price list. Tock does not say whether amountCents is per person or per party, so the unit is unverified.",
      "fields": [
        { "to": "/tiers/0/amount", "from": "experience.amountCents", "transform": "string (uint64), already minor units → parseInt. Excludes add-ons/options.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/tiers/0/currency", "from": "business.currencyCode", "transform": "ISO 4217 upper case → lower case", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/tiers/0/label", "from": "experience.name", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/tiers/0/unit", "from": "const:per_group", "transform": "Treat the experience amount on a reservation as the whole party's price. Not stated in the docs.", "unverified": true },
        { "to": "/tiers/0/fees/0/percent", "from": "serviceChargeRate", "transform": "type service, included false. float; the docs do not say whether 0.2 or 20 means 20%.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json", "unverified": true },
        { "to": "/tiers/0/fees/1/percent", "from": "taxRate", "transform": "type tax, included false. Present only when one rate covers the subtotal; same scale question as serviceChargeRate.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json", "unverified": true },
        { "to": "/tiers/0/fees/2/percent", "from": "selectedGratuityRate", "transform": "type gratuity, included false; same scale question.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json", "unverified": true },
        { "to": "/tiers/0/deposit/amount", "from": "netAmountPaidCents when experience.variety = DEPOSIT", "transform": "integer cents as is. Tock does not define a deposit amount field; the amount already paid on a DEPOSIT experience is the best documented proxy.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json", "unverified": true }
      ]
    },
    "Availability": {
      "source": "none",
      "source_url": "https://tock.zendesk.com/hc/en-us/articles/25447494175508-API-FAQ",
      "note": "API FAQ: 'Is it possible to pull available bookings for a restaurant (unbooked reservations)? Not at this time.' Availability must be entered in SplitClub by hand or skipped (booking-first flow).",
      "fields": []
    },
    "Checkout": {
      "source": "Real-time Reservation Webhook payload (the Reservation object)",
      "source_url": "https://api.exploretock.com/docs/latest/reservation.html",
      "fields": [
        { "to": "/external_booking_id", "from": "id", "transform": "string (uint64). Stable across edits; a transferred reservation gets a new id.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/party_size", "from": "partySize", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/amount", "from": "amountDueCents (or totalPriceCents when nothing is paid yet)", "transform": "integer, already minor units. amountDueCents = totalPriceCents − netAmountPaidCents; split only what is still owed.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/currency", "from": "business.currencyCode", "transform": "upper → lower case", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/offering_external_id", "from": "experience.id", "transform": "same prefix as Offering.external_id", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/description", "from": "experience.name + dateTime", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/pay_by", "from": "serviceDateTimestamp", "transform": "string of epoch milliseconds → instant → ISO 8601 with offset in business.timeZone, minus the venue's cutoff. Do not use dateTime: it is only local HH:MM with no date and no offset.", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/metadata/confirmation_code", "from": "confirmationCode", "transform": "8 characters; absent for walk-ins", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/metadata/sequence_id", "from": "sequenceId", "transform": "integer → string; ignore a webhook whose sequenceId is not higher than the last one stored", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
        { "to": "/return_url", "from": "const:<venue's registered origin>", "transform": "not from Tock; SplitClub config per venue" }
      ]
    }
  },
  "webhooks": {
    "supported": true,
    "signature": {
      "header": "Whatever header the business supplies when requesting the webhook (format headername:headervalue)",
      "algorithm": "none: a static shared value, compared verbatim. No HMAC, no timestamp.",
      "signed_payload": "nothing is signed",
      "source_url": "https://tock.zendesk.com/hc/en-us/articles/25447494175508-API-FAQ"
    },
    "events": [
      {
        "event": "Real-time Reservation Webhook",
        "maps_to": "upsert Checkout by external_booking_id = id; if isCancelled = true or partyState = CANCELLED, cancel the open Checkout; ignore when sequenceId is not newer",
        "source_url": "https://tock.zendesk.com/hc/en-us/articles/25447494175508-API-FAQ",
        "fields": [
          { "to": "/external_booking_id", "from": "id", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" },
          { "to": "/status", "from": "isCancelled / partyState", "transform": "isCancelled true or partyState CANCELLED → canceled; other partyState values do not change the SplitClub status", "source_url": "https://api.exploretock.com/docs/latest/reservation.swagger.json" }
        ]
      },
      {
        "event": "walk-ins (requested alongside reservations)",
        "maps_to": "ignore (walk-ins have walkinId, no experience, no confirmationCode)",
        "source_url": "https://tock.zendesk.com/hc/en-us/articles/25447494175508-API-FAQ"
      },
      {
        "event": "Real-time Guest Profile Webhook",
        "maps_to": "ignore",
        "source_url": "https://tock.zendesk.com/hc/en-us/articles/25447494175508-API-FAQ"
      }
    ],
    "note": "Tock names webhooks by subscription, not by event type: every reservation change (create, edit, cancel) is one POST of JSON for all locations in the business group. The docs do not show whether the body is the bare Reservation or wrapped like GetReservationResponse {reservation: …}; accept both. Retries and timeouts are not documented."
  },
  "gaps": [
    "No availability or experience-catalogue API: Offering, PriceTier and Availability cannot be synced; only learned from booked reservations (API FAQ).",
    "Reservations cannot be created or cancelled by API, so SplitClub cannot hold or release a Tock booking when a Checkout expires; the venue cancels in the Tock Dashboard.",
    "Webhook payload envelope (bare Reservation vs {reservation: …}), retry policy and delivery timeouts are not documented.",
    "Webhook has no signature: only a static header the business chooses. SplitClub's hook needs a per-venue token check for Tock.",
    "API key transport (header name), base URL and Data Exports endpoint paths are not in the public docs; the swagger has definitions but no paths.",
    "Whether experience.amountCents is per person or per party, and the scale of taxRate / serviceChargeRate / selectedGratuityRate (fraction vs percent), are not stated.",
    "No minimum spend concept in the reservation model."
  ]
}
