# Bookings

Table reservations and booking management

## GET /api/v1/bookings

List bookings

List bookings with optional filtering and pagination

**Permissions:** 🔵 `bookings.read`

Query parameters:
- `locationId` (number, optional) — Filter by location ID
- `tableId` (string, optional) — Filter by table ID
- `status` ("pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show", optional) — Booking status
- `bookingDate` (string, optional) — Filter by exact date
- `startDate` (string, optional) — Filter by start date (inclusive)
- `endDate` (string, optional) — Filter by end date (inclusive)
- `userId` (number, optional) — Filter by user ID
- `confirmationCode` (string, optional) — Filter by confirmation code
- `bookingSource` ("direct" | "website" | "app" | "phone" | "walkin" | "third_party", optional) — Source of the booking
- `guestEmail` (string, optional) — Filter by guest contact email (case-insensitive). Finds bookings made without a user account, which have no userId to filter on.
- `guestPhone` (string, optional) — Filter by guest contact phone (exact match). Finds bookings made without a user account, which have no userId to filter on.
- `page` (number, optional) — Page number
- `limit` (number, optional) — Items per page

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    bookings: Array<{
        customFields?: unknown[]
        metadata?: { … } | null
        id: string // uuid
        locationId: number
        tableId?: string | null // uuid
        bookingDate: string // date
        startTime: string
        endTime: string
        timezone: string
        primaryUserId?: number | null
        guestCount: number
        occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
        specialRequests?: string | null
        internalNotes?: string | null
        notes?: unknown[]
        status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
        paymentType?: "none" | "deposit" | "full_prepayment"
        depositAmountCents?: number | null
        purchaseId?: number | null
        confirmationCode: string
        bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
        estimatedRevenueCents?: number | null
        guestName?: string | null
        guestEmail?: string | null
        guestPhone?: string | null
        createdAt: string // date-time
        updatedAt: string // date-time
        confirmedAt?: string | null // date-time
        seatedAt?: string | null // date-time
        completedAt?: string | null // date-time
        cancelledAt?: string | null // date-time
        tabId?: string | null
        defaultTabStrategy: "single" | "per_resource"
        resources: unknown[]
        userName?: string | null
        userEmail?: string | null
        userPhone?: string | null
        tableName?: string | null
        location?: { … } | null
        attribution?: { … } | null
      }>
    pagination: {
      page: number
      limit: number
      total: number
      totalPages: number
    }
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings

Create booking

Create a new booking. The response always includes a linked `tab` (the booking creates a `pre_arrival` tab atomically) so integrations can begin committing orders against `data.tab.id` immediately. Pass `seatImmediately: true` in the body to atomically transition the new booking to `seated` and open its tab in a single call (recommended for walk-in flows). When `seatImmediately` is true and the booking has unpaid required items or its table already has an open tab, the booking is still created but seating is skipped and `seatWarning` is returned.

**Permissions:** 🟢 `bookings.create`

