> ## Documentation Index
> Fetch the complete documentation index at: https://opencals.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reschedule appointment

> Reschedules an existing appointment to a new time. Subject to reschedule_gap restrictions defined in the service configuration.



## OpenAPI

````yaml /api-reference/openapi.json put /storefront/appointments/{appointmentId}/reschedule
openapi: 3.0.0
info:
  title: Opencals API
  description: >
    # Opencals Storefront API


    Build custom booking storefronts with the Opencals Storefront API. Browse
    services, check real-time availability, manage carts, process payments, and
    complete bookings — all programmatically.


    ---


    ## Getting Started


    ### 1. Create an Account


    Sign up at [app.opencals.com](https://app.opencals.com) and set up your
    store with services, staff members, locations, and schedules.


    ### 2. Generate a Storefront API Key


    Navigate to **Settings > API Keys** in your dashboard. Create a new
    **Storefront API Key**. Each key is scoped to your store and starts with
    `sfk_`.


    ### 3. Make Your First Request


    ```bash

    curl -H "X-Api-Key: sfk_your_key_here" \
      https://api.opencals.com/customer/products
    ```


    This returns a paginated list of your store's active services.


    ---


    ## Authentication


    ### Storefront API Key


    All requests require a **Storefront API Key** in the `X-Api-Key` header:


    ```

    X-Api-Key: sfk_your_key_here

    ```


    This key identifies your store. It should be kept server-side (e.g., in
    environment variables) and never exposed to the browser.


    ### Customer Authentication


    Some endpoints require a logged-in customer (viewing appointments, managing
    profile). Customers authenticate via credentials and receive JWT tokens:


    ```http

    POST /auth/customer/sign-in

    X-Api-Key: sfk_your_key_here

    Content-Type: application/json


    {
      "email": "customer@example.com",
      "password": "securepassword"
    }

    ```


    **Response:**

    ```json

    {
      "accessToken": "eyJhbGciOi...",
      "refreshToken": "eyJhbGciOi..."
    }

    ```


    Include the customer token alongside the API key for authenticated requests:

    ```

    Authorization: Bearer <customer_access_token>

    X-Api-Key: sfk_your_key_here

    ```


    ### Customer Auth Endpoints


    | Method | Endpoint | Description |

    |--------|----------|-------------|

    | `POST` | `/auth/customer/sign-up` | Register a new customer (sends
    verification email) |

    | `POST` | `/auth/customer/sign-in` | Sign in with email + password |

    | `POST` | `/auth/customer/oauth` | Sign in/register via OAuth provider
    (e.g., Google) |

    | `POST` | `/auth/customer/verify-email` | Verify email with token from
    verification email |

    | `POST` | `/auth/customer/request-email-verification` | Resend verification
    email |

    | `POST` | `/auth/customer/request-password-reset` | Send password reset
    email |

    | `POST` | `/auth/customer/reset-password` | Reset password with token |

    | `GET`  | `/auth/customer/refresh` | Refresh tokens (pass refresh token as
    Bearer) |


    ---


    ## Booking Flows


    The booking flow depends on your product configuration. Below are the common
    patterns.


    ### Standard Booking (Single Time Slot)


    The most common flow — customer picks a service, selects a date/time, and
    books.


    ```

    1. GET  /customer/products                              → Browse services

    2. GET  /customer/products/:id/current-availabilities    → Get available
    time slots for a date
          ?date=2026-06-15&timezone=America/New_York
    3. POST /customer/appointments                           → Create
    appointment with chosen slot
          { slot: { productId, fromDate, fromTime, toDate, toTime }, numberOfAttendees: 1 }
    4. POST /customer/cart                                   → Create or get
    cart

    5. POST /customer/cart/items                             → Add appointment
    to cart
          { appointmentId: "..." }
    6. POST /customer/cart/checkout/start                    → Start checkout
    with payment provider
          { provider: "stripe" }
    7. POST /customer/cart/checkout/save-customer            → Save customer
    info
          { customer: { email, firstName, lastName } }
    8. POST /customer/cart/checkout/submit                   → Complete booking
    & capture payment

    ```


    ### Custom Duration / Date Range Booking


    When a product has **custom duration** enabled (e.g., car rentals, venue
    bookings), customers select a date range instead of a single time slot.


    **How to detect:** The product's `isCustomDuration` field is `true`.


    ```

    1. GET /customer/products/:id/current-availability-ranges   → Get available
    date ranges
          ?timezone=America/New_York
    2. UI shows a date range picker instead of individual slots

    3. POST /customer/appointments
          { slot: { productId, fromDate: "2026-06-15", fromTime: "09:00:00",
                    toDate: "2026-06-18", toTime: "17:00:00" } }
    4. Continue with cart → checkout flow as above

    ```


    ### Group Appointments


    When a product supports multiple attendees (`maxAttendees > 1`), display an
    attendees selector.


    ```

    POST /customer/appointments

    {
      "slot": { ... },
      "numberOfAttendees": 4
    }

    ```


    ### Location-First Flow


    When a store has multiple locations, let the customer pick a location first,
    then filter services and availability by location.


    ```

    1. GET /customer/locations                                → List all
    locations

    2. User selects a location

    3. GET /customer/products?locationId=<locationId>         → Products at that
    location

    4. GET /customer/products/:id/current-availabilities
          ?date=2026-06-15&locationId=<locationId>            → Slots at that location
    5. POST /customer/appointments
          { slot: { ..., locationId: "<locationId>" } }
    ```


    ### Staff-First Flow


    When staff member selection is important (e.g., hair salons), let customers
    choose their preferred staff member.


    ```

    1. GET /customer/staff-members                            → List staff
    members

    2. User selects a staff member

    3. GET /customer/products/:id/current-availabilities
          ?date=2026-06-15&staffMemberId=<staffMemberId>      → Slots with that staff member
    4. POST /customer/appointments
          { slot: { ..., staffMemberId: "<staffMemberId>" } }
    ```


    ### Combined Flow (Location + Staff + Service)


    All filters can be combined:


    ```

    GET /customer/products/:id/current-availabilities
        ?date=2026-06-15
        &locationId=<locationId>
        &staffMemberId=<staffMemberId>
        &timezone=America/New_York
    ```


    ---


    ## Product Configuration Flags


    Products have configuration fields that determine which UI elements to
    display. Check these fields when rendering your booking interface:


    | Field | Type | UI Behavior |

    |-------|------|-------------|

    | `isCustomDuration` | `boolean` | `true` → show date range picker; `false`
    → show single time slot picker |

    | `maxAttendees` | `number` | `> 1` → show attendees selector; `1` → hide it
    |

    | `skipCheckout` | `boolean` | `true` → skip payment step, book directly;
    `false` → normal checkout |

    | `fixedStartTimes` / `fixedEndTimes` | `string[]` | If present, only these
    specific times are bookable |

    | `locations` | `Location[]` | If populated, show location selector |

    | `staffMembers` | `StaffMember[]` | If populated, show staff member
    selector |

    | `price` | `number` | Service price; `0` = free |

    | `minPrice` / `maxPrice` | `number` | Price range for variable-priced
    services |

    | `duration` | `number` | Service duration in seconds |

    | `minDuration` / `maxDuration` | `number` | Duration range for custom
    duration services |

    | `images` | `Image[]` | Product images for gallery display |

    | `description` | `string` | Product description (may contain markdown) |


    ---


    ## Endpoint Reference


    ### Products


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/products` | API key | List all active products
    (paginated) |

    | `GET` | `/customer/products/:id` | API key | Get product by ID |

    | `GET` | `/customer/products/slug/:slug` | API key | Get product by URL
    slug |

    | `GET` | `/customer/products/:id/current-availabilities` | API key | Get
    bookable time slots for a date |

    | `GET` | `/customer/products/:id/current-availability-ranges` | API key |
    Get available date ranges (for custom duration) |

    | `GET` | `/customer/products/:id/nearest-availability` | API key | Find
    next available slot |


    **Availability query parameters:**


    | Parameter | Required | Description |

    |-----------|----------|-------------|

    | `date` | Yes (for current-availabilities) | Date in `YYYY-MM-DD` format |

    | `locationId` | No | Filter by location |

    | `staffMemberId` | No | Filter by staff member |

    | `timezone` | No | Timezone for date/time calculations (e.g.,
    `America/New_York`) |


    ### Product Collections


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/product-collections` | API key | List all collections
    (paginated) |

    | `GET` | `/customer/product-collections/slug/:slug` | API key | Get
    collection by slug |


    ### Locations


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/locations` | API key | List all locations (paginated) |

    | `GET` | `/customer/locations/:id` | API key | Get location by ID |

    | `GET` | `/customer/locations/slug/:slug` | API key | Get location by slug
    |


    ### Staff Members


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/staff-members` | API key | List all staff members
    (paginated) |

    | `GET` | `/customer/staff-members/slug/:slug` | API key | Get staff member
    by slug |


    ### Cart


    All cart endpoints use the `X-Cart-Id` header to identify the cart.


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `POST` | `/customer/cart` | API key | Create or retrieve a cart |

    | `GET` | `/customer/cart` | API key | Get current cart |

    | `POST` | `/customer/cart/items` | API key | Add an appointment to the cart
    |

    | `DELETE` | `/customer/cart/items/:itemId` | API key | Remove item from
    cart |

    | `POST` | `/customer/cart/extend` | API key | Extend cart expiration (5
    min) |


    **Adding items to cart:**

    ```json

    POST /customer/cart/items

    X-Cart-Id: <cart_id>


    { "appointmentId": "appointment-uuid-here" }

    ```


    ### Add-Ons


    Add-ons are optional extras customers can attach to a cart item at checkout
    (equipment rentals, upgrades, accessories, etc.). Add-ons are scoped to
    products — only add-ons assigned to the booked service appear.


    **Storefront (listing):**


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/storefront/add-ons` | API key | List add-ons (filter by
    `productId`) |

    | `GET` | `/storefront/add-ons/:id` | API key | Get add-on by ID |

    | `GET` | `/storefront/add-ons/slug/:slug` | API key | Get add-on by slug |

    | `GET` | `/storefront/products/:id/add-ons` | API key | List add-ons for a
    specific product |


    **Cart (selection):**


    All cart add-on endpoints require the `X-Cart-Id` header.


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `POST` | `/storefront/cart/add-ons` | API key | Add an add-on to a cart
    item |

    | `PATCH` | `/storefront/cart/add-ons/:id` | API key | Update add-on
    quantity |

    | `DELETE` | `/storefront/cart/add-ons/:id` | API key | Remove add-on from
    cart |


    **Adding an add-on to a cart item:**

    ```json

    POST /storefront/cart/add-ons

    X-Cart-Id: <cart_id>


    {
      "cartItemId": "uuid-of-cart-item",
      "addOnId": "uuid-of-add-on",
      "quantity": 1
    }

    ```


    The system validates that the add-on is assigned to the cart item's product
    and that `quantity` does not exceed `maxQuantity` (if set). For
    duration-multiplied add-ons, quantity is auto-set to match the service
    duration units — send any value and it will be overridden.


    **Add-on object (storefront response):**

    ```json

    {
      "id": "addon-uuid",
      "slug": "deep-conditioning",
      "title": "Deep Conditioning Treatment",
      "description": "Intensive moisture treatment added to any color or cut service.",
      "price": 20.00,
      "taxable": true,
      "durationMultiplied": false,
      "maxQuantity": 1,
      "status": "active",
      "imageId": "img-uuid-or-null"
    }

    ```


    **Pricing notes:**

    - `price` — unit price. For duration-multiplied add-ons, multiply by
    duration units to get the total.

    - `durationMultiplied: true` — quantity is auto-set to the number of
    base-duration units booked (useful for per-day or per-hour charges).

    - Prices are snapshotted at cart/order creation. Later catalog price changes
    do not affect existing orders.


    **In the order response**, add-ons appear as `addOnLineItems` on each line
    item:

    ```json

    {
      "lineItems": [
        {
          "id": "...",
          "appointmentId": "...",
          "addOnLineItems": [
            {
              "id": "...",
              "addOnId": "...",
              "title": "Deep Conditioning Treatment",
              "quantity": 1,
              "originalUnitPrice": 20.00,
              "unitTaxAmount": 3.33,
              "totalPrice": 20.00
            }
          ]
        }
      ]
    }

    ```


    ### Checkout


    All checkout endpoints require the `X-Cart-Id` header.


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `POST` | `/customer/cart/checkout/start` | API key | Start checkout
    (initialize payment) |

    | `POST` | `/customer/cart/checkout/save-customer` | API key | Save customer
    info to cart |

    | `GET` | `/customer/cart/checkout/questions/:language` | API key | Get
    checkout questions |

    | `POST` | `/customer/cart/checkout/save-answers` | API key | Save checkout
    question answers |

    | `POST` | `/customer/cart/checkout/submit` | API key | Submit checkout
    (capture payment, create order) |


    **Start checkout:**

    ```json

    POST /customer/cart/checkout/start

    X-Cart-Id: <cart_id>


    {
      "provider": "stripe",
      "customer": {
        "email": "john@example.com",
        "firstName": "John",
        "lastName": "Smith"
      }
    }

    ```


    The `provider` field accepts: `stripe`, `cash`, or other configured payment
    providers.


    **Submit checkout response** includes order details and optionally
    auto-generated customer auth tokens:

    ```json

    {
      "order": { "id": "...", "name": "#1001", ... },
      "tokens": {
        "accessToken": "eyJ...",
        "refreshToken": "eyJ..."
      }
    }

    ```


    ### Appointments (Customer Authenticated)


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `POST` | `/customer/appointments` | API key (+ optional customer token) |
    Create appointment |

    | `GET` | `/customer/appointments` | Customer token | List customer
    appointments (paginated) |

    | `GET` | `/customer/appointments/:id` | Customer token | Get appointment
    details |

    | `PUT` | `/customer/appointments/:id/reschedule` | Customer token |
    Reschedule appointment |

    | `PUT` | `/customer/appointments/:id/cancel` | Customer token | Cancel
    appointment |

    | `POST` | `/customer/appointments/:id/feedback` | Customer token | Submit
    feedback |

    | `GET` | `/customer/appointments/preferences/booking-history` | Customer
    token | Get booking preferences |


    ### Orders (Customer Authenticated)


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/orders` | Customer token | List customer orders
    (paginated) |

    | `GET` | `/customer/orders/:id` | Customer token | Get order details |


    ### Customer Profile (Customer Authenticated)


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/profile` | Customer token | Get customer profile |

    | `PUT` | `/customer/profile` | Customer token | Update customer profile |

    | `PUT` | `/customer/password` | Customer token | Change password |


    ### Store


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/stores/public-settings` | API key | Get store public
    settings |


    **Public settings response includes:**

    - `name` — store display name

    - `domain` — store domain

    - `currency` — store currency code (e.g., `USD`)

    - `settings` — general store settings (timezone, locale, etc.)

    - `storefrontSettings` — presentation config (logo, colors, banners, etc.)

    - `contactInfo` — store contact information

    - `features` — enabled plan features


    ### Payment


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/payment/providers` | API key | List available payment
    providers |

    | `GET` | `/customer/payment/settings` | API key | Get public payment
    settings |


    ### Images


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/images/:id` | API key | Get image metadata by ID |


    ### Checkout Questions


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/checkout-questions/:productId/translations/:language` |
    API key | Get checkout questions for a product |


    ### Feedback Questions


    | Method | Endpoint | Auth | Description |

    |--------|----------|------|-------------|

    | `GET` | `/customer/feedback-questions/:productId/translations/:language` |
    Customer token | Get feedback questions for a product |


    ---


    ## Checkout Flow Detail


    ### Step 1: Create Appointment


    Before adding to a cart, create the appointment with the selected time slot:


    ```json

    POST /customer/appointments

    {
      "slot": {
        "productId": "product-uuid",
        "fromDate": "2026-06-15",
        "fromTime": "09:00:00",
        "toDate": "2026-06-15",
        "toTime": "10:00:00",
        "locationId": "location-uuid-or-null",
        "staffMemberId": "staff-uuid-or-null"
      },
      "numberOfAttendees": 1
    }

    ```


    All date/time values are in **UTC**. Convert from the user's timezone before
    sending.


    ### Step 2: Create Cart & Add Items


    ```json

    POST /customer/cart

    → Returns { "id": "cart-uuid", ... }


    POST /customer/cart/items

    X-Cart-Id: cart-uuid

    { "appointmentId": "appointment-uuid" }

    ```


    Carts expire automatically. Use `POST /customer/cart/extend` to add 5
    minutes.


    ### Step 3: Start Checkout


    ```json

    POST /customer/cart/checkout/start

    X-Cart-Id: cart-uuid

    {
      "provider": "stripe",
      "customer": { "email": "john@example.com", "firstName": "John", "lastName": "Smith" }
    }

    ```


    Response includes payment provider details (e.g., Stripe `clientSecret` for
    Elements).


    ### Step 4: Save Customer Info


    If not provided in start, save customer details separately:


    ```json

    POST /customer/cart/checkout/save-customer

    X-Cart-Id: cart-uuid

    {
      "customer": { "email": "john@example.com", "firstName": "John", "lastName": "Smith" }
    }

    ```


    For logged-in customers, pass `{ "customer": { "customerId":
    "existing-customer-uuid" } }`.


    ### Step 5: Checkout Questions (Optional)


    If the store has checkout questions configured:


    ```

    GET /customer/cart/checkout/questions/en

    X-Cart-Id: cart-uuid

    → Returns array of questions with types (text, select, checkbox, etc.)


    POST /customer/cart/checkout/save-answers

    X-Cart-Id: cart-uuid

    { "answers": [{ "questionId": "...", "value": "..." }] }

    ```


    ### Step 6: Submit Checkout


    ```json

    POST /customer/cart/checkout/submit

    X-Cart-Id: cart-uuid

    {}

    ```


    Response includes the created order and optionally customer auth tokens for
    auto-login.


    ---


    ## Payment Integration


    ### Available Providers


    Fetch configured payment providers for your store:


    ```

    GET /customer/payment/providers

    → [{ "name": "stripe", "displayName": "Credit Card", ... }]

    ```


    ### Stripe Integration


    1. Get Stripe publishable key from `GET /customer/payment/settings`

    2. Start checkout with `provider: "stripe"` — response includes
    `clientSecret`

    3. Use Stripe Elements / Payment Element with the `clientSecret`

    4. After Stripe confirms payment client-side, call `POST /checkout/submit`


    ### Cash / Pay at Venue


    Start checkout with `provider: "cash"`. No payment processing needed —
    submit directly.


    ### Redirect-Based Payments


    Some providers redirect the customer to an external page. The start response
    includes a `redirectUrl`. After payment, the customer is redirected back to
    your `thankYouUrl`.


    ---


    ## Store Settings


    Use `GET /customer/stores/public-settings` to retrieve store configuration
    for your template:


    - **Store name** → display in header/navbar

    - **Logo** → from `storefrontSettings`

    - **Currency** → format prices correctly (e.g., `USD`, `EUR`)

    - **Timezone** → convert availability times to/from the store's timezone

    - **Contact info** → display in footer or contact page

    - **Features** → check which features are enabled (e.g., customer portal)


    ---


    ## Rate Limiting


    Requests are rate-limited per API key:


    | Category | Limit |

    |----------|-------|

    | General | 100 requests / 60 seconds |

    | Authentication | 20 requests / 10 seconds |

    | Password reset | 5 requests / hour |

    | Registration | 10 requests / minute |


    When rate-limited, you'll receive a `429 Too Many Requests` response.


    ---


    ## Response Format


    All responses use **camelCase** property names:


    ```json

    {
      "id": "abc-123",
      "title": "Haircut",
      "maxAttendees": 1,
      "duration": 3600,
      "staffMembers": [
        { "id": "staff-1", "firstName": "John", "lastName": "Doe" }
      ]
    }

    ```


    ### Pagination


    List endpoints return paginated collections:


    ```json

    {
      "data": [ ... ],
      "meta": {
        "total": 42,
        "page": 1,
        "limit": 10,
        "totalPages": 5
      }
    }

    ```


    Use `?page=2&limit=20` query parameters to paginate.


    ---


    ## Error Responses


    ```json

    {
      "statusCode": 400,
      "message": "Validation failed",
      "errors": [
        { "property": "email", "messages": ["Email is required"] }
      ]
    }

    ```


    | Status | Meaning |

    |--------|---------|

    | 400 | Validation error |

    | 401 | Invalid or missing API key / customer token |

    | 404 | Resource not found |

    | 429 | Rate limit exceeded |


    ---


    ## Timezone Handling


    - All date/time values in API requests and responses are in **UTC**

    - Pass the `timezone` query parameter to availability endpoints to get slots
    converted to a specific timezone

    - Use the store's timezone from public settings as the default display
    timezone

    - Convert user-selected times to UTC before creating appointments


    ---


    ## Webhooks (Coming Soon)


    Webhook support for real-time notifications is planned. Events will include:


    - `product.created`, `product.updated`, `product.deleted`

    - `staff_member.created`, `staff_member.updated`, `staff_member.deleted`

    - `location.created`, `location.updated`, `location.deleted`

    - `store.settings_updated`

    - `collection.updated`


    Use webhooks to invalidate cached data in your deployed storefronts.
  version: '1.0'
  contact: {}
servers:
  - url: https://api.opencals.com
security:
  - Storefront API key: []
tags: []
paths:
  /storefront/appointments/{appointmentId}/reschedule:
    put:
      tags:
        - Appointments
      summary: Reschedule appointment
      description: >-
        Reschedules an existing appointment to a new time. Subject to
        reschedule_gap restrictions defined in the service configuration.
      operationId: appointment_reschedule
      parameters:
        - name: appointmentId
          required: true
          in: path
          description: Appointment unique identifier
          schema:
            example: 123e4567-e89b-12d3-a456-426614174000
            type: string
      requestBody:
        required: true
        description: New appointment time details
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RescheduleAppointment'
      responses:
        '200':
          description: The rescheduled appointment details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentResponse'
      security:
        - bearer: []
components:
  schemas:
    RescheduleAppointment:
      type: object
      properties:
        slot:
          description: >-
            Appointment slot information — date/time range with product,
            location, and staff
          allOf:
            - $ref: '#/components/schemas/AppointmentDateRangeSlot'
        notifyCustomer:
          type: boolean
          description: Whether to send rescheduling notification to the customer
          default: true
          example: true
        isManual:
          type: boolean
          description: >-
            Indicates if the appointment was rescheduled manually instead of by
            the customer
          example: false
          default: false
        address:
          description: Physical address where the appointment will take place
          allOf:
            - $ref: '#/components/schemas/AppointmentAddress'
      required:
        - slot
    AppointmentResponse:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier of the appointment
          example: 123e4567-e89b-12d3-a456-426614174000
        name:
          type: string
          description: >-
            Generated appointment name for the user (starts at 1001 and
            increments)
          example: '1001'
        productId:
          type: string
          description: ID of the service/product for this appointment
          example: 123e4567-e89b-12d3-a456-426614174001
        staffMemberId:
          type: string
          description: ID of the staff member assigned to this appointment (if any)
          example: 123e4567-e89b-12d3-a456-426614174002
          nullable: true
        locationId:
          type: string
          description: ID of the location where the appointment will take place (if any)
          example: 123e4567-e89b-12d3-a456-426614174003
          nullable: true
        storeId:
          type: string
          description: ID of the store that owns this appointment
          example: 123e4567-e89b-12d3-a456-426614174004
        customerId:
          type: string
          description: ID of the customer who booked the appointment
          example: 123e4567-e89b-12d3-a456-426614174005
          nullable: true
        externalOrderId:
          type: string
          description: External order ID from user's platform (if any)
          example: '12345678'
          nullable: true
        externalOrderName:
          type: string
          description: External order name/reference from user's platform
          example: '#1001'
          nullable: true
        from:
          format: date-time
          type: string
          description: Start date and time of the appointment (UTC timezone)
          example: '2023-06-15T09:00:00Z'
        to:
          format: date-time
          type: string
          description: End date and time of the appointment (UTC timezone)
          example: '2023-06-15T10:00:00Z'
        addressLine1:
          type: string
          description: First line of the appointment address
          example: 123 Main Street
          nullable: true
        addressLine2:
          type: string
          description: Second line of the appointment address
          example: Suite 456
          nullable: true
        city:
          type: string
          description: City of the appointment location
          example: New York
          nullable: true
        state:
          type: string
          description: State/province of the appointment location
          example: NY
          nullable: true
        postalCode:
          type: string
          description: Postal/zip code of the appointment location
          example: '10001'
          nullable: true
        country:
          type: string
          description: Country of the appointment location
          example: USA
          nullable: true
        status:
          $ref: '#/components/schemas/AppointmentStatusType'
          example: scheduled
        internalNote:
          type: string
          description: Internal notes about the appointment (not visible to customers)
          example: Customer requested extra attention for specific needs
          nullable: true
        createdBy:
          $ref: '#/components/schemas/AppointmentCreatedByEnum'
          example: customer
        reminderSentAt:
          format: date-time
          type: string
          description: Date and time when reminder was sent to the customer
          example: '2023-06-14T09:00:00Z'
          nullable: true
        numberOfAttendees:
          type: number
          description: Number of people attending the appointment
          example: 1
          default: 1
        createdAt:
          format: date-time
          type: string
          description: Date and time when the appointment was created
          example: '2023-06-01T10:30:00Z'
        updatedAt:
          format: date-time
          type: string
          description: Date and time when the appointment was last updated
          example: '2023-06-02T14:15:00Z'
          nullable: true
        deletedAt:
          format: date-time
          type: string
          description: Date and time when the appointment was soft-deleted
          example: '2023-06-03T09:45:00Z'
          nullable: true
        product:
          description: Service/product details for this appointment
          allOf:
            - $ref: '#/components/schemas/AppointmentProduct'
        staffMember:
          description: Staff member assigned to this appointment
          nullable: true
          allOf:
            - $ref: '#/components/schemas/AppointmentStaffMember'
        location:
          description: Location where the appointment will take place
          nullable: true
          allOf:
            - $ref: '#/components/schemas/AppointmentLocation'
        addOns:
          description: Add-ons attached to this appointment
          type: array
          items:
            $ref: '#/components/schemas/AppointmentAddOn'
        guests:
          description: Guests invited to this appointment (copied on notifications)
          type: array
          items:
            $ref: '#/components/schemas/AppointmentGuest'
      required:
        - id
        - name
        - productId
        - staffMemberId
        - locationId
        - storeId
        - customerId
        - externalOrderId
        - externalOrderName
        - from
        - to
        - addressLine1
        - addressLine2
        - city
        - state
        - postalCode
        - country
        - status
        - internalNote
        - createdBy
        - reminderSentAt
        - numberOfAttendees
        - createdAt
        - updatedAt
        - deletedAt
        - product
        - staffMember
        - location
        - addOns
        - guests
    AppointmentDateRangeSlot:
      type: object
      properties:
        fromDate:
          type: string
          description: Start date for the appointment in YYYY-MM-DD format (UTC timezone)
          example: '2023-06-15'
          pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/
        fromTime:
          type: string
          description: Start time for the appointment in HH:MM:SS format (UTC timezone)
          example: '09:00:00'
          pattern: /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
        toDate:
          type: string
          description: End date for the appointment in YYYY-MM-DD format (UTC timezone)
          example: '2023-06-15'
          pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/
        toTime:
          type: string
          description: End time for the appointment in HH:MM:SS format (UTC timezone)
          example: '10:00:00'
          pattern: /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
        productId:
          type: string
          description: ID of the service/product for this appointment
          example: 123e4567-e89b-12d3-a456-426614174001
        locationId:
          type: string
          description: ID of the location where the appointment will take place (if any)
          example: 123e4567-e89b-12d3-a456-426614174002
          nullable: true
        staffMemberId:
          type: string
          description: ID of the staff member assigned to this appointment (if any)
          example: 123e4567-e89b-12d3-a456-426614174003
          nullable: true
      required:
        - fromDate
        - fromTime
        - toDate
        - toTime
        - productId
    AppointmentAddress:
      type: object
      properties:
        addressLine1:
          type: string
          description: First line of the appointment address
          example: 123 Main Street
          maxLength: 255
        addressLine2:
          type: string
          description: Second line of the appointment address
          example: Suite 456
          maxLength: 255
        city:
          type: string
          description: City of the appointment location
          example: New York
          maxLength: 255
        state:
          type: string
          description: State/province of the appointment location
          example: NY
          maxLength: 255
        postalCode:
          type: string
          description: Postal/zip code of the appointment location
          example: '10001'
          maxLength: 255
        country:
          type: string
          description: Country of the appointment location
          example: USA
          maxLength: 255
    AppointmentStatusType:
      type: string
      description: Current status of the appointment
      default: pending
      enum:
        - pending
        - scheduled
        - completed
        - canceled
    AppointmentCreatedByEnum:
      type: string
      description: Indicates who created the appointment (customer or merchant)
      default: customer
      enum:
        - customer
        - merchant
    AppointmentProduct:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the product
          example: 123e4567-e89b-12d3-a456-426614174000
        productId:
          type: string
          description: ID of the product
          example: 123e4567-e89b-12d3-a456-426614174000
        externalId:
          type: string
          description: External platform product ID (e.g. Shopify Product ID)
          example: '1234567890'
        externalVariantId:
          type: string
          description: External platform variant ID (e.g. Shopify Variant ID)
          example: '9876543210'
        storeId:
          type: string
          description: ID of the store that owns this product
          example: 123e4567-e89b-12d3-a456-426614174001
        slug:
          type: string
          description: URL-friendly slug for the product (unique per store)
          example: haircut-basic
        scheduleId:
          type: string
          description: ID of the schedule associated with this product
        productPoolId:
          type: string
          description: >-
            ID of the product pool this product belongs to (controls overlap
            prevention)
          nullable: true
        title:
          type: string
          description: Title of the product
          example: Haircut Service
        variantTitle:
          type: string
          description: Title of the specific product variant
          example: Short Hair
        description:
          type: string
          description: Detailed description of the product
        imageId:
          type: string
          description: ID of the product image
        color:
          type: string
          description: Color code for displaying the product
          enum:
            - slate
            - gray
            - zinc
            - neutral
            - stone
            - red
            - orange
            - amber
            - yellow
            - lime
            - green
            - emerald
            - teal
            - cyan
            - sky
            - blue
            - indigo
            - violet
            - purple
            - fuchsia
            - pink
            - rose
          example: BLUE
          default: slate
        price:
          type: number
          description: Price of the product
          example: 250
        taxable:
          type: boolean
          description: Whether the product is taxable
          default: false
        duration:
          type: number
          description: Duration of the service in seconds
          example: 3600
        allowCustomDuration:
          type: boolean
          description: >-
            Whether customers can customize the duration by selecting multiples
            of the base service duration (e.g., if base duration is 1 day,
            customers can book 1, 2, 3+ days)
          default: false
        maxDuration:
          type: number
          description: >-
            When allow_custom_duration=true, maximum duration allowed for
            multi-duration bookings in seconds (-1 for unlimited)
          default: -1
        maxAttendees:
          type: number
          description: Maximum number of attendees allowed
          default: 1
        maxGuests:
          type: number
          description: >-
            Maximum number of guests allowed per appointment. Null means
            unlimited.
          nullable: true
        allowGuests:
          type: boolean
          description: Whether customers can invite guests to the appointment
          default: false
        allowCustomerReschedule:
          type: boolean
          description: Whether customers can reschedule their bookings
          default: false
        allowCustomerCancel:
          type: boolean
          description: Whether customers can cancel their bookings
          default: false
        rescheduleGap:
          type: number
          description: >-
            Time in seconds before appointment when rescheduling is no longer
            allowed
        cancelGap:
          type: number
          description: >-
            Time in seconds before appointment when cancellation is no longer
            allowed
        beforeGap:
          type: number
          description: Buffer time in seconds required before this service
        afterGap:
          type: number
          description: Buffer time in seconds required after this service
        fixedTimes:
          type: boolean
          description: >-
            When allow_custom_duration=true, whether appointments must start and
            end at fixed times (like fixed check-in/check-out times for
            multi-duration bookings)
          default: false
        fixedStartTime:
          type: string
          description: >-
            When allow_custom_duration=true and fixed_times=true, the fixed time
            when appointments must start (HH:MM:SS) - specified in the product's
            schedule timezone, or user settings timezone if no schedule is
            assigned
          example: '09:00:00'
        fixedEndTime:
          type: string
          description: >-
            When allow_custom_duration=true and fixed_times=true, the fixed time
            when appointments must end (HH:MM:SS) - specified in the product's
            schedule timezone, or user settings timezone if no schedule is
            assigned
          example: '17:00:00'
        advanceScheduleThreshold:
          type: number
          description: How many days in advance bookings can be made
          default: 0
        sendConfirmationEmail:
          type: boolean
          description: Whether to send confirmation emails for bookings
          default: true
        sendReminderEmail:
          type: boolean
          description: Whether to send reminder emails before appointments
          default: false
        sendFeedbackEmail:
          type: boolean
          description: Whether to send feedback request emails after appointments
          default: false
        skipCheckout:
          type: boolean
          description: >-
            Whether to skip the checkout process for this product (Shopify
            merchants only)
          default: false
        status:
          type: string
          description: Current status of the product
          enum:
            - active
            - inactive
          example: ACTIVE
          default: active
        createdAt:
          type: string
          description: Timestamp when the product was created
        updatedAt:
          type: string
          description: Timestamp when the product was last updated
        deletedAt:
          type: string
          description: Timestamp when the product was deleted (for soft deletes)
        image:
          description: Primary image of the product
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Image'
      required:
        - id
        - productId
        - externalId
        - externalVariantId
        - storeId
        - slug
        - scheduleId
        - productPoolId
        - title
        - variantTitle
        - description
        - imageId
        - color
        - price
        - taxable
        - duration
        - allowCustomDuration
        - maxDuration
        - maxAttendees
        - maxGuests
        - allowGuests
        - allowCustomerReschedule
        - allowCustomerCancel
        - rescheduleGap
        - cancelGap
        - beforeGap
        - afterGap
        - fixedTimes
        - fixedStartTime
        - fixedEndTime
        - advanceScheduleThreshold
        - sendConfirmationEmail
        - sendReminderEmail
        - sendFeedbackEmail
        - skipCheckout
        - status
        - createdAt
        - updatedAt
        - deletedAt
    AppointmentStaffMember:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier of the staff member
          example: 123e4567-e89b-12d3-a456-426614174000
        storeId:
          type: string
          description: ID of the store that owns this staff member record
          example: 123e4567-e89b-12d3-a456-426614174111
        userId:
          type: string
          description: User ID of the staff member login identity (nullable until linked)
          example: user-uuid
        scheduleId:
          type: string
          description: >-
            ID of the schedule assigned to this staff member, defines their
            availability
          example: 123e4567-e89b-12d3-a456-426614174000
        imageId:
          type: string
          description: ID of the image associated with this staff member
          example: 123e4567-e89b-12d3-a456-426614174000
        firstName:
          type: string
          description: First name of the staff member
          example: John
        lastName:
          type: string
          description: Last name of the staff member
          example: Doe
        email:
          type: string
          description: >-
            Email address of the staff member, used for notifications and
            calendar integration
          example: john.doe@example.com
        slug:
          type: string
          description: URL-friendly slug for the staff member (unique per store)
          example: mike
        refreshToken:
          type: string
          description: Refresh token for integrations like Google Calendar
          example: abc123xyz456
        createdAt:
          type: string
          description: Date and time when the staff member was created
          example: '2025-05-01T12:00:00Z'
        updatedAt:
          type: string
          description: Date and time when the staff member was last updated
          example: '2025-05-15T14:30:00Z'
        deletedAt:
          type: string
          description: Date and time when the staff member was soft deleted
          example: '2025-06-01T09:15:00Z'
        image:
          description: Profile image of the staff member
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Image'
      required:
        - id
        - storeId
        - userId
        - scheduleId
        - imageId
        - firstName
        - lastName
        - email
        - slug
        - refreshToken
        - createdAt
        - updatedAt
        - deletedAt
    AppointmentLocation:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier of the location
          example: 123e4567-e89b-12d3-a456-426614174000
        storeId:
          type: string
          description: ID of the store that owns this location
          example: 123e4567-e89b-12d3-a456-426614174001
        scheduleId:
          type: string
          description: ID of the schedule assigned to this location (if any)
          example: 123e4567-e89b-12d3-a456-426614174002
          nullable: true
        title:
          type: string
          description: Name of the location
          example: Main Office
          nullable: true
        description:
          type: string
          description: Description of the location
          example: Our main downtown office location with 3 treatment rooms
          nullable: true
        slug:
          type: string
          description: URL-friendly slug for the location (unique per store)
          example: new-york
        type:
          type: string
          description: Type of location
          enum:
            - physical
            - online
            - delivery
          example: physical
        link:
          type: string
          description: URL link for online locations
          example: https://zoom.us/j/123456789
          nullable: true
        addressLine1:
          type: string
          description: First line of address for physical locations
          example: 123 Main Street
          nullable: true
        addressLine2:
          type: string
          description: Second line of address for physical locations
          example: Suite 200
          nullable: true
        city:
          type: string
          description: City for physical locations
          example: San Francisco
          nullable: true
        state:
          type: string
          description: State/province for physical locations
          example: CA
          nullable: true
        postalCode:
          type: string
          description: Postal code for physical locations
          example: '94105'
          nullable: true
        country:
          type: string
          description: Country for physical locations
          example: United States
          nullable: true
        addressPlaceId:
          type: string
          description: Google Place ID for the physical address
          example: ChIJIQBpAG2ahYAR_6128GcTUEo
          nullable: true
        latitude:
          type: number
          description: Latitude in WGS84
          example: 37.4224764
          nullable: true
        longitude:
          type: number
          description: Longitude in WGS84
          example: -122.0842499
          nullable: true
        createdAt:
          type: string
          description: Date when the location was created
          example: '2023-01-20T08:30:00Z'
        updatedAt:
          type: string
          description: Date when the location was last updated
          example: '2023-02-15T14:20:00Z'
          nullable: true
        deletedAt:
          type: string
          description: Date when the location was deleted (for soft deletes)
          example: '2023-03-10T11:45:00Z'
          nullable: true
        address:
          type: string
          description: The formatted address string
          nullable: true
      required:
        - id
        - storeId
        - scheduleId
        - title
        - description
        - slug
        - type
        - link
        - addressLine1
        - addressLine2
        - city
        - state
        - postalCode
        - country
        - addressPlaceId
        - latitude
        - longitude
        - createdAt
        - updatedAt
        - deletedAt
        - address
    AppointmentAddOn:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier
        appointmentId:
          type: string
          description: Appointment ID
        addOnId:
          type: string
          description: Add-on ID
        quantity:
          type: number
          description: Quantity of this add-on for the appointment
          default: 1
        createdAt:
          format: date-time
          type: string
          description: Created at timestamp
        addOn:
          description: The add-on details
          allOf:
            - $ref: '#/components/schemas/AppointmentAddOnItem'
      required:
        - id
        - appointmentId
        - addOnId
        - quantity
        - createdAt
        - addOn
    AppointmentGuest:
      type: object
      properties:
        email:
          type: string
          description: Email address of the guest invited to the appointment
          example: guest@example.com
      required:
        - email
    Image:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier
          example: 123e4567-e89b-12d3-a456-426614174000
        externalId:
          type: string
          description: External identifier for the image (e.g., from storage provider)
          example: img_123456
          nullable: true
        url:
          type: string
          description: URL where the image can be accessed
          example: https://storage.example.com/images/product-image.jpg
        filename:
          type: string
          description: Original filename of the uploaded image
          example: product-image.jpg
          nullable: true
        mime:
          type: string
          description: MIME type of the image
          example: image/jpeg
          nullable: true
        createdAt:
          format: date-time
          type: string
          description: Creation timestamp
          example: '2023-01-01T12:00:00Z'
        updatedAt:
          format: date-time
          type: string
          description: Last update timestamp
          example: '2023-01-02T12:00:00Z'
          nullable: true
        deletedAt:
          format: date-time
          type: string
          description: Soft deletion timestamp
          example: '2023-01-03T12:00:00Z'
          nullable: true
        storeId:
          type: string
          description: ID of the store that owns this image
          example: store-uuid
          nullable: true
      required:
        - id
        - externalId
        - url
        - filename
        - mime
        - createdAt
        - updatedAt
        - deletedAt
        - storeId
    AppointmentAddOnItem:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the add-on
          example: 123e4567-e89b-12d3-a456-426614174000
        externalId:
          type: string
          description: >-
            External platform product ID (e.g. Shopify Product ID) for synced
            add-ons
          example: '1234567890'
        externalVariantId:
          type: string
          description: >-
            External platform variant ID (e.g. Shopify Variant ID) for synced
            add-ons
          example: '9876543210'
        storeId:
          type: string
          description: ID of the store that owns this add-on
          example: 123e4567-e89b-12d3-a456-426614174001
        slug:
          type: string
          description: URL-friendly slug for the add-on (unique per store)
          example: child-seat
        title:
          type: string
          description: Title of the add-on
          example: Child Seat
        description:
          type: string
          description: Detailed description of the add-on
        imageId:
          type: string
          description: ID of the primary image for the add-on
        color:
          type: string
          description: Color code used when displaying the add-on
          enum:
            - slate
            - gray
            - zinc
            - neutral
            - stone
            - red
            - orange
            - amber
            - yellow
            - lime
            - green
            - emerald
            - teal
            - cyan
            - sky
            - blue
            - indigo
            - violet
            - purple
            - fuchsia
            - pink
            - rose
          example: slate
          default: slate
        price:
          type: number
          description: Unit price of the add-on
          example: 15
        taxable:
          type: boolean
          description: Whether the add-on is taxable
          default: true
        durationMultiplied:
          type: boolean
          description: >-
            When true, the add-on is charged per base-duration unit of the
            parent appointment. Quantity on the cart/order line item mirrors the
            parent CartItem.quantity (i.e., the booked duration units). Only
            meaningful for attached products with allowCustomDuration=true.
          default: false
        maxQuantity:
          type: number
          description: >-
            Maximum quantity a customer can pick for this add-on. Only applies
            to fixed add-ons (durationMultiplied=false). Null means unlimited.
          example: 4
          minimum: 1
          nullable: true
        status:
          type: string
          description: Current status of the add-on
          enum:
            - active
            - inactive
          example: active
          default: active
        createdAt:
          type: string
          description: Timestamp when the add-on was created
        updatedAt:
          type: string
          description: Timestamp when the add-on was last updated
        deletedAt:
          type: string
          description: Timestamp when the add-on was soft-deleted
        image:
          description: Primary image of the add-on
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Image'
      required:
        - id
        - externalId
        - externalVariantId
        - storeId
        - slug
        - title
        - description
        - imageId
        - color
        - price
        - taxable
        - durationMultiplied
        - maxQuantity
        - status
        - createdAt
        - updatedAt
        - deletedAt
  securitySchemes:
    Storefront API key:
      type: apiKey
      in: header
      name: x-api-key
      description: Storefront API key (sfk_...)
      x-default: sfk_your_api_key

````