{
  "openapi": "3.1.0",
  "info": {
    "title": "Parlor API",
    "version": "1.0.0",
    "summary": "Reservations, guests, email subscribers, ticket orders, waitlist and private-event leads for one Parlor venue.",
    "description": "Read and write one venue's Parlor data with an API key, and receive signed webhooks when things happen. Available by request to venues on the Pro and Group plans. Human-readable guide: https://parlorhq.app/developers. Terms: https://parlorhq.app/terms.html#api.",
    "termsOfService": "https://parlorhq.app/terms.html#api",
    "contact": {
      "name": "Parlor",
      "email": "parlorhq@mizkathis.net",
      "url": "https://parlorhq.app/developers"
    }
  },
  "servers": [
    {
      "url": "https://parlorhq.app",
      "description": "Production. There is no sandbox; see the guide for testing safely."
    }
  ],
  "security": [
    {
      "ParlorKey": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Checking a key."
    },
    {
      "name": "Reservations"
    },
    {
      "name": "Guests"
    },
    {
      "name": "Subscribers"
    },
    {
      "name": "Ticket orders"
    },
    {
      "name": "Waitlist"
    },
    {
      "name": "Leads"
    },
    {
      "name": "Tasks"
    },
    {
      "name": "Campaigns",
      "description": "Write a marketing email, build its list, test it and send it -- through the same checks as the dashboard. API keys only; a Zapier key is refused."
    },
    {
      "name": "Webhooks",
      "description": "Your own endpoints, for API keys with Read and write access."
    },
    {
      "name": "Zapier",
      "description": "Used by Parlor's Zapier app. Not for your own server."
    }
  ],
  "paths": {
    "/api/v1/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Check a key",
        "description": "Who this key belongs to, and what kind of key it is. Use it to test a connection.",
        "responses": {
          "200": {
            "description": "The key's venue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "examples": {
                  "me": {
                    "summary": "A working key",
                    "value": {
                      "venue": {
                        "slug": "cotillion",
                        "name": "Cotillion Cafe",
                        "timezone": "America/New_York"
                      },
                      "plan": "pro",
                      "key": {
                        "kind": "api",
                        "scope": "read"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "The key's venue could not be found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/reservations": {
      "get": {
        "operationId": "listReservations",
        "tags": [
          "Reservations"
        ],
        "summary": "List reservations",
        "description": "Newest first, by when each was created. Up to 100 per call (default 25). In a list, a reservation's `updatedAt` reflects arrival, seating and cancellation; edits to its time or size arrive through webhooks.",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/since"
          }
        ],
        "responses": {
          "200": {
            "description": "A JSON array of reservations, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Reservation"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "Newest reservations",
                    "value": [
                      {
                        "id": 48213,
                        "guestName": "Jordan Ellis",
                        "email": "jordan@example.com",
                        "phone": "(555) 010-4477",
                        "partySize": 4,
                        "date": "2026-10-17",
                        "time": "19:30",
                        "durationMinutes": 90,
                        "status": "Confirmed",
                        "source": "public",
                        "bookingType": "Dinner",
                        "notes": "Anniversary -- window table if possible",
                        "floorStage": null,
                        "createdAt": "2026-10-02T14:05:11.482Z",
                        "checkedInAt": null,
                        "cancelledAt": null,
                        "cancelledBy": null,
                        "updatedAt": "2026-10-02T14:05:11.482Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` or `since` could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "badSince": {
                    "summary": "since unreadable",
                    "value": {
                      "error": "since must be a date and time, for example 2026-09-29T18:00:00-04:00."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "operationId": "createReservation",
        "tags": [
          "Reservations"
        ],
        "summary": "Request a reservation",
        "description": "Always made as a REQUEST (status Requested, no table) for staff to confirm. It is checked first against the venue's hours, closed days, blocks, same-day cut-off, party limits, notice, how far ahead, last arrival, set seating times and pacing, and refused with a reason if any of them says no. No confirmation email is sent to the guest, because nothing is confirmed yet.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReservationInput"
              },
              "examples": {
                "localTime": {
                  "summary": "Date and time in the venue's local time",
                  "value": {
                    "guestName": "Jordan Ellis",
                    "email": "jordan@example.com",
                    "phone": "(555) 010-4477",
                    "partySize": 4,
                    "date": "2026-10-17",
                    "time": "7:30 PM",
                    "notes": "From the website enquiry form"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The request was made.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservationResult"
                },
                "examples": {
                  "created": {
                    "summary": "A reservation request",
                    "value": {
                      "reservation": {
                        "id": 48260,
                        "guestName": "Jordan Ellis",
                        "email": "jordan@example.com",
                        "phone": "(555) 010-4477",
                        "partySize": 4,
                        "date": "2026-10-17",
                        "time": "19:30",
                        "durationMinutes": 90,
                        "status": "Requested",
                        "source": "zapier",
                        "bookingType": null,
                        "notes": "From the website enquiry form",
                        "floorStage": null,
                        "createdAt": "2026-10-03T16:40:02.114Z",
                        "checkedInAt": null,
                        "cancelledAt": null,
                        "cancelledBy": null,
                        "updatedAt": "2026-10-03T16:40:02.114Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the body is missing or unreadable, or the body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noName": {
                    "summary": "No name",
                    "value": {
                      "error": "guestName is required."
                    }
                  },
                  "tooLarge": {
                    "summary": "Over 64 KB",
                    "value": {
                      "error": "That request is too large."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "409": {
            "description": "The venue can't take that booking (`reason` says why), or an Idempotency-Key clash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "closed": {
                    "summary": "Closed that day",
                    "value": {
                      "error": "The venue is closed that day (Closed for Thanksgiving).",
                      "reason": "closed"
                    }
                  },
                  "full": {
                    "summary": "Fully booked",
                    "value": {
                      "error": "The venue is fully booked at that time -- it has reached the number of guests it takes in that slot.",
                      "reason": "pacing-covers-full"
                    }
                  },
                  "past": {
                    "summary": "In the past",
                    "value": {
                      "error": "That time has already passed at the venue (it is 2026-10-17 20:05 there now).",
                      "reason": "in-the-past"
                    }
                  },
                  "idemDifferent": {
                    "summary": "Same key, different request",
                    "value": {
                      "error": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                    }
                  },
                  "idemProcessing": {
                    "summary": "The first try is still running",
                    "value": {
                      "error": "A request with this Idempotency-Key is still being processed. Try again in a moment."
                    }
                  },
                  "idemOutcomeUnknown": {
                    "summary": "An earlier try never finished",
                    "value": {
                      "error": "An earlier try of this step didn't finish, so Parlor can't tell whether it went through. Check Parlor before running it again -- if it isn't there, run the step with changed details or wait a day.",
                      "reason": "outcome-unknown"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/reservations/find": {
      "get": {
        "operationId": "findReservations",
        "tags": [
          "Reservations"
        ],
        "summary": "Find a guest's reservations",
        "description": "By the guest's email, or a phone number with at least 7 digits. Upcoming first (soonest first), then past (most recent first). At most 10. An empty array means none found.",
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Guest email."
          },
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Guest phone, at least 7 digits."
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Only this venue-local date."
          }
        ],
        "responses": {
          "200": {
            "description": "Matching reservations, possibly none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Reservation"
                  },
                  "maxItems": 10
                },
                "examples": {
                  "found": {
                    "summary": "One upcoming booking",
                    "value": [
                      {
                        "id": 48213,
                        "guestName": "Jordan Ellis",
                        "email": "jordan@example.com",
                        "phone": "(555) 010-4477",
                        "partySize": 4,
                        "date": "2026-10-17",
                        "time": "19:30",
                        "durationMinutes": 90,
                        "status": "Confirmed",
                        "source": "public",
                        "bookingType": "Dinner",
                        "notes": "Anniversary -- window table if possible",
                        "floorStage": null,
                        "createdAt": "2026-10-02T14:05:11.482Z",
                        "checkedInAt": null,
                        "cancelledAt": null,
                        "cancelledBy": null,
                        "updatedAt": "2026-10-02T14:05:11.482Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Neither an email nor a usable phone number was given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "nothing": {
                    "summary": "Nothing to search by",
                    "value": {
                      "error": "Search by the guest's email, or a phone number with at least 7 digits."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/guests": {
      "get": {
        "operationId": "listGuests",
        "tags": [
          "Guests"
        ],
        "summary": "List guests",
        "description": "Newest first, by when each was created. Up to 100 per call (default 25).",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/since"
          }
        ],
        "responses": {
          "200": {
            "description": "A JSON array of guests, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Guest"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "Newest guests",
                    "value": [
                      {
                        "id": 1760,
                        "name": "Jordan Ellis",
                        "email": "jordan@example.com",
                        "phone": "(555) 010-4477",
                        "seatingPreference": "Booth",
                        "referralSource": "Website",
                        "visitCount": 6,
                        "noShowCount": 0,
                        "cancellationCount": 1,
                        "lastVisitDate": "2026-09-12",
                        "createdAt": "2025-11-03T17:22:40.000Z",
                        "updatedAt": "2026-09-12T23:10:02.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` or `since` could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "badSince": {
                    "summary": "since unreadable",
                    "value": {
                      "error": "since must be a date and time, for example 2026-09-29T18:00:00-04:00."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "operationId": "upsertGuest",
        "tags": [
          "Guests"
        ],
        "summary": "Create or update a guest",
        "description": "Matched the way Parlor matches everywhere: exact email first, then phone. A matched guest's name, email and phone are only FILLED IN where empty, never replaced; a different value is written into the guest's notes for staff and returned in `conflicting`. Notes are added to, never replaced.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GuestInput"
              },
              "examples": {
                "guest": {
                  "summary": "A guest from a form",
                  "value": {
                    "name": "Jordan Ellis",
                    "email": "jordan@example.com",
                    "phone": "(555) 010-4477"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An existing guest was matched.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestResult"
                },
                "examples": {
                  "matched": {
                    "summary": "Matched, with a different phone kept aside",
                    "value": {
                      "created": false,
                      "guest": {
                        "id": 1760,
                        "name": "Jordan Ellis",
                        "email": "jordan@example.com",
                        "phone": "(555) 010-4477",
                        "seatingPreference": "Booth",
                        "referralSource": "Website",
                        "visitCount": 6,
                        "noShowCount": 0,
                        "cancellationCount": 1,
                        "lastVisitDate": "2026-09-12",
                        "createdAt": "2025-11-03T17:22:40.000Z",
                        "updatedAt": "2026-09-12T23:10:02.000Z"
                      },
                      "conflicting": {
                        "phone": "(555) 010-9999"
                      }
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "A new guest was made.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestResult"
                },
                "examples": {
                  "created": {
                    "summary": "New guest",
                    "value": {
                      "created": true,
                      "guest": {
                        "id": 1760,
                        "name": "Jordan Ellis",
                        "email": "jordan@example.com",
                        "phone": "(555) 010-4477",
                        "seatingPreference": "Booth",
                        "referralSource": "Website",
                        "visitCount": 6,
                        "noShowCount": 0,
                        "cancellationCount": 1,
                        "lastVisitDate": "2026-09-12",
                        "createdAt": "2025-11-03T17:22:40.000Z",
                        "updatedAt": "2026-09-12T23:10:02.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No email and no usable phone, or the body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noContact": {
                    "summary": "No way to tell guests apart",
                    "value": {
                      "error": "A guest needs an email address or a phone number (at least 7 digits), so Parlor can tell them apart."
                    }
                  },
                  "tooLarge": {
                    "summary": "Over 64 KB",
                    "value": {
                      "error": "That request is too large."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/guests/find": {
      "get": {
        "operationId": "findGuests",
        "tags": [
          "Guests"
        ],
        "summary": "Find a guest",
        "description": "By email, or a phone number with at least 7 digits. Answers an array with at most one guest; empty means not found.",
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Email address."
          },
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Phone, at least 7 digits."
          }
        ],
        "responses": {
          "200": {
            "description": "The guest, or an empty array.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Guest"
                  },
                  "maxItems": 1
                },
                "examples": {
                  "found": {
                    "summary": "Found",
                    "value": [
                      {
                        "id": 1760,
                        "name": "Jordan Ellis",
                        "email": "jordan@example.com",
                        "phone": "(555) 010-4477",
                        "seatingPreference": "Booth",
                        "referralSource": "Website",
                        "visitCount": 6,
                        "noShowCount": 0,
                        "cancellationCount": 1,
                        "lastVisitDate": "2026-09-12",
                        "createdAt": "2025-11-03T17:22:40.000Z",
                        "updatedAt": "2026-09-12T23:10:02.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Nothing to search by.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "nothing": {
                    "summary": "Nothing to search by",
                    "value": {
                      "error": "Search by email, or by a phone number with at least 7 digits."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/subscribers": {
      "get": {
        "operationId": "listSubscribers",
        "tags": [
          "Subscribers"
        ],
        "summary": "List subscribers",
        "description": "Newest first, by when each was created. Up to 100 per call (default 25).",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/since"
          }
        ],
        "responses": {
          "200": {
            "description": "A JSON array of subscribers, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Subscriber"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "Newest subscribers",
                    "value": [
                      {
                        "id": 8812,
                        "email": "jordan@example.com",
                        "firstName": "Jordan",
                        "status": "subscribed",
                        "source": "signup_form",
                        "birthMonth": 4,
                        "birthDay": 22,
                        "consentAt": "2026-03-14T18:02:55.000Z",
                        "confirmedAt": "2026-03-14T18:04:10.000Z",
                        "unsubscribedAt": null,
                        "createdAt": "2026-03-14T18:02:55.000Z",
                        "updatedAt": "2026-03-14T18:04:10.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` or `since` could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "badSince": {
                    "summary": "since unreadable",
                    "value": {
                      "error": "since must be a date and time, for example 2026-09-29T18:00:00-04:00."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "operationId": "addSubscriber",
        "tags": [
          "Subscribers"
        ],
        "summary": "Add an email subscriber",
        "description": "Needs proof of consent. Someone who unsubscribed is never added back, an address on the do-not-email list is never added, and someone waiting to confirm stays waiting. Those outcomes are a 200 with `added: false` and a sentence, not an error.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriberInput"
              },
              "examples": {
                "subscriber": {
                  "summary": "From a website form",
                  "value": {
                    "email": "jordan@example.com",
                    "firstName": "Jordan",
                    "consent": {
                      "source": "Website contact form",
                      "statement": "Yes, send me news and offers from Cotillion Cafe by email.",
                      "at": "2026-10-03T12:14:00-04:00",
                      "pageUrl": "https://example.com/contact"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Nothing changed, and why.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriberResult"
                },
                "examples": {
                  "unsubscribed": {
                    "summary": "They unsubscribed earlier",
                    "value": {
                      "added": false,
                      "status": "unsubscribed",
                      "message": "Not added: this person unsubscribed from the venue's emails. Parlor never adds them back on someone else's say-so -- they can rejoin themselves through the venue's signup form.",
                      "subscriber": {
                        "id": 8812,
                        "email": "jordan@example.com",
                        "firstName": "Jordan",
                        "status": "unsubscribed",
                        "source": "signup_form",
                        "birthMonth": 4,
                        "birthDay": 22,
                        "consentAt": "2026-03-14T18:02:55.000Z",
                        "confirmedAt": "2026-03-14T18:04:10.000Z",
                        "unsubscribedAt": "2026-06-01T10:00:00.000Z",
                        "createdAt": "2026-03-14T18:02:55.000Z",
                        "updatedAt": "2026-06-01T10:00:00.000Z"
                      }
                    }
                  },
                  "suppressed": {
                    "summary": "On the do-not-email list",
                    "value": {
                      "added": false,
                      "status": "suppressed",
                      "message": "Not added: this address is on the venue's do-not-email list (it bounced, was reported as spam, or was removed by staff).",
                      "subscriber": null
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Added to the list.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriberResult"
                },
                "examples": {
                  "added": {
                    "summary": "Added",
                    "value": {
                      "added": true,
                      "status": "subscribed",
                      "message": "Added to the venue's email list.",
                      "subscriber": {
                        "id": 8812,
                        "email": "jordan@example.com",
                        "firstName": "Jordan",
                        "status": "subscribed",
                        "source": "signup_form",
                        "birthMonth": 4,
                        "birthDay": 22,
                        "consentAt": "2026-03-14T18:02:55.000Z",
                        "confirmedAt": "2026-03-14T18:04:10.000Z",
                        "unsubscribedAt": null,
                        "createdAt": "2026-03-14T18:02:55.000Z",
                        "updatedAt": "2026-03-14T18:04:10.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The address or the consent evidence is missing or wrong, or the body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noConsent": {
                    "summary": "No consent",
                    "value": {
                      "error": "consent.source is missing -- say where they agreed, e.g. \"Website contact form\"."
                    }
                  },
                  "tooLarge": {
                    "summary": "Over 64 KB",
                    "value": {
                      "error": "That request is too large."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/ticket-orders": {
      "get": {
        "operationId": "listTicketOrders",
        "tags": [
          "Ticket orders"
        ],
        "summary": "List ticket orders",
        "description": "Newest first, by when each was created. Up to 100 per call (default 25). Abandoned and in-flight checkouts are left out.",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/since"
          }
        ],
        "responses": {
          "200": {
            "description": "A JSON array of ticket orders, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TicketOrder"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "Newest ticket orders",
                    "value": [
                      {
                        "id": 311,
                        "orderNumber": "MR-7K4Q2",
                        "status": "paid",
                        "channel": "public",
                        "paymentMethod": "square_link",
                        "buyerFirstName": "Jordan",
                        "buyerLastName": "Ellis",
                        "buyerEmail": "jordan@example.com",
                        "buyerPhone": "(555) 010-4477",
                        "eventName": "Murder Mystery Supper",
                        "occurrenceTitle": "Friday seating",
                        "eventDate": "2026-10-30",
                        "eventStartTime": "18:30",
                        "subtotalCents": 9000,
                        "discountCents": 0,
                        "feeCents": 350,
                        "taxCents": 630,
                        "totalCents": 9980,
                        "refundedCents": 0,
                        "total": "99.80",
                        "refunded": "0.00",
                        "promoCode": null,
                        "marketingOptIn": true,
                        "paidAt": "2026-10-04T20:31:09.000Z",
                        "createdAt": "2026-10-04T20:29:47.000Z",
                        "updatedAt": "2026-10-04T20:31:09.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` or `since` could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "badSince": {
                    "summary": "since unreadable",
                    "value": {
                      "error": "since must be a date and time, for example 2026-09-29T18:00:00-04:00."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/waitlist": {
      "get": {
        "operationId": "listWaitlist",
        "tags": [
          "Waitlist"
        ],
        "summary": "List waitlist entries",
        "description": "Newest first, by when each was created. Up to 100 per call (default 25).",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/since"
          }
        ],
        "responses": {
          "200": {
            "description": "A JSON array of waitlist entries, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WaitlistEntry"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "Newest waitlist entries",
                    "value": [
                      {
                        "id": 902,
                        "guestName": "Sam Okafor",
                        "email": null,
                        "phone": "(555) 010-9310",
                        "partySize": 2,
                        "date": "2026-10-04",
                        "requestedTime": null,
                        "windowStart": null,
                        "windowEnd": null,
                        "status": "waiting",
                        "source": "staff",
                        "notes": "High chair",
                        "quotedMinutes": 25,
                        "quotedAt": "2026-10-04T23:02:00.000Z",
                        "pagedAt": null,
                        "seatedAt": null,
                        "leftAt": null,
                        "createdAt": "2026-10-04T23:02:00.000Z",
                        "updatedAt": "2026-10-04T23:02:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` or `since` could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "badSince": {
                    "summary": "since unreadable",
                    "value": {
                      "error": "since must be a date and time, for example 2026-09-29T18:00:00-04:00."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/leads": {
      "get": {
        "operationId": "listLeads",
        "tags": [
          "Leads"
        ],
        "summary": "List private-event leads",
        "description": "Newest first, by when each was created. Up to 100 per call (default 25).",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/since"
          }
        ],
        "responses": {
          "200": {
            "description": "A JSON array of private-event leads, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Lead"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "Newest private-event leads",
                    "value": [
                      {
                        "id": 1790953960118,
                        "contactName": "Jordan Ellis",
                        "contactEmail": "jordan@example.com",
                        "contactPhone": "(555) 010-4477",
                        "eventName": "Rehearsal dinner",
                        "occasion": "Wedding",
                        "date": "2026-12-12",
                        "guestCount": "40",
                        "budgetPerPerson": "75",
                        "diningStyle": "Plated",
                        "dietaryNeeds": "Two vegetarian",
                        "stage": "Inquiry",
                        "priority": "Medium",
                        "notes": "Would like the garden room",
                        "submittedVia": "zapier",
                        "submittedAt": "2026-10-02T15:12:40.118Z",
                        "createdAt": "2026-10-02T15:12:40.118Z",
                        "updatedAt": "2026-10-02T15:12:40.118Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` or `since` could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "badSince": {
                    "summary": "since unreadable",
                    "value": {
                      "error": "since must be a date and time, for example 2026-09-29T18:00:00-04:00."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "operationId": "createLead",
        "tags": [
          "Leads"
        ],
        "summary": "Add a private-event enquiry",
        "description": "Lands on the venue's Pipeline at stage Inquiry. The contact is matched to an existing guest first, the same way POST /api/v1/guests matches.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeadInput"
              },
              "examples": {
                "lead": {
                  "summary": "From an events form",
                  "value": {
                    "name": "Jordan Ellis",
                    "email": "jordan@example.com",
                    "eventName": "Rehearsal dinner",
                    "occasion": "Wedding",
                    "eventDate": "2026-12-12",
                    "guestCount": "40",
                    "notes": "Would like the garden room"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The enquiry was made.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeadResult"
                },
                "examples": {
                  "created": {
                    "summary": "New enquiry",
                    "value": {
                      "lead": {
                        "id": 1790953960118,
                        "contactName": "Jordan Ellis",
                        "contactEmail": "jordan@example.com",
                        "contactPhone": "(555) 010-4477",
                        "eventName": "Rehearsal dinner",
                        "occasion": "Wedding",
                        "date": "2026-12-12",
                        "guestCount": "40",
                        "budgetPerPerson": "75",
                        "diningStyle": "Plated",
                        "dietaryNeeds": "Two vegetarian",
                        "stage": "Inquiry",
                        "priority": "Medium",
                        "notes": "Would like the garden room",
                        "submittedVia": "zapier",
                        "submittedAt": "2026-10-02T15:12:40.118Z",
                        "createdAt": "2026-10-02T15:12:40.118Z",
                        "updatedAt": "2026-10-02T15:12:40.118Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something is missing, or the body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noName": {
                    "summary": "No name",
                    "value": {
                      "error": "name is required -- who is the inquiry from?"
                    }
                  },
                  "tooLarge": {
                    "summary": "Over 64 KB",
                    "value": {
                      "error": "That request is too large."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflictOnce"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/tasks": {
      "post": {
        "operationId": "createTask",
        "tags": [
          "Tasks"
        ],
        "summary": "Add a task",
        "description": "A line on the venue's Tasks tab. Not protected against repeats by default: send an Idempotency-Key if a retry must not add it twice.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskInput"
              },
              "examples": {
                "task": {
                  "summary": "A follow-up",
                  "value": {
                    "title": "Call Jordan about the rehearsal dinner",
                    "due": "2026-10-05",
                    "priority": "High",
                    "category": "Follow-Up",
                    "client": "Jordan Ellis"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The task was added.",
            "headers": {
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskResult"
                },
                "examples": {
                  "created": {
                    "summary": "New task",
                    "value": {
                      "task": {
                        "id": 1791032400000,
                        "title": "Call Jordan about the rehearsal dinner",
                        "due": "2026-10-05",
                        "priority": "High",
                        "category": "Follow-Up",
                        "client": "Jordan Ellis",
                        "done": false
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No title, or an unreadable date, or the body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noTitle": {
                    "summary": "No title",
                    "value": {
                      "error": "title is required."
                    }
                  },
                  "tooLarge": {
                    "summary": "Over 64 KB",
                    "value": {
                      "error": "That request is too large."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflictOnce"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "tags": [
          "Webhooks"
        ],
        "summary": "List your webhook endpoints",
        "description": "Newest first, with how the last delivery went. Needs an API key with Read and write access. Secrets are never shown again.",
        "responses": {
          "200": {
            "description": "Your endpoints, possibly none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Webhook"
                  }
                },
                "examples": {
                  "list": {
                    "summary": "One endpoint",
                    "value": [
                      {
                        "id": "3f1c9a52-8d0e-4b7a-9c61-2e5d7a0b4f18",
                        "url": "https://hooks.example.com/parlor",
                        "events": [
                          "reservation.cancelled",
                          "reservation.created"
                        ],
                        "status": "active",
                        "disabledReason": null,
                        "failureCount": 0,
                        "lastDeliveryAt": "2026-10-04T20:31:10.000Z",
                        "lastDeliveryStatus": "ok 200",
                        "createdAt": "2026-10-01T15:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Add a webhook endpoint",
        "description": "Parlor will POST each chosen event to `url`, signed. The `secret` is in this reply and NEVER again. https only, standard port, a public domain name. Redirects are not followed. At most 10 endpoints per venue, and one per address: to change an endpoint's events, delete it and add it again. An endpoint belongs to the key that made it, and stops if that key is revoked.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookInput"
              },
              "examples": {
                "webhook": {
                  "summary": "Bookings to your server",
                  "value": {
                    "url": "https://hooks.example.com/parlor",
                    "events": [
                      "reservation.cancelled",
                      "reservation.created"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added. Store the secret now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreated"
                },
                "examples": {
                  "created": {
                    "summary": "New endpoint",
                    "value": {
                      "id": "3f1c9a52-8d0e-4b7a-9c61-2e5d7a0b4f18",
                      "url": "https://hooks.example.com/parlor",
                      "events": [
                        "reservation.cancelled",
                        "reservation.created"
                      ],
                      "status": "active",
                      "createdAt": "2026-10-01T15:00:00.000Z",
                      "secret": "whsec_4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The address or the events are not acceptable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rawIp": {
                    "summary": "An IP address",
                    "value": {
                      "error": "Parlor can't send to that address: it is a raw IP address -- use your server's domain name, for example https://hooks.example.com/parlor."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "409": {
            "description": "The venue already has an endpoint for that address, or already has 10.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The body is over 16 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "tooLarge": {
                    "summary": "Over 16 KB",
                    "value": {
                      "error": "That request is too large."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          },
          "description": "The endpoint's id."
        }
      ],
      "delete": {
        "operationId": "deleteWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Remove a webhook endpoint",
        "description": "Stops deliveries to it at once.",
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "deleted"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "const": true
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "deleted": {
                    "summary": "Removed",
                    "value": {
                      "id": "3f1c9a52-8d0e-4b7a-9c61-2e5d7a0b4f18",
                      "deleted": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "404": {
            "description": "No such endpoint for this venue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/test": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          },
          "description": "The endpoint's id."
        }
      ],
      "post": {
        "operationId": "testWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test delivery",
        "description": "Sends one signed `ping` to the endpoint now and tells you what your server answered, so you can check your signature code. A test never counts towards the endpoint's failures.",
        "responses": {
          "200": {
            "description": "The ping was tried. `ok` says whether your server answered 2xx.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PingResult"
                },
                "examples": {
                  "delivered": {
                    "summary": "Your server answered 200",
                    "value": {
                      "id": "0d9e8f7a-6b5c-4d3e-8f1a-2b3c4d5e6f70",
                      "event": "ping",
                      "ok": true,
                      "statusCode": 200,
                      "error": null,
                      "durationMs": 184
                    }
                  },
                  "failed": {
                    "summary": "Your server answered 401",
                    "value": {
                      "id": "0d9e8f7a-6b5c-4d3e-8f1a-2b3c4d5e6f70",
                      "event": "ping",
                      "ok": false,
                      "statusCode": 401,
                      "error": "HTTP 401",
                      "durationMs": 97
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "404": {
            "description": "No such endpoint for this venue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/hooks": {
      "post": {
        "operationId": "createZapierHook",
        "tags": [
          "Zapier"
        ],
        "summary": "Subscribe a Zap (Zapier only)",
        "description": "Used by Parlor's Zapier app when a Zap is switched on. Only accepts hooks.zapier.com addresses. For your own server, use /api/v1/webhooks.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event",
                  "targetUrl"
                ],
                "properties": {
                  "event": {
                    "$ref": "#/components/schemas/EventName"
                  },
                  "targetUrl": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscribed (or the existing identical subscription).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hook"
                },
                "examples": {
                  "hook": {
                    "summary": "A Zap",
                    "value": {
                      "id": "8b2e4f60-1c3d-4a5b-9e7f-0a1b2c3d4e5f",
                      "event": "reservation.created",
                      "targetUrl": "https://hooks.zapier.com/hooks/standard/123456/abcdef/"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown event or a non-Zapier address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "409": {
            "description": "Too many Zaps are listening.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/hooks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The hook's id."
        }
      ],
      "delete": {
        "operationId": "deleteZapierHook",
        "tags": [
          "Zapier"
        ],
        "summary": "Unsubscribe a Zap (Zapier only)",
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "deleted"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "const": true
                    }
                  }
                },
                "examples": {
                  "deleted": {
                    "summary": "Removed",
                    "value": {
                      "id": "8b2e4f60-1c3d-4a5b-9e7f-0a1b2c3d4e5f",
                      "deleted": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenWrite"
          },
          "404": {
            "description": "No such hook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/campaigns": {
      "post": {
        "operationId": "createCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Write a campaign",
        "description": "Saves a marketing email as a draft. Nothing is sent and nothing is scheduled: build the list with compose, send a test, then send.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignInput"
              },
              "examples": {
                "lesson": {
                  "summary": "A weekly lesson email",
                  "value": {
                    "name": "Lesson -- 2026-10-11 -- Why your steak never gets a crust",
                    "subject": "Why your steak never gets a crust",
                    "preheader": "Three small things before it ever hits the pan.",
                    "bodyHtml": "<!doctype html><html><body>...</body></html>",
                    "bodyText": "Why your steak never gets a crust ...",
                    "bodyFormat": "html",
                    "segment": {
                      "type": "all"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved as a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignResult"
                },
                "examples": {
                  "draft": {
                    "summary": "A draft",
                    "value": {
                      "ok": true,
                      "campaign": {
                        "id": 42,
                        "restaurantId": "3f6c2a1e-9b7d-4c55-a1f0-2d8e6b4c9a17",
                        "name": "Lesson -- 2026-10-11 -- Why your steak never gets a crust",
                        "subject": "Why your steak never gets a crust",
                        "preheader": "Three small things before it ever hits the pan.",
                        "bodyHtml": "<!doctype html><html><body>...</body></html>",
                        "bodyText": "Why your steak never gets a crust ...",
                        "bodyFormat": "html",
                        "segment": {
                          "type": "all"
                        },
                        "status": "draft",
                        "scheduledAt": null,
                        "startedAt": null,
                        "finishedAt": null,
                        "testSentAt": null,
                        "createdBy": null,
                        "recipientsTotal": 0,
                        "sentCount": 0,
                        "failedCount": 0,
                        "lastError": "",
                        "sendHoldUntil": null,
                        "eventId": null,
                        "seriesKey": "",
                        "seriesStep": null,
                        "seriesTotal": null,
                        "createdAt": "2026-10-11T12:02:11.000Z",
                        "updatedAt": "2026-10-11T12:02:11.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the body is missing or wrong.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "subjectTooLong": {
                    "summary": "Subject too long",
                    "value": {
                      "error": "A subject line over 200 characters is truncated by most mail clients — shorten it."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "As for any 403, and also: a read-only key, or a Zapier key. Email campaigns need an API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "readOnlyKey": {
                    "summary": "A read-only key",
                    "value": {
                      "error": "This API key is read-only, so it can look things up but can't add, change or remove anything. Create a Read and write key in Parlor under Settings -> Integrations & API if this app needs to make changes."
                    }
                  },
                  "zapierKey": {
                    "summary": "A Zapier key",
                    "value": {
                      "error": "Email campaigns need a Parlor API key, not a Zapier key. Request API access under Settings -> Integrations & API, then create an API key there.",
                      "reason": "api-key-required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/campaigns/compose": {
      "post": {
        "operationId": "composeCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Build the list",
        "description": "Works out who the campaign goes to and queues them. Safe to repeat: someone already queued is not queued twice. It never schedules the campaign, even one that already has a send time -- that is send's job alone.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignCompose"
              },
              "examples": {
                "everyone": {
                  "summary": "Everyone subscribed",
                  "value": {
                    "id": 42
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The list is built. recipientsTotal is who it will go to; unsubscribed and do-not-email addresses are already left out.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "campaign",
                    "recipientsTotal"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "campaign": {
                      "$ref": "#/components/schemas/Campaign"
                    },
                    "recipientsTotal": {
                      "type": "integer"
                    }
                  }
                },
                "examples": {
                  "built": {
                    "summary": "Built",
                    "value": {
                      "ok": true,
                      "campaign": {
                        "id": 42,
                        "restaurantId": "3f6c2a1e-9b7d-4c55-a1f0-2d8e6b4c9a17",
                        "name": "Lesson -- 2026-10-11 -- Why your steak never gets a crust",
                        "subject": "Why your steak never gets a crust",
                        "preheader": "Three small things before it ever hits the pan.",
                        "bodyHtml": "<!doctype html><html><body>...</body></html>",
                        "bodyText": "Why your steak never gets a crust ...",
                        "bodyFormat": "html",
                        "segment": {
                          "type": "all"
                        },
                        "status": "draft",
                        "scheduledAt": null,
                        "startedAt": null,
                        "finishedAt": null,
                        "testSentAt": null,
                        "createdBy": null,
                        "recipientsTotal": 412,
                        "sentCount": 0,
                        "failedCount": 0,
                        "lastError": "",
                        "sendHoldUntil": null,
                        "eventId": null,
                        "seriesKey": "",
                        "seriesStep": null,
                        "seriesTotal": null,
                        "createdAt": "2026-10-11T12:02:11.000Z",
                        "updatedAt": "2026-10-11T12:02:40.000Z"
                      },
                      "recipientsTotal": 412,
                      "heldBack": 0,
                      "holdoutPct": 0,
                      "holdUntil": null,
                      "added": 412,
                      "alreadyPresent": 0,
                      "removed": 0,
                      "keptInFlight": 0,
                      "matched": 431,
                      "matchedKnown": true,
                      "noEmail": 0,
                      "unsubscribed": 12,
                      "suppressed": 7,
                      "excludedTicketHolders": 0,
                      "reliable": true,
                      "notes": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No campaign with that id at this venue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing": {
                    "summary": "Not found",
                    "value": {
                      "error": "No such campaign."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "As for any 403, and also: a read-only key, or a Zapier key. Email campaigns need an API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "readOnlyKey": {
                    "summary": "A read-only key",
                    "value": {
                      "error": "This API key is read-only, so it can look things up but can't add, change or remove anything. Create a Read and write key in Parlor under Settings -> Integrations & API if this app needs to make changes."
                    }
                  },
                  "zapierKey": {
                    "summary": "A Zapier key",
                    "value": {
                      "error": "Email campaigns need a Parlor API key, not a Zapier key. Request API access under Settings -> Integrations & API, then create an API key there.",
                      "reason": "api-key-required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/campaigns/test": {
      "post": {
        "operationId": "testCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Send a test",
        "description": "Sends one copy to each of the venue's saved test addresses (Settings -> Marketing email). The address is never taken from the request -- that would let a key mail anyone in the venue's name. A campaign cannot be sent until a test has reached the first of them.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignId"
              },
              "examples": {
                "test": {
                  "summary": "Test it",
                  "value": {
                    "id": 42
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent to the venue's saved test addresses.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignTestResult"
                },
                "examples": {
                  "sent": {
                    "summary": "Sent",
                    "value": {
                      "ok": true,
                      "to": "owner@example.com",
                      "testSentAt": "2026-10-11T12:03:05.000Z",
                      "messageId": "0100018f-3c2a-7d1e",
                      "alsoSent": [
                        "kitchen@example.com"
                      ],
                      "alsoFailed": []
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "There is no test address saved for this venue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noTestAddress": {
                    "summary": "No test address saved",
                    "value": {
                      "error": "There is no test address saved for this venue, so there is nowhere to send a test. Add one under Settings -> Marketing email -> Test recipients.",
                      "reason": "no-test-address"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "As for any 403, and also: a read-only key, or a Zapier key. Email campaigns need an API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "readOnlyKey": {
                    "summary": "A read-only key",
                    "value": {
                      "error": "This API key is read-only, so it can look things up but can't add, change or remove anything. Create a Read and write key in Parlor under Settings -> Integrations & API if this app needs to make changes."
                    }
                  },
                  "zapierKey": {
                    "summary": "A Zapier key",
                    "value": {
                      "error": "Email campaigns need a Parlor API key, not a Zapier key. Request API access under Settings -> Integrations & API, then create an API key there.",
                      "reason": "api-key-required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/campaigns/send": {
      "post": {
        "operationId": "sendCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Send or schedule it",
        "description": "Puts the campaign on its schedule through every check the dashboard's Send button makes: a test must have reached the venue, the list must be built, the plan must allow it, and the pre-send checklist must have no blockers.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignSend"
              },
              "examples": {
                "at9": {
                  "summary": "At 9am Eastern",
                  "value": {
                    "id": 42,
                    "scheduledAt": "2026-10-11T09:00:00-04:00",
                    "confirmCount": 412
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scheduled. Until it starts, cancel stops it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "campaign",
                    "recipientsTotal",
                    "pending",
                    "holdUntil"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "campaign": {
                      "$ref": "#/components/schemas/Campaign"
                    },
                    "recipientsTotal": {
                      "type": "integer"
                    },
                    "pending": {
                      "type": "integer"
                    },
                    "retried": {
                      "type": "integer"
                    },
                    "holdUntil": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "When a send-now leaves its ten-minute hold. Null for a chosen time."
                    }
                  }
                },
                "examples": {
                  "scheduled": {
                    "summary": "Scheduled for 9am Eastern",
                    "value": {
                      "ok": true,
                      "campaign": {
                        "id": 42,
                        "restaurantId": "3f6c2a1e-9b7d-4c55-a1f0-2d8e6b4c9a17",
                        "name": "Lesson -- 2026-10-11 -- Why your steak never gets a crust",
                        "subject": "Why your steak never gets a crust",
                        "preheader": "Three small things before it ever hits the pan.",
                        "bodyHtml": "<!doctype html><html><body>...</body></html>",
                        "bodyText": "Why your steak never gets a crust ...",
                        "bodyFormat": "html",
                        "segment": {
                          "type": "all"
                        },
                        "status": "scheduled",
                        "scheduledAt": "2026-10-11T13:00:00.000Z",
                        "startedAt": "2026-10-11T12:03:30.000Z",
                        "finishedAt": null,
                        "testSentAt": "2026-10-11T12:03:05.000Z",
                        "createdBy": null,
                        "recipientsTotal": 412,
                        "sentCount": 0,
                        "failedCount": 0,
                        "lastError": "",
                        "sendHoldUntil": null,
                        "eventId": null,
                        "seriesKey": "",
                        "seriesStep": null,
                        "seriesTotal": null,
                        "createdAt": "2026-10-11T12:02:11.000Z",
                        "updatedAt": "2026-10-11T12:02:11.000Z"
                      },
                      "recipientsTotal": 412,
                      "pending": 412,
                      "retried": 0,
                      "holdUntil": null
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Over the venue's plan allowance. Nothing is sent; the campaign stays a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "planLimit": {
                    "summary": "Over the plan",
                    "value": {
                      "error": "This send would go over the emails included in this month's plan. See Settings -> Billing to add more.",
                      "reason": "plan-limit"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Not sent, and why. The campaign is left exactly as it was.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noTest": {
                    "summary": "No test yet",
                    "value": {
                      "error": "Send yourself a test first — a campaign nobody has seen rendered must not go to your whole list.",
                      "reason": "no-test-send"
                    }
                  },
                  "noRecipients": {
                    "summary": "No list yet",
                    "value": {
                      "error": "Compose the audience first — this campaign has nobody in it.",
                      "reason": "no-recipients"
                    }
                  },
                  "countChanged": {
                    "summary": "The list changed",
                    "value": {
                      "error": "This list has changed since you looked at it: it now has 415 people, not 412. Check it and try again.",
                      "reason": "count-changed"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "As for any 403, and also: a read-only key, or a Zapier key. Email campaigns need an API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "readOnlyKey": {
                    "summary": "A read-only key",
                    "value": {
                      "error": "This API key is read-only, so it can look things up but can't add, change or remove anything. Create a Read and write key in Parlor under Settings -> Integrations & API if this app needs to make changes."
                    }
                  },
                  "zapierKey": {
                    "summary": "A Zapier key",
                    "value": {
                      "error": "Email campaigns need a Parlor API key, not a Zapier key. Request API access under Settings -> Integrations & API, then create an API key there.",
                      "reason": "api-key-required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/campaigns/cancel": {
      "post": {
        "operationId": "cancelCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Cancel it",
        "description": "Stops a scheduled or sending campaign. Anyone not yet sent to is skipped, not marked failed. Copies already delivered cannot be called back.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignId"
              },
              "examples": {
                "cancel": {
                  "summary": "Cancel it",
                  "value": {
                    "id": 42
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled. Nobody still waiting will get it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "campaign"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "campaign": {
                      "$ref": "#/components/schemas/Campaign"
                    },
                    "counts": {
                      "type": "object"
                    }
                  }
                },
                "examples": {
                  "cancelled": {
                    "summary": "Cancelled before it started",
                    "value": {
                      "ok": true,
                      "campaign": {
                        "id": 42,
                        "restaurantId": "3f6c2a1e-9b7d-4c55-a1f0-2d8e6b4c9a17",
                        "name": "Lesson -- 2026-10-11 -- Why your steak never gets a crust",
                        "subject": "Why your steak never gets a crust",
                        "preheader": "Three small things before it ever hits the pan.",
                        "bodyHtml": "<!doctype html><html><body>...</body></html>",
                        "bodyText": "Why your steak never gets a crust ...",
                        "bodyFormat": "html",
                        "segment": {
                          "type": "all"
                        },
                        "status": "cancelled",
                        "scheduledAt": "2026-10-11T13:00:00.000Z",
                        "startedAt": null,
                        "finishedAt": "2026-10-11T12:40:00.000Z",
                        "testSentAt": "2026-10-11T12:03:05.000Z",
                        "createdBy": null,
                        "recipientsTotal": 412,
                        "sentCount": 0,
                        "failedCount": 0,
                        "lastError": "",
                        "sendHoldUntil": null,
                        "eventId": null,
                        "seriesKey": "",
                        "seriesStep": null,
                        "seriesTotal": null,
                        "createdAt": "2026-10-11T12:02:11.000Z",
                        "updatedAt": "2026-10-11T12:02:11.000Z"
                      },
                      "counts": {
                        "total": 412,
                        "pending": 0,
                        "sent": 0,
                        "failed": 0,
                        "skipped": 412,
                        "heldBack": 0
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Already sent or already cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "alreadySent": {
                    "summary": "Already sent",
                    "value": {
                      "error": "This campaign is already sent."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "As for any 403, and also: a read-only key, or a Zapier key. Email campaigns need an API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "readOnlyKey": {
                    "summary": "A read-only key",
                    "value": {
                      "error": "This API key is read-only, so it can look things up but can't add, change or remove anything. Create a Read and write key in Parlor under Settings -> Integrations & API if this app needs to make changes."
                    }
                  },
                  "zapierKey": {
                    "summary": "A Zapier key",
                    "value": {
                      "error": "Email campaigns need a Parlor API key, not a Zapier key. Request API access under Settings -> Integrations & API, then create an API key there.",
                      "reason": "api-key-required"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "webhooks": {
    "reservation.created": {
      "post": {
        "operationId": "event_reservation_created",
        "summary": "A reservation is made.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "reservation.created"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Reservation"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A reservation is made.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "reservation.created",
                    "occurredAt": "2026-10-02T14:05:11.482Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 48213,
                      "guestName": "Jordan Ellis",
                      "email": "jordan@example.com",
                      "phone": "(555) 010-4477",
                      "partySize": 4,
                      "date": "2026-10-17",
                      "time": "19:30",
                      "durationMinutes": 90,
                      "status": "Confirmed",
                      "source": "public",
                      "bookingType": "Dinner",
                      "notes": "Anniversary -- window table if possible",
                      "floorStage": null,
                      "createdAt": "2026-10-02T14:05:11.482Z",
                      "checkedInAt": null,
                      "cancelledAt": null,
                      "cancelledBy": null,
                      "updatedAt": "2026-10-02T14:05:11.482Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "reservation.updated": {
      "post": {
        "operationId": "event_reservation_updated",
        "summary": "A reservation is changed (time, size, status, arrival, seating).",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "reservation.updated"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Reservation"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A reservation is changed (time, size, status, arrival, seating).",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "reservation.updated",
                    "occurredAt": "2026-10-02T14:05:11.482Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 48213,
                      "guestName": "Jordan Ellis",
                      "email": "jordan@example.com",
                      "phone": "(555) 010-4477",
                      "partySize": 4,
                      "date": "2026-10-17",
                      "time": "19:30",
                      "durationMinutes": 90,
                      "status": "Confirmed",
                      "source": "public",
                      "bookingType": "Dinner",
                      "notes": "Anniversary -- window table if possible",
                      "floorStage": null,
                      "createdAt": "2026-10-02T14:05:11.482Z",
                      "checkedInAt": null,
                      "cancelledAt": null,
                      "cancelledBy": null,
                      "updatedAt": "2026-10-02T14:05:11.482Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "reservation.cancelled": {
      "post": {
        "operationId": "event_reservation_cancelled",
        "summary": "A reservation is cancelled.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "reservation.cancelled"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Reservation"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A reservation is cancelled.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "reservation.cancelled",
                    "occurredAt": "2026-10-02T14:05:11.482Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 48213,
                      "guestName": "Jordan Ellis",
                      "email": "jordan@example.com",
                      "phone": "(555) 010-4477",
                      "partySize": 4,
                      "date": "2026-10-17",
                      "time": "19:30",
                      "durationMinutes": 90,
                      "status": "Confirmed",
                      "source": "public",
                      "bookingType": "Dinner",
                      "notes": "Anniversary -- window table if possible",
                      "floorStage": null,
                      "createdAt": "2026-10-02T14:05:11.482Z",
                      "checkedInAt": null,
                      "cancelledAt": null,
                      "cancelledBy": null,
                      "updatedAt": "2026-10-02T14:05:11.482Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "guest.created": {
      "post": {
        "operationId": "event_guest_created",
        "summary": "A new guest record is made.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "guest.created"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Guest"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A new guest record is made.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "guest.created",
                    "occurredAt": "2026-09-12T23:10:02.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 1760,
                      "name": "Jordan Ellis",
                      "email": "jordan@example.com",
                      "phone": "(555) 010-4477",
                      "seatingPreference": "Booth",
                      "referralSource": "Website",
                      "visitCount": 6,
                      "noShowCount": 0,
                      "cancellationCount": 1,
                      "lastVisitDate": "2026-09-12",
                      "createdAt": "2025-11-03T17:22:40.000Z",
                      "updatedAt": "2026-09-12T23:10:02.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "subscriber.added": {
      "post": {
        "operationId": "event_subscriber_added",
        "summary": "Someone joins the email list.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "subscriber.added"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Subscriber"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "Someone joins the email list.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "subscriber.added",
                    "occurredAt": "2026-03-14T18:04:10.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 8812,
                      "email": "jordan@example.com",
                      "firstName": "Jordan",
                      "status": "subscribed",
                      "source": "signup_form",
                      "birthMonth": 4,
                      "birthDay": 22,
                      "consentAt": "2026-03-14T18:02:55.000Z",
                      "confirmedAt": "2026-03-14T18:04:10.000Z",
                      "unsubscribedAt": null,
                      "createdAt": "2026-03-14T18:02:55.000Z",
                      "updatedAt": "2026-03-14T18:04:10.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "subscriber.unsubscribed": {
      "post": {
        "operationId": "event_subscriber_unsubscribed",
        "summary": "Someone leaves the email list.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "subscriber.unsubscribed"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Subscriber"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "Someone leaves the email list.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "subscriber.unsubscribed",
                    "occurredAt": "2026-03-14T18:04:10.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 8812,
                      "email": "jordan@example.com",
                      "firstName": "Jordan",
                      "status": "subscribed",
                      "source": "signup_form",
                      "birthMonth": 4,
                      "birthDay": 22,
                      "consentAt": "2026-03-14T18:02:55.000Z",
                      "confirmedAt": "2026-03-14T18:04:10.000Z",
                      "unsubscribedAt": null,
                      "createdAt": "2026-03-14T18:02:55.000Z",
                      "updatedAt": "2026-03-14T18:04:10.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "ticket_order.paid": {
      "post": {
        "operationId": "event_ticket_order_paid",
        "summary": "A ticket order is paid.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "ticket_order.paid"
                      },
                      "data": {
                        "$ref": "#/components/schemas/TicketOrder"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A ticket order is paid.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "ticket_order.paid",
                    "occurredAt": "2026-10-04T20:31:09.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 311,
                      "orderNumber": "MR-7K4Q2",
                      "status": "paid",
                      "channel": "public",
                      "paymentMethod": "square_link",
                      "buyerFirstName": "Jordan",
                      "buyerLastName": "Ellis",
                      "buyerEmail": "jordan@example.com",
                      "buyerPhone": "(555) 010-4477",
                      "eventName": "Murder Mystery Supper",
                      "occurrenceTitle": "Friday seating",
                      "eventDate": "2026-10-30",
                      "eventStartTime": "18:30",
                      "subtotalCents": 9000,
                      "discountCents": 0,
                      "feeCents": 350,
                      "taxCents": 630,
                      "totalCents": 9980,
                      "refundedCents": 0,
                      "total": "99.80",
                      "refunded": "0.00",
                      "promoCode": null,
                      "marketingOptIn": true,
                      "paidAt": "2026-10-04T20:31:09.000Z",
                      "createdAt": "2026-10-04T20:29:47.000Z",
                      "updatedAt": "2026-10-04T20:31:09.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "ticket_order.refunded": {
      "post": {
        "operationId": "event_ticket_order_refunded",
        "summary": "A ticket order is refunded, fully or partly.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "ticket_order.refunded"
                      },
                      "data": {
                        "$ref": "#/components/schemas/TicketOrder"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A ticket order is refunded, fully or partly.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "ticket_order.refunded",
                    "occurredAt": "2026-10-04T20:31:09.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 311,
                      "orderNumber": "MR-7K4Q2",
                      "status": "paid",
                      "channel": "public",
                      "paymentMethod": "square_link",
                      "buyerFirstName": "Jordan",
                      "buyerLastName": "Ellis",
                      "buyerEmail": "jordan@example.com",
                      "buyerPhone": "(555) 010-4477",
                      "eventName": "Murder Mystery Supper",
                      "occurrenceTitle": "Friday seating",
                      "eventDate": "2026-10-30",
                      "eventStartTime": "18:30",
                      "subtotalCents": 9000,
                      "discountCents": 0,
                      "feeCents": 350,
                      "taxCents": 630,
                      "totalCents": 9980,
                      "refundedCents": 0,
                      "total": "99.80",
                      "refunded": "0.00",
                      "promoCode": null,
                      "marketingOptIn": true,
                      "paidAt": "2026-10-04T20:31:09.000Z",
                      "createdAt": "2026-10-04T20:29:47.000Z",
                      "updatedAt": "2026-10-04T20:31:09.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "waitlist.added": {
      "post": {
        "operationId": "event_waitlist_added",
        "summary": "A party joins the waitlist.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "waitlist.added"
                      },
                      "data": {
                        "$ref": "#/components/schemas/WaitlistEntry"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A party joins the waitlist.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "waitlist.added",
                    "occurredAt": "2026-10-04T23:02:00.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 902,
                      "guestName": "Sam Okafor",
                      "email": null,
                      "phone": "(555) 010-9310",
                      "partySize": 2,
                      "date": "2026-10-04",
                      "requestedTime": null,
                      "windowStart": null,
                      "windowEnd": null,
                      "status": "waiting",
                      "source": "staff",
                      "notes": "High chair",
                      "quotedMinutes": 25,
                      "quotedAt": "2026-10-04T23:02:00.000Z",
                      "pagedAt": null,
                      "seatedAt": null,
                      "leftAt": null,
                      "createdAt": "2026-10-04T23:02:00.000Z",
                      "updatedAt": "2026-10-04T23:02:00.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "waitlist.seated": {
      "post": {
        "operationId": "event_waitlist_seated",
        "summary": "A waitlist party is seated.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "waitlist.seated"
                      },
                      "data": {
                        "$ref": "#/components/schemas/WaitlistEntry"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A waitlist party is seated.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "waitlist.seated",
                    "occurredAt": "2026-10-04T23:02:00.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 902,
                      "guestName": "Sam Okafor",
                      "email": null,
                      "phone": "(555) 010-9310",
                      "partySize": 2,
                      "date": "2026-10-04",
                      "requestedTime": null,
                      "windowStart": null,
                      "windowEnd": null,
                      "status": "waiting",
                      "source": "staff",
                      "notes": "High chair",
                      "quotedMinutes": 25,
                      "quotedAt": "2026-10-04T23:02:00.000Z",
                      "pagedAt": null,
                      "seatedAt": null,
                      "leftAt": null,
                      "createdAt": "2026-10-04T23:02:00.000Z",
                      "updatedAt": "2026-10-04T23:02:00.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "lead.created": {
      "post": {
        "operationId": "event_lead_created",
        "summary": "A private-event enquiry arrives.",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "lead.created"
                      },
                      "data": {
                        "$ref": "#/components/schemas/Lead"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A private-event enquiry arrives.",
                  "value": {
                    "id": "b7f3c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
                    "event": "lead.created",
                    "occurredAt": "2026-10-02T15:12:40.118Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "id": 1790953960118,
                      "contactName": "Jordan Ellis",
                      "contactEmail": "jordan@example.com",
                      "contactPhone": "(555) 010-4477",
                      "eventName": "Rehearsal dinner",
                      "occasion": "Wedding",
                      "date": "2026-12-12",
                      "guestCount": "40",
                      "budgetPerPerson": "75",
                      "diningStyle": "Plated",
                      "dietaryNeeds": "Two vegetarian",
                      "stage": "Inquiry",
                      "priority": "Medium",
                      "notes": "Would like the garden room",
                      "submittedVia": "zapier",
                      "submittedAt": "2026-10-02T15:12:40.118Z",
                      "createdAt": "2026-10-02T15:12:40.118Z",
                      "updatedAt": "2026-10-02T15:12:40.118Z"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "Gone: stop sending to this endpoint. Parlor switches it off."
          },
          "2XX": {
            "description": "Received. Any 2xx counts; the body is ignored."
          }
        }
      }
    },
    "ping": {
      "post": {
        "operationId": "event_ping",
        "summary": "A test delivery, sent only when you ask for one (POST /api/v1/webhooks/{id}/test).",
        "parameters": [
          {
            "name": "X-Parlor-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^t=\\d+,v1=[0-9a-f]{64}$"
            },
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\"> keyed with the endpoint's secret."
          },
          {
            "name": "X-Parlor-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The event name, the same as the body's `event`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Delivery"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "event": {
                        "const": "ping"
                      },
                      "data": {
                        "type": "object",
                        "required": [
                          "webhookId",
                          "message"
                        ],
                        "properties": {
                          "webhookId": {
                            "type": "string"
                          },
                          "message": {
                            "type": "string"
                          }
                        },
                        "additionalProperties": false
                      }
                    }
                  }
                ]
              },
              "examples": {
                "delivery": {
                  "summary": "A ping",
                  "value": {
                    "id": "0d9e8f7a-6b5c-4d3e-8f1a-2b3c4d5e6f70",
                    "event": "ping",
                    "occurredAt": "2026-10-04T20:40:00.000Z",
                    "venue": {
                      "slug": "cotillion"
                    },
                    "data": {
                      "webhookId": "3f1c9a52-8d0e-4b7a-9c61-2e5d7a0b4f18",
                      "message": "This is a test from Parlor. If your server answered with a 2xx status, it is receiving Parlor's webhooks."
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Received."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ParlorKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Parlor-Key",
        "description": "Your API key, parlor_sk_ followed by 40 letters and digits. In this header ONLY: never in a URL, never in Authorization."
      }
    },
    "parameters": {
      "limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 25
        },
        "description": "How many to return, 1 to 100 (default 25). Larger numbers are treated as 100."
      },
      "since": {
        "name": "since",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "Only records created at or after this moment. Include a time zone, e.g. 2026-10-01T00:00:00-04:00."
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "description": "Any unique string you choose (1 to 200 visible characters). A retry with the same key and the same body within 24 hours gets the first answer back instead of doing it twice."
      }
    },
    "headers": {
      "IdempotentReplayed": {
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        },
        "description": "Present when this answer is a replay of an earlier request with the same Idempotency-Key."
      },
      "RetryAfter": {
        "schema": {
          "type": "integer"
        },
        "description": "Seconds to wait before trying again."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "No key, or a key Parlor does not recognise or has revoked. When Parlor withdraws a venue's API access, every API key it has is revoked for good, so those keys answer this too; a new key will not work until access is given again.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "badKey": {
                "summary": "Unknown or revoked key",
                "value": {
                  "error": "That Parlor API key isn't recognised or has been revoked. Create a new one in Parlor under Settings -> Integrations & API and reconnect."
                }
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key is fine but it can't be used right now: the venue's plan does not include the API, or its account is paused. On the webhook endpoints, also: a Zapier key, or a read-only key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "paused": {
                "summary": "The venue's account is paused",
                "value": {
                  "error": "This restaurant's Parlor account is paused, so the API can't reach it. Nothing has been deleted -- contact Parlor to switch it back on."
                }
              }
            }
          }
        }
      },
      "ForbiddenWrite": {
        "description": "As for any 403, and also: a read-only key tried to change something.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "readOnlyKey": {
                "summary": "A read-only key tried to change something",
                "value": {
                  "error": "This API key is read-only, so it can look things up but can't add, change or remove anything. Create a Read and write key in Parlor under Settings -> Integrations & API if this app needs to make changes."
                }
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "More than the key's rate limit this minute.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "tooMany": {
                "summary": "Slow down",
                "value": {
                  "error": "Too many requests with this key -- Parlor allows 120 a minute. Try again in 20 seconds."
                }
              }
            }
          }
        }
      },
      "Unavailable": {
        "description": "Parlor could not check the key or reach the data just now. Try again shortly.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "An Idempotency-Key clash: the same key was sent with a different body, or the first request with it is still being worked on.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "idemDifferent": {
                "summary": "Same key, different request",
                "value": {
                  "error": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                }
              },
              "idemProcessing": {
                "summary": "The first try is still running",
                "value": {
                  "error": "A request with this Idempotency-Key is still being processed. Try again in a moment."
                }
              }
            }
          }
        }
      },
      "IdempotencyConflictOnce": {
        "description": "An Idempotency-Key clash: the same key was sent with a different body, the first request with it is still being worked on, or an earlier try never finished and Parlor can't tell whether it went through (`reason` `outcome-unknown`: check Parlor before sending it again).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "idemDifferent": {
                "summary": "Same key, different request",
                "value": {
                  "error": "This Idempotency-Key was already used for a different request. Use a new key for a new request."
                }
              },
              "idemProcessing": {
                "summary": "The first try is still running",
                "value": {
                  "error": "A request with this Idempotency-Key is still being processed. Try again in a moment."
                }
              },
              "idemOutcomeUnknown": {
                "summary": "An earlier try never finished",
                "value": {
                  "error": "An earlier try of this step didn't finish, so Parlor can't tell whether it went through. Check Parlor before running it again -- if it isn't there, run the step with changed details or wait a day.",
                  "reason": "outcome-unknown"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every refusal. `error` is one plain-English sentence you can show to a person. `reason` is a short stable code, present where a program might want to branch on it.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong, in a sentence."
          },
          "reason": {
            "type": "string",
            "description": "A stable code for programs, e.g. `closed`, `party-too-large`, `in-the-past`."
          }
        },
        "additionalProperties": false
      },
      "Me": {
        "type": "object",
        "required": [
          "venue",
          "plan",
          "key"
        ],
        "properties": {
          "venue": {
            "type": "object",
            "required": [
              "slug",
              "name",
              "timezone"
            ],
            "properties": {
              "slug": {
                "type": "string",
                "description": "The venue's short name in Parlor."
              },
              "name": {
                "type": "string",
                "description": "The venue's name."
              },
              "timezone": {
                "type": "string",
                "description": "IANA time zone. Every `date` and `time` in this API is in this zone."
              }
            },
            "additionalProperties": false
          },
          "plan": {
            "type": [
              "string",
              "null"
            ],
            "description": "The venue's Parlor plan (`pro`, `group`, ...), or null when it cannot be read."
          },
          "key": {
            "type": "object",
            "description": "What kind of key made this request. Nothing secret: no id, prefix or hash. Parlor's Zapier app uses it to tell an owner who pasted an API key into Zapier to make a Zapier key instead.",
            "required": [
              "kind",
              "scope"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "zapier",
                  "api"
                ],
                "description": "`api` for a key made under Parlor's API (for your own software); `zapier` for a key made for Parlor's Zapier app."
              },
              "scope": {
                "type": "string",
                "enum": [
                  "read",
                  "read_write"
                ],
                "description": "`read` keys are refused on every write route; `read_write` keys may also write."
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "Reservation": {
        "type": "object",
        "description": "A booking. The same shape in lists, searches, action replies and webhook deliveries.",
        "required": [
          "id",
          "guestName",
          "email",
          "phone",
          "partySize",
          "date",
          "time",
          "durationMinutes",
          "status",
          "source",
          "bookingType",
          "notes",
          "floorStage",
          "createdAt",
          "checkedInAt",
          "cancelledAt",
          "cancelledBy",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "The reservation's id. Stable."
          },
          "guestName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name on the booking."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Guest email on the booking."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Guest phone on the booking, as typed."
          },
          "partySize": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Number of guests."
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Venue-local date, YYYY-MM-DD.",
            "format": "date"
          },
          "time": {
            "type": [
              "string",
              "null"
            ],
            "description": "Venue-local time, HH:MM (24-hour).",
            "pattern": "^\\d{2}:\\d{2}$"
          },
          "durationMinutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How long the table is held."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Requested, Confirmed, Seated, Completed, Cancelled or No-show."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the booking came from: public (the booking page), staff, walkin, waitlist, zapier (an integration or the API), ..."
          },
          "bookingType": {
            "type": [
              "string",
              "null"
            ],
            "description": "The venue's name for the kind of booking (\"Dinner\", \"Afternoon tea\"), if any."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "The booking's own request (\"anniversary, window table\"). Never staff notes about the guest."
          },
          "floorStage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the party is in their meal, when the venue tracks it."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the booking was made.",
            "format": "date-time"
          },
          "checkedInAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the party arrived.",
            "format": "date-time"
          },
          "cancelledAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it was cancelled.",
            "format": "date-time"
          },
          "cancelledBy": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "guest",
              "staff",
              null
            ],
            "description": "Who cancelled it. Never which member of staff."
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "The latest change Parlor has a time for. In a webhook delivery, when this change happened.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": 48213,
            "guestName": "Jordan Ellis",
            "email": "jordan@example.com",
            "phone": "(555) 010-4477",
            "partySize": 4,
            "date": "2026-10-17",
            "time": "19:30",
            "durationMinutes": 90,
            "status": "Confirmed",
            "source": "public",
            "bookingType": "Dinner",
            "notes": "Anniversary -- window table if possible",
            "floorStage": null,
            "createdAt": "2026-10-02T14:05:11.482Z",
            "checkedInAt": null,
            "cancelledAt": null,
            "cancelledBy": null,
            "updatedAt": "2026-10-02T14:05:11.482Z"
          }
        ]
      },
      "Guest": {
        "type": "object",
        "description": "A guest record. Staff-written profile notes and allergies are never included.",
        "required": [
          "id",
          "name",
          "email",
          "phone",
          "seatingPreference",
          "referralSource",
          "visitCount",
          "noShowCount",
          "cancellationCount",
          "lastVisitDate",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "The guest's id. Stable."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email address."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Phone number, as typed."
          },
          "seatingPreference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Preferred seating, if recorded."
          },
          "referralSource": {
            "type": [
              "string",
              "null"
            ],
            "description": "How they first came to the venue."
          },
          "visitCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Visits recorded."
          },
          "noShowCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "No-shows recorded."
          },
          "cancellationCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Cancellations recorded."
          },
          "lastVisitDate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Venue-local date of the last visit.",
            "format": "date"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the record was made.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the record last changed.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": 1760,
            "name": "Jordan Ellis",
            "email": "jordan@example.com",
            "phone": "(555) 010-4477",
            "seatingPreference": "Booth",
            "referralSource": "Website",
            "visitCount": 6,
            "noShowCount": 0,
            "cancellationCount": 1,
            "lastVisitDate": "2026-09-12",
            "createdAt": "2025-11-03T17:22:40.000Z",
            "updatedAt": "2026-09-12T23:10:02.000Z"
          }
        ]
      },
      "Subscriber": {
        "type": "object",
        "description": "Someone on the venue's email list.",
        "required": [
          "id",
          "email",
          "firstName",
          "status",
          "source",
          "birthMonth",
          "birthDay",
          "consentAt",
          "confirmedAt",
          "unsubscribedAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id. Stable."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email address."
          },
          "firstName": {
            "type": [
              "string",
              "null"
            ],
            "description": "First name, if given."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "subscribed, pending (waiting to confirm) or unsubscribed."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "How they joined (signup_form, booking, zapier, import, ...)."
          },
          "birthMonth": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Birthday month, 1-12, if given."
          },
          "birthDay": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Birthday day of the month, if given."
          },
          "consentAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When they agreed to marketing email.",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When they confirmed, for double opt-in.",
            "format": "date-time"
          },
          "unsubscribedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When they left the list.",
            "format": "date-time"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the row was made.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the row last changed.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": 8812,
            "email": "jordan@example.com",
            "firstName": "Jordan",
            "status": "subscribed",
            "source": "signup_form",
            "birthMonth": 4,
            "birthDay": 22,
            "consentAt": "2026-03-14T18:02:55.000Z",
            "confirmedAt": "2026-03-14T18:04:10.000Z",
            "unsubscribedAt": null,
            "createdAt": "2026-03-14T18:02:55.000Z",
            "updatedAt": "2026-03-14T18:04:10.000Z"
          }
        ]
      },
      "TicketOrder": {
        "type": "object",
        "description": "A ticket order. Abandoned and in-flight checkouts are never listed. Money is given both in cents and as a decimal string.",
        "required": [
          "id",
          "orderNumber",
          "status",
          "channel",
          "paymentMethod",
          "buyerFirstName",
          "buyerLastName",
          "buyerEmail",
          "buyerPhone",
          "eventName",
          "occurrenceTitle",
          "eventDate",
          "eventStartTime",
          "subtotalCents",
          "discountCents",
          "feeCents",
          "taxCents",
          "totalCents",
          "refundedCents",
          "total",
          "refunded",
          "promoCode",
          "marketingOptIn",
          "paidAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id. Stable."
          },
          "orderNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "The order number the buyer sees."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "paid, paid_unconfirmed, refunded, partially_refunded, refund_failed or cancelled."
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "description": "public (bought online) or box_office (sold by staff or at the door)."
          },
          "paymentMethod": {
            "type": [
              "string",
              "null"
            ],
            "description": "square_link, cash, comp, free or external."
          },
          "buyerFirstName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Buyer's first name."
          },
          "buyerLastName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Buyer's last name."
          },
          "buyerEmail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Buyer's email."
          },
          "buyerPhone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Buyer's phone."
          },
          "eventName": {
            "type": [
              "string",
              "null"
            ],
            "description": "The event."
          },
          "occurrenceTitle": {
            "type": [
              "string",
              "null"
            ],
            "description": "Which date or seating of the event, if named."
          },
          "eventDate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Venue-local date of the event.",
            "format": "date"
          },
          "eventStartTime": {
            "type": [
              "string",
              "null"
            ],
            "description": "Venue-local start time, HH:MM.",
            "pattern": "^\\d{2}:\\d{2}$"
          },
          "subtotalCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Before discounts, fees and tax, in cents."
          },
          "discountCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Discount, in cents."
          },
          "feeCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Fees, in cents."
          },
          "taxCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Tax, in cents."
          },
          "totalCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What the buyer paid, in cents."
          },
          "refundedCents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Refunded so far, in cents."
          },
          "total": {
            "type": [
              "string",
              "null"
            ],
            "description": "totalCents as dollars, e.g. \"99.80\"."
          },
          "refunded": {
            "type": [
              "string",
              "null"
            ],
            "description": "refundedCents as dollars."
          },
          "promoCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Promo code used, if any."
          },
          "marketingOptIn": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the buyer ticked the box to hear from the venue."
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it was paid.",
            "format": "date-time"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the order was started.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it last changed.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": 311,
            "orderNumber": "MR-7K4Q2",
            "status": "paid",
            "channel": "public",
            "paymentMethod": "square_link",
            "buyerFirstName": "Jordan",
            "buyerLastName": "Ellis",
            "buyerEmail": "jordan@example.com",
            "buyerPhone": "(555) 010-4477",
            "eventName": "Murder Mystery Supper",
            "occurrenceTitle": "Friday seating",
            "eventDate": "2026-10-30",
            "eventStartTime": "18:30",
            "subtotalCents": 9000,
            "discountCents": 0,
            "feeCents": 350,
            "taxCents": 630,
            "totalCents": 9980,
            "refundedCents": 0,
            "total": "99.80",
            "refunded": "0.00",
            "promoCode": null,
            "marketingOptIn": true,
            "paidAt": "2026-10-04T20:31:09.000Z",
            "createdAt": "2026-10-04T20:29:47.000Z",
            "updatedAt": "2026-10-04T20:31:09.000Z"
          }
        ]
      },
      "WaitlistEntry": {
        "type": "object",
        "description": "A party on the waitlist.",
        "required": [
          "id",
          "guestName",
          "email",
          "phone",
          "partySize",
          "date",
          "requestedTime",
          "windowStart",
          "windowEnd",
          "status",
          "source",
          "notes",
          "quotedMinutes",
          "quotedAt",
          "pagedAt",
          "seatedAt",
          "leftAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id. Stable."
          },
          "guestName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email, if given."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Phone, if given."
          },
          "partySize": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Number of guests."
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Venue-local date.",
            "format": "date"
          },
          "requestedTime": {
            "type": [
              "string",
              "null"
            ],
            "description": "A time they asked for, HH:MM, if any."
          },
          "windowStart": {
            "type": [
              "string",
              "null"
            ],
            "description": "Start of the window they can come in, HH:MM, if any."
          },
          "windowEnd": {
            "type": [
              "string",
              "null"
            ],
            "description": "End of that window, HH:MM, if any."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "waiting, offered, booked (seated or given a table), expired or cancelled."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who added them: public (the online list) or staff."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "The party's own request."
          },
          "quotedMinutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The latest wait quoted, in minutes."
          },
          "quotedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When that quote was given.",
            "format": "date-time"
          },
          "pagedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When they were told their table is ready.",
            "format": "date-time"
          },
          "seatedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When they were seated.",
            "format": "date-time"
          },
          "leftAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When they left without being seated.",
            "format": "date-time"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When they joined the list.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the entry last changed.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": 902,
            "guestName": "Sam Okafor",
            "email": null,
            "phone": "(555) 010-9310",
            "partySize": 2,
            "date": "2026-10-04",
            "requestedTime": null,
            "windowStart": null,
            "windowEnd": null,
            "status": "waiting",
            "source": "staff",
            "notes": "High chair",
            "quotedMinutes": 25,
            "quotedAt": "2026-10-04T23:02:00.000Z",
            "pagedAt": null,
            "seatedAt": null,
            "leftAt": null,
            "createdAt": "2026-10-04T23:02:00.000Z",
            "updatedAt": "2026-10-04T23:02:00.000Z"
          }
        ]
      },
      "Lead": {
        "type": "object",
        "description": "A private-event enquiry on the venue's Pipeline, with its contact. Internal notes on the conversation, drafts and triage stay in Parlor.",
        "required": [
          "id",
          "contactName",
          "contactEmail",
          "contactPhone",
          "eventName",
          "occasion",
          "date",
          "guestCount",
          "budgetPerPerson",
          "diningStyle",
          "dietaryNeeds",
          "stage",
          "priority",
          "notes",
          "submittedVia",
          "submittedAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id. Stable."
          },
          "contactName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who the enquiry is from."
          },
          "contactEmail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Their email."
          },
          "contactPhone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Their phone."
          },
          "eventName": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the event is called."
          },
          "occasion": {
            "type": [
              "string",
              "null"
            ],
            "description": "Occasion."
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "description": "The event date as given (free text; usually YYYY-MM-DD)."
          },
          "guestCount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Guest count as given."
          },
          "budgetPerPerson": {
            "type": [
              "string",
              "null"
            ],
            "description": "Budget per person as given."
          },
          "diningStyle": {
            "type": [
              "string",
              "null"
            ],
            "description": "Dining style."
          },
          "dietaryNeeds": {
            "type": [
              "string",
              "null"
            ],
            "description": "Dietary needs as given by the contact."
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pipeline stage: Inquiry, Proposal, Booked, ..."
          },
          "priority": {
            "type": [
              "string",
              "null"
            ],
            "description": "High, Medium or Low."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Notes."
          },
          "submittedVia": {
            "type": [
              "string",
              "null"
            ],
            "description": "How it arrived."
          },
          "submittedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it was submitted.",
            "format": "date-time"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it was made in Parlor.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "The latest change Parlor has a time for.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": 1790953960118,
            "contactName": "Jordan Ellis",
            "contactEmail": "jordan@example.com",
            "contactPhone": "(555) 010-4477",
            "eventName": "Rehearsal dinner",
            "occasion": "Wedding",
            "date": "2026-12-12",
            "guestCount": "40",
            "budgetPerPerson": "75",
            "diningStyle": "Plated",
            "dietaryNeeds": "Two vegetarian",
            "stage": "Inquiry",
            "priority": "Medium",
            "notes": "Would like the garden room",
            "submittedVia": "zapier",
            "submittedAt": "2026-10-02T15:12:40.118Z",
            "createdAt": "2026-10-02T15:12:40.118Z",
            "updatedAt": "2026-10-02T15:12:40.118Z"
          }
        ]
      },
      "Task": {
        "type": "object",
        "description": "A line on the venue's Tasks tab.",
        "required": [
          "id",
          "title",
          "due",
          "priority",
          "category",
          "client",
          "done"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id."
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "What to do."
          },
          "due": {
            "type": [
              "string",
              "null"
            ],
            "description": "Due date, YYYY-MM-DD, or null.",
            "format": "date"
          },
          "priority": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "High",
              "Medium",
              "Low",
              null
            ],
            "description": "Priority."
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "Menu, Finance, Operations, POS, HR, Follow-Up or Other."
          },
          "client": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who it is about, if anyone."
          },
          "done": {
            "type": "boolean",
            "description": "Always false for a new task."
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": 1791032400000,
            "title": "Call Jordan about the rehearsal dinner",
            "due": "2026-10-05",
            "priority": "High",
            "category": "Follow-Up",
            "client": "Jordan Ellis",
            "done": false
          }
        ]
      },
      "GuestInput": {
        "type": "object",
        "description": "Needs an email, or a phone number with at least 7 digits.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Full name.",
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "description": "Email address.",
            "maxLength": 200
          },
          "phone": {
            "type": "string",
            "description": "Phone number.",
            "maxLength": 50
          },
          "notes": {
            "type": "string",
            "description": "Added to the guest's notes. Never replaces what staff wrote.",
            "maxLength": 2000
          }
        }
      },
      "GuestResult": {
        "type": "object",
        "required": [
          "created",
          "guest"
        ],
        "properties": {
          "created": {
            "type": "boolean",
            "description": "true when a new guest was made; false when an existing one was matched."
          },
          "guest": {
            "$ref": "#/components/schemas/Guest"
          },
          "conflicting": {
            "type": "object",
            "description": "Present only when you sent a name, email or phone that differs from what Parlor already has. Parlor keeps its own value, notes yours for staff, and tells you here.",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "SubscriberInput": {
        "type": "object",
        "required": [
          "email",
          "consent"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "Email address.",
            "maxLength": 200
          },
          "firstName": {
            "type": "string",
            "description": "First name.",
            "maxLength": 100
          },
          "tag": {
            "type": "string",
            "description": "A tag to file them under.",
            "maxLength": 60
          },
          "consent": {
            "type": "object",
            "description": "Proof this person agreed to marketing email. Required. Stored as the venue's evidence.",
            "required": [
              "source",
              "statement",
              "at"
            ],
            "properties": {
              "source": {
                "type": "string",
                "description": "Where they agreed, e.g. \"Website contact form\".",
                "maxLength": 200
              },
              "statement": {
                "type": "string",
                "description": "The exact words they agreed to.",
                "minLength": 10,
                "maxLength": 1000
              },
              "at": {
                "type": "string",
                "description": "When they agreed, with a time zone. Not in the future.",
                "format": "date-time"
              },
              "pageUrl": {
                "type": "string",
                "description": "The page they agreed on, if any.",
                "maxLength": 300
              }
            }
          }
        }
      },
      "SubscriberResult": {
        "type": "object",
        "required": [
          "added",
          "status",
          "message",
          "subscriber"
        ],
        "properties": {
          "added": {
            "type": "boolean",
            "description": "true only when this request put them on the list."
          },
          "status": {
            "type": "string",
            "enum": [
              "subscribed",
              "pending",
              "unsubscribed",
              "suppressed"
            ],
            "description": "Where they stand now."
          },
          "message": {
            "type": "string",
            "description": "What happened, in a sentence."
          },
          "subscriber": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Subscriber"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "ReservationInput": {
        "type": "object",
        "description": "Give `date` and `time` in the venue's local time, OR one `startsAt` with a time zone. Needs an email or a phone number with at least 7 digits.",
        "required": [
          "guestName",
          "partySize"
        ],
        "properties": {
          "guestName": {
            "type": "string",
            "description": "Name for the booking.",
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "description": "Guest email.",
            "maxLength": 200
          },
          "phone": {
            "type": "string",
            "description": "Guest phone.",
            "maxLength": 50
          },
          "partySize": {
            "type": "integer",
            "description": "Number of guests.",
            "minimum": 1,
            "maximum": 100
          },
          "date": {
            "type": "string",
            "description": "Venue-local date, YYYY-MM-DD.",
            "format": "date"
          },
          "time": {
            "type": "string",
            "description": "Venue-local time: 19:00, 7:00 PM or 7pm."
          },
          "startsAt": {
            "type": "string",
            "description": "Instead of date and time: an ISO date-time WITH an offset. Converted to the venue's clock.",
            "format": "date-time"
          },
          "notes": {
            "type": "string",
            "description": "The guest's request.",
            "maxLength": 1000
          }
        }
      },
      "ReservationResult": {
        "type": "object",
        "required": [
          "reservation"
        ],
        "properties": {
          "reservation": {
            "$ref": "#/components/schemas/Reservation"
          }
        },
        "additionalProperties": false
      },
      "TaskInput": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "What to do.",
            "maxLength": 200
          },
          "due": {
            "type": "string",
            "description": "Due date, YYYY-MM-DD.",
            "format": "date"
          },
          "priority": {
            "type": "string",
            "enum": [
              "High",
              "Medium",
              "Low"
            ],
            "description": "Default Medium."
          },
          "category": {
            "type": "string",
            "enum": [
              "Menu",
              "Finance",
              "Operations",
              "POS",
              "HR",
              "Follow-Up",
              "Other"
            ],
            "description": "Anything else is filed as Other."
          },
          "client": {
            "type": "string",
            "description": "Who it is about.",
            "maxLength": 200
          }
        }
      },
      "TaskResult": {
        "type": "object",
        "required": [
          "task"
        ],
        "properties": {
          "task": {
            "$ref": "#/components/schemas/Task"
          }
        },
        "additionalProperties": false
      },
      "LeadInput": {
        "type": "object",
        "description": "Needs `name`, and an email or a phone number with at least 7 digits.",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Who the enquiry is from.",
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "description": "Their email.",
            "maxLength": 200
          },
          "phone": {
            "type": "string",
            "description": "Their phone.",
            "maxLength": 50
          },
          "eventName": {
            "type": "string",
            "description": "What the event is called. Default \"TBD\".",
            "maxLength": 200
          },
          "occasion": {
            "type": "string",
            "description": "Occasion.",
            "maxLength": 200
          },
          "eventDate": {
            "type": "string",
            "description": "The date, YYYY-MM-DD.",
            "format": "date"
          },
          "guestCount": {
            "type": "string",
            "description": "How many guests.",
            "maxLength": 40
          },
          "budgetPerPerson": {
            "type": "string",
            "description": "Budget per person.",
            "maxLength": 40
          },
          "concept": {
            "type": "string",
            "description": "Which room or brand. Defaults to the one the venue used last.",
            "maxLength": 100
          },
          "priority": {
            "type": "string",
            "enum": [
              "High",
              "Medium",
              "Low"
            ]
          },
          "notes": {
            "type": "string",
            "description": "Anything else.",
            "maxLength": 4000
          }
        }
      },
      "LeadResult": {
        "type": "object",
        "required": [
          "lead"
        ],
        "properties": {
          "lead": {
            "$ref": "#/components/schemas/Lead"
          }
        },
        "additionalProperties": false
      },
      "EventName": {
        "type": "string",
        "enum": [
          "reservation.created",
          "reservation.updated",
          "reservation.cancelled",
          "guest.created",
          "subscriber.added",
          "subscriber.unsubscribed",
          "ticket_order.paid",
          "ticket_order.refunded",
          "waitlist.added",
          "waitlist.seated",
          "lead.created"
        ],
        "description": "Something that happened at the venue."
      },
      "WebhookInput": {
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Where to send deliveries. https, standard port, a public domain name: no IP addresses, no names that only work inside one network, no user:password@.",
            "maxLength": 2000
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/EventName"
            },
            "description": "Which events to send."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "description": "One of your endpoints. At most 10 per venue. The secret is never shown again.",
        "required": [
          "id",
          "url",
          "events",
          "status",
          "disabledReason",
          "failureCount",
          "lastDeliveryAt",
          "lastDeliveryStatus",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The endpoint's id (a uuid)."
          },
          "url": {
            "type": "string",
            "description": "Where deliveries go."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EventName"
            },
            "description": "The events it receives."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "disabled"
            ],
            "description": "active; paused (the owner paused it in Settings); or disabled (Parlor stopped sending, see disabledReason)."
          },
          "disabledReason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why Parlor stopped sending, when status is disabled."
          },
          "failureCount": {
            "type": "integer",
            "description": "Failed deliveries in a row. 20 switches it off."
          },
          "lastDeliveryAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When Parlor last tried to deliver to it.",
            "format": "date-time"
          },
          "lastDeliveryStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "What happened then: \"ok 200\", \"HTTP 500\", \"timed out after 10 seconds\"..."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When it was added.",
            "format": "date-time"
          }
        },
        "additionalProperties": false,
        "examples": [
          {
            "id": "3f1c9a52-8d0e-4b7a-9c61-2e5d7a0b4f18",
            "url": "https://hooks.example.com/parlor",
            "events": [
              "reservation.cancelled",
              "reservation.created"
            ],
            "status": "active",
            "disabledReason": null,
            "failureCount": 0,
            "lastDeliveryAt": "2026-10-04T20:31:10.000Z",
            "lastDeliveryStatus": "ok 200",
            "createdAt": "2026-10-01T15:00:00.000Z"
          }
        ]
      },
      "WebhookCreated": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "status",
          "createdAt",
          "secret"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The endpoint's id."
          },
          "url": {
            "type": "string",
            "description": "Where deliveries go."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EventName"
            }
          },
          "status": {
            "const": "active"
          },
          "createdAt": {
            "type": "string",
            "description": "When it was added.",
            "format": "date-time"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret. Shown ONCE, here. Store it; Parlor cannot show it again.",
            "pattern": "^whsec_[0-9a-f]{64}$"
          }
        },
        "additionalProperties": false
      },
      "PingResult": {
        "type": "object",
        "required": [
          "id",
          "event",
          "ok",
          "statusCode",
          "error",
          "durationMs"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The ping delivery's id."
          },
          "event": {
            "const": "ping"
          },
          "ok": {
            "type": "boolean",
            "description": "true when your server answered 2xx."
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What your server answered, or null when there was no answer."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "What went wrong, when ok is false."
          },
          "durationMs": {
            "type": "integer",
            "description": "How long it took."
          }
        },
        "additionalProperties": false
      },
      "Hook": {
        "type": "object",
        "required": [
          "id",
          "event",
          "targetUrl"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Hook id."
          },
          "event": {
            "$ref": "#/components/schemas/EventName"
          },
          "targetUrl": {
            "type": "string",
            "description": "A hooks.zapier.com address."
          }
        },
        "additionalProperties": false
      },
      "Delivery": {
        "type": "object",
        "description": "What Parlor POSTs to your endpoint. The body is signed; see X-Parlor-Signature.",
        "required": [
          "id",
          "event",
          "occurredAt",
          "venue",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The event's id. The SAME on every retry, so use it to ignore repeats."
          },
          "event": {
            "type": "string",
            "description": "The event name, or `ping` for a test delivery."
          },
          "occurredAt": {
            "type": "string",
            "description": "When it happened.",
            "format": "date-time"
          },
          "venue": {
            "type": "object",
            "required": [
              "slug"
            ],
            "properties": {
              "slug": {
                "type": "string",
                "description": "The venue's short name."
              }
            },
            "additionalProperties": false
          },
          "data": {
            "type": "object",
            "description": "The record, in the same shape the matching list endpoint returns."
          }
        },
        "additionalProperties": false
      },
      "Campaign": {
        "type": "object",
        "description": "A marketing email, as the dashboard's Campaigns tab holds it: every column of the campaign, named in camelCase. The fields below are the ones a program needs; others (A/B testing, batching) may be present too and are safe to ignore.",
        "required": [
          "id",
          "name",
          "subject",
          "status",
          "recipientsTotal",
          "sentCount",
          "failedCount",
          "lastError"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "The campaign's id. Every other campaign call takes it."
          },
          "name": {
            "type": "string",
            "description": "The internal name staff see in Parlor. Never shown to readers."
          },
          "subject": {
            "type": "string"
          },
          "preheader": {
            "type": "string",
            "description": "The preview line a mail app shows after the subject."
          },
          "bodyFormat": {
            "type": "string",
            "enum": [
              "guided",
              "html"
            ],
            "description": "`html` sends your document as written (Parlor still enforces the unsubscribe link and postal address); `guided` wraps the body in the venue's email design."
          },
          "segment": {
            "type": "object",
            "description": "Who it goes to. `{\"type\": \"all\"}` is everyone subscribed."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled",
              "sending",
              "paused",
              "sent",
              "cancelled"
            ]
          },
          "scheduledAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the send starts (UTC)."
          },
          "testSentAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a test last reached the venue's first test address. A campaign without one cannot be sent."
          },
          "sendHoldUntil": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set while a send-now is on its ten-minute hold and can still be cancelled."
          },
          "recipientsTotal": {
            "type": "integer"
          },
          "sentCount": {
            "type": "integer"
          },
          "failedCount": {
            "type": "integer"
          },
          "lastError": {
            "type": "string",
            "description": "Why a send paused itself, in a sentence. Empty when there is nothing to explain."
          }
        }
      },
      "CampaignInput": {
        "type": "object",
        "description": "A new campaign. It is always saved as a draft; nothing here can schedule it (that is POST /api/v1/campaigns/send).",
        "required": [
          "name",
          "subject"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "The internal name staff see in Parlor."
          },
          "subject": {
            "type": "string",
            "maxLength": 200
          },
          "preheader": {
            "type": "string",
            "description": "The preview line after the subject."
          },
          "bodyHtml": {
            "type": "string",
            "maxLength": 400000,
            "description": "The email. Gmail clips anything over about 102 KB."
          },
          "bodyText": {
            "type": "string",
            "description": "The plain-text part."
          },
          "bodyFormat": {
            "type": "string",
            "enum": [
              "guided",
              "html"
            ],
            "description": "`html` for a complete document of your own; `guided` (the default) to wrap it in the venue's design."
          },
          "segment": {
            "type": "object",
            "description": "Who it goes to. `{\"type\": \"all\"}` is everyone subscribed to the venue."
          }
        },
        "additionalProperties": false
      },
      "CampaignId": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "The campaign's id."
          }
        },
        "additionalProperties": false
      },
      "CampaignCompose": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "segment": {
            "type": "object",
            "description": "Change who it goes to while building the list. Leave it out to use the campaign's own."
          }
        },
        "additionalProperties": false
      },
      "CampaignSend": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "scheduledAt": {
            "type": "string",
            "format": "date-time",
            "description": "When to send, with a time zone. Leave it out to send now: a send-now waits ten minutes first, and POST /api/v1/campaigns/cancel stops it in that time with nobody having received it."
          },
          "confirmCount": {
            "type": "integer",
            "description": "How many people you expect it to go to (recipientsTotal from compose). If the list has changed since, nothing is sent and you get 409 count-changed."
          }
        },
        "additionalProperties": false
      },
      "CampaignResult": {
        "type": "object",
        "required": [
          "ok",
          "campaign"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "campaign": {
            "$ref": "#/components/schemas/Campaign"
          }
        },
        "description": "The campaign as it now stands."
      },
      "CampaignTestResult": {
        "type": "object",
        "required": [
          "ok",
          "to"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Whether the copy to the first test address arrived. Only that copy unlocks sending."
          },
          "to": {
            "type": "string",
            "description": "The venue's first saved test address. Never an address from the request."
          },
          "testSentAt": {
            "type": "string"
          },
          "messageId": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "error": {
            "type": "string"
          },
          "alsoSent": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The venue's other saved test addresses that got a copy."
          },
          "alsoFailed": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      }
    }
  }
}