Custom fields: send customFields: [{ "key": "your_field_key", "value": "your_value", "expectedVersion": 0 }]. Use either fieldId or a stable key. expectedVersion is optional; supply the last-read version to reject stale edits. Omitted fields are unchanged; null clears an optional value. See [Custom field inputs](/docs/custom-fields#values-on-normal-create-and-update-calls).

Request body:

```ts
{
  guestFieldAnswers?: unknown // Guest form schema token and answers; validated against the current reservation form.
  customFields?: unknown | unknown[]
  requiredItemAnswers?: unknown // Guest form answers keyed by the booking group's required item ID.
  locationId: number
  tableId?: string | null // uuid
  tableIds?: string[]
  defaultTabStrategy?: "single" | "per_resource"
  bookingDate: string // date
  startTime: string
  endTime: string
  timezone: string
  primaryUserId?: number | null
  guestCount: number
  occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
  specialRequests?: string
  internalNotes?: string
  paymentType?: "none" | "deposit" | "full_prepayment"
  depositAmountCents?: number
  bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
  guestName?: string
  guestEmail?: string
  guestPhone?: string
  seatImmediately?: boolean
  sendNotification?: boolean
  sendConfirmation?: boolean
  attribution?: {
    firstTouch?: {
      capturedAt: string // date-time
      utmSource?: string
      utmMedium?: string
      utmCampaign?: string
      utmContent?: string
      utmTerm?: string
      referrer?: string
      landingPage?: string
    }
    bookingTouch?: {
      capturedAt: string // date-time
      utmSource?: string
      utmMedium?: string
      utmCampaign?: string
      utmContent?: string
      utmTerm?: string
      referrer?: string
      landingPage?: string
    }
    reportedSource?: string
  }
}
```

Response 201 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id?: string // uuid
    locationId?: number
    tableId?: string | null // uuid
    bookingDate?: string // date
    startTime?: string
    endTime?: string
    timezone?: string
    primaryUserId?: number | null
    guestCount?: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status?: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode?: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt?: string // date-time
    updatedAt?: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy?: "single" | "per_resource"
    resources?: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    tab?: {
      id?: string // uuid
      tabMemberId?: string | null // uuid
    }
    requiresPayment?: boolean
    totalRequiredItemsCents?: number
    confirmationDelivery?: {
      email: "sent" | "no_address"
      sms: "sent" | "no_number"
    } | null
    seated?: boolean
    seatWarning?: string
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/bookings/{id}

Get booking

Get a single booking by ID

**Permissions:** 🔵 `bookings.read`

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PUT /api/v1/bookings/{id}

Update booking

Update an existing booking

**Permissions:** 🟠 `bookings.update`

Custom fields: send customFields: [{ "key": "your_field_key", "value": "your_value", "expectedVersion": 0 }]. Use either fieldId or a stable key. expectedVersion is optional; supply the last-read version to reject stale edits. Omitted fields are unchanged; null clears an optional value. See [Custom field inputs](/docs/custom-fields#values-on-normal-create-and-update-calls).

Path parameters:
- `id` (string, required) — Booking ID

Request body:

```ts
{
  tableId?: string | null // uuid
  tableIds?: string[]
  defaultTabStrategy?: "single" | "per_resource"
  confirmRemoveActiveTab?: boolean
  bookingDate?: string // date
  startTime?: string
  endTime?: string
  guestCount?: number
  occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
  specialRequests?: string
  internalNotes?: string
  paymentType?: "none" | "deposit" | "full_prepayment"
  depositAmountCents?: number
  customFields?: unknown | unknown[]
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/cancel

Cancel booking

Cancel a booking. Guest email/SMS is sent by default; pass `sendNotification: false` when the caller handles guest communication. Booking webhooks and internal events are always emitted.

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Request body:

```ts
{
  reason?: string
  closeTab?: boolean
  sendNotification?: boolean
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/check-in

Check in booking

Mark a confirmed booking as checked in

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/complete

Complete booking

Mark a booking as completed

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/confirm

Confirm booking

Confirm a pending booking. Guest email/SMS is sent by default; pass `sendNotification: false` to suppress it. Booking webhooks and internal events are always emitted.

**Permissions:** ⚪ `bookings.manage`

Query parameters:
- `sendNotification` (boolean, optional) — Whether to send the guest a confirmation by email and SMS. Defaults to true. This does not suppress booking webhooks or internal events.

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/bookings/{id}/history

Get booking history

Get audit log/history for a booking

**Permissions:** 🔵 `bookings.read`

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: Array<{
      id: number
      bookingId: string // uuid
      eventType: string
      eventData: Record<string, unknown>
      userId?: number | null
      retailerUserId?: number | null
      createdAt: string // date-time
    }>
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/bookings/{id}/members

List booking members

List members of a booking

**Permissions:** 🔵 `bookings.read`

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: Array<{
      id: string // uuid
      bookingId: string // uuid
      userId?: number | null
      isPrimary: boolean
      guestName?: string | null
      guestEmail?: string | null
      rsvpStatus?: "pending" | "accepted" | "declined"
      createdAt: string // date-time
    }>
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/members

Add booking member

Add a member to a booking

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Request body:

```ts
{
  userId?: number
  guestName?: string
  guestEmail?: string
  rsvpStatus?: "pending" | "accepted" | "declined"
}
```

Response 201 — Default Response

```ts
{
  status: "success"
  data: {
    id: string // uuid
    bookingId: string // uuid
    userId?: number | null
    isPrimary: boolean
    guestName?: string | null
    guestEmail?: string | null
    rsvpStatus?: "pending" | "accepted" | "declined"
    createdAt: string // date-time
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## DELETE /api/v1/bookings/{id}/members/{memberId}

Remove booking member

Remove a non-primary member from a booking

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID
- `memberId` (string, required) — Member ID

Response 200 — Default Response

```ts
Record<string, unknown>
```

## PATCH /api/v1/bookings/{id}/members/{memberId}

Update booking member

Update a booking member RSVP status

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID
- `memberId` (string, required) — Member ID

Request body:

```ts
{
  rsvpStatus: "pending" | "accepted" | "declined"
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    id: string // uuid
    bookingId: string // uuid
    userId?: number | null
    isPrimary: boolean
    guestName?: string | null
    guestEmail?: string | null
    rsvpStatus?: "pending" | "accepted" | "declined"
    createdAt: string // date-time
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/no-show

Mark no-show

Mark a booking as no-show

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/seat

Seat booking

Mark a booking as seated. Optionally create a linked tab for the booking. Retrying an already-applied seat operation returns the current booking with 200. A booking without a reserved table returns 409 NO_RESERVED_RESOURCE.

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Request body:

```ts
{
  tabId?: string // uuid
  createTab?: boolean
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id?: string // uuid
    locationId?: number
    tableId?: string | null // uuid
    bookingDate?: string // date
    startTime?: string
    endTime?: string
    timezone?: string
    primaryUserId?: number | null
    guestCount?: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status?: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode?: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt?: string // date-time
    updatedAt?: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy?: "single" | "per_resource"
    resources?: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    tab?: {
      id?: string // uuid
      tabMemberId?: string | null // uuid
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/split

Split booking into per-resource tabs

Phase 40 R6 — convert a seated `single`-strategy multi-resource booking into per-resource tabs. Creates N-1 new tabs (one per non-primary resource that does not yet have its own tab). The existing primary tab and any items on it are preserved. `bookings.default_tab_strategy` is NOT mutated — it records the original choice. Idempotent: re-running only creates tabs for resources missing one.

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    tabs: Array<{
        id: string // uuid
        qrCodeId: string // uuid
        isPrimary: boolean
        created: boolean
      }>
    createdCount: integer
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/{id}/unseat

Unseat booking

Undo a seat operation. Reverts the booking to its prior status (checked_in or confirmed), puts the linked tab back to pre_arrival, and reverses all kitchen dispatches advanced by seating. Retrying after the booking is restored returns 200. Refuses if purchases exist, additional orders were placed, or any dispatch is past accepted.

**Permissions:** ⚪ `bookings.manage`

Path parameters:
- `id` (string, required) — Booking ID

Request body:

```ts
{
  reason?: string
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
    attribution?: {
      firstTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      bookingTouch?: {
        capturedAt: string // date-time
        utmSource?: string
        utmMedium?: string
        utmCampaign?: string
        utmContent?: string
        utmTerm?: string
        referrer?: string
        landingPage?: string
      }
      reportedSource?: string
      recordedAt: string // date-time
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/by-code/{code}/cancel

Cancel booking by confirmation code

Public endpoint: cancel a booking using confirmation code (no auth required)

Path parameters:
- `code` (string, required) — Booking confirmation code

Request body:

```ts
{
  reason?: string
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata?: {
      customFields?: Record<string, unknown>
    } | null
    id: string // uuid
    locationId: number
    tableId?: string | null // uuid
    bookingDate: string // date
    startTime: string
    endTime: string
    timezone: string
    primaryUserId?: number | null
    guestCount: number
    occasion?: "none" | "birthday" | "anniversary" | "business" | "date" | "celebration" | "other"
    specialRequests?: string | null
    internalNotes?: string | null
    notes?: Array<{
        uuid: string // uuid
        note: string
        sortOrder: number
        createdByRetailerUserId?: number | null
        updatedByRetailerUserId?: number | null
        createdAt: string // date-time
        updatedAt: string // date-time
      }>
    status: "pending" | "confirmed" | "checked_in" | "seated" | "completed" | "cancelled" | "no_show"
    paymentType?: "none" | "deposit" | "full_prepayment"
    depositAmountCents?: number | null
    purchaseId?: number | null
    confirmationCode: string
    bookingSource?: "direct" | "website" | "app" | "phone" | "walkin" | "third_party"
    estimatedRevenueCents?: number | null
    guestName?: string | null
    guestEmail?: string | null
    guestPhone?: string | null
    createdAt: string // date-time
    updatedAt: string // date-time
    confirmedAt?: string | null // date-time
    seatedAt?: string | null // date-time
    completedAt?: string | null // date-time
    cancelledAt?: string | null // date-time
    tabId?: string | null
    defaultTabStrategy: "single" | "per_resource"
    resources: Array<{
        qrCodeId: string // uuid
        isPrimary: boolean
        position: integer
      }>
    userName?: string | null
    userEmail?: string | null
    userPhone?: string | null
    tableName?: string | null
    location?: {
      id?: number
      name?: string | null
      city?: string | null
      latitude?: number | null
      longitude?: number | null
      logoImageUrl?: string | null
    } | null
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/bookings/rsvp

RSVP to booking

Public endpoint: RSVP to a booking using confirmation code (no auth required)

Request body:

```ts
{
  confirmationCode: string
  guestName: string
  guestEmail?: string
  rsvpStatus: "pending" | "accepted" | "declined"
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    id: string // uuid
    bookingId: string // uuid
    userId?: number | null
    isPrimary: boolean
    guestName?: string | null
    guestEmail?: string | null
    rsvpStatus?: "pending" | "accepted" | "declined"
    createdAt: string // date-time
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

Response 201 — Default Response

```ts
{
  status: "success"
  data: {
    id: string // uuid
    bookingId: string // uuid
    userId?: number | null
    isPrimary: boolean
    guestName?: string | null
    guestEmail?: string | null
    rsvpStatus?: "pending" | "accepted" | "declined"
    createdAt: string // date-time
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/locations/{locationId}/booking-availability

Get availability

Get available booking slots for a date and party size

**Permissions:** 🔵 `bookings.read`

Query parameters:
- `date` (string, required) — Date to check availability
- `partySize` (number, required) — Number of guests
- `bookingGroupId` (string, optional) — Optional booking group ID to filter availability by group schedule, table pool, and settings

Path parameters:
- `locationId` (number, required) — Location ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    date: string // date
    partySize: number
    slots: Array<{
        id: string // uuid
        tableId?: string | null // uuid
        locationId: number
        slotDate: string // date
        startTime: string
        endTime: string
        durationMinutes: number
        maxCapacity: number
        currentBookings: number
        status: "available" | "partially_booked" | "fully_booked" | "blocked"
        predictedDemandScore?: number | null
        optimalPartySizeMin?: number | null
        optimalPartySizeMax?: number | null
        scheduleId?: string | null // uuid
        createdAt: string // date-time
      }>
    tables?: Array<{
        id?: string
        name?: string
        minCapacity?: number
        maxCapacity?: number
        availableSlots?: unknown[]
      }>
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/locations/{locationId}/booking-availability-range

Get availability across a date range

Per-day availability status across a date range, for calendar rendering. Returns counts and a status per day rather than slot lists — fetch the single-date availability endpoint for the day the guest picks.

**Permissions:** 🔵 `bookings.read`

Query parameters:
- `startDate` (string, required) — First date of the range (inclusive)
- `endDate` (string, required) — Last date of the range (inclusive). Must not be more than 62 days after startDate.
- `partySize` (number, required) — Number of guests

Path parameters:
- `locationId` (number, required) — Location ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    locationId: number
    partySize: number
    startDate: string // date
    endDate: string // date
    days: Array<{
        date: string // date
        status: "available" | "limited" | "full" | "closed"
        availableSlotCount: number
        totalSlotCount: number
        firstAvailableTime: string | null
      }>
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/locations/{locationId}/bookings/by-table

Get bookings by table

Get bookings grouped by table for a specific date. Useful for table map display in POS.

**Permissions:** 🔵 `bookings.read`

Query parameters:
- `date` (string, required) — Date to fetch bookings for (YYYY-MM-DD)

Path parameters:
- `locationId` (number, required) — Location ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    tables: Array<{
        tableId: string // uuid
        tableName?: string | null
        bookings: unknown[]
      }>
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```
