openapi: 3.1.0
info:
  title: Tote Online Ordering API
  version: 1.0.0
  description: |
    REST API for 3rd party developers building online ordering
    integrations with Tote POS convenience stores and fuel stations.

    ## Overview

    The Tote Online Ordering API enables partners to:
    - Authenticate via OAuth 2.0 client credentials
    - Browse store locations and business hours
    - Retrieve full menus with nested modifier groups
    - Create and manage shopping carts
    - Submit orders with split payment support
    - Track order fulfillment status
    - Subscribe to webhook events for real-time updates
    - Check item availability and inventory

    ## Base URL

    The server URLs below are **placeholders**. Your actual API base URL,
    client ID, and client secret are provided during partner onboarding.
    Contact developer@totepos.com to get started.

    Path parameters use snake_case. JSON response fields use snake_case.
    Schema names use PascalCase. Enum values use SCREAMING_SNAKE_CASE.
  contact:
    name: Tote Developer Support
    email: developer@totepos.com
servers:
  - url: https://api.tote.ai/v1/online-ordering
    description: Production (placeholder -- use your assigned base URL)
  - url: https://sandbox.api.tote.ai/v1/online-ordering
    description: Sandbox (placeholder -- use your assigned base URL)
security:
  - oauth2: []
tags:
  - name: Authentication
    description: OAuth 2.0 client credentials token management.
  - name: Locations
    description: Store locations, hours, and capabilities.
  - name: Menus
    description: Menu categories, items, modifier groups, and pricing.
  - name: Carts
    description: Shopping cart creation, item management, pricing, and checkout.
  - name: Orders
    description: Order retrieval, fulfillment tracking, and cancellation.
  - name: Payments
    description: Payment submission, split payments, and refunds.
  - name: Webhook Subscriptions
    description: Webhook subscription management and event delivery.
  - name: Inventory
    description: Item availability and stock status.
  - name: Events
    description: Webhook event payloads delivered to your subscription URLs.
x-tagGroups:
  - name: API
    tags:
      - Authentication
      - Locations
      - Menus
      - Carts
      - Orders
      - Payments
      - Webhook Subscriptions
      - Inventory
  - name: Events
    tags:
      - Events
paths:
  /auth/token:
    post:
      tags:
        - Authentication
      operationId: createToken
      summary: Create an access token
      description: >
        Exchange your client credentials for an access token using the

        OAuth 2.0 client credentials flow.


        **How it works:**

        1. Send your `client_id` and `client_secret` in the request body

        2. Receive a Bearer token valid for 24 hours

        3. Include the token in the `Authorization` header of all subsequent
        requests


        **Token caching:** Cache your token and reuse it until it expires.

        Do not request a new token for every API call -- this will trigger

        rate limiting.


        **Token expiry:** Tokens expire after 86400 seconds (24 hours).

        When your token expires, request a new one. There is no refresh

        token flow -- simply re-authenticate with your client credentials.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
            example:
              grant_type: CLIENT_CREDENTIALS
              client_id: d7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23
              client_secret: sk_live_a1b2c3d4e5f6...
      responses:
        '200':
          description: Token created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
              example:
                access_token: eyJhbGciOiJSUzI1NiIs...
                token_type: BEARER
                expires_in: 86400
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /locations:
    get:
      tags:
        - Locations
      operationId: listLocations
      summary: List all locations
      description: >
        Returns a paginated list of locations accessible to your API
        credentials.

        Use the `online_ordering_enabled` field to filter locations that accept

        online orders.


        **Pagination:** Results are returned in pages. Use the `cursor`
        parameter

        from the `pagination.next_cursor` field to retrieve the next page.
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
      responses:
        '200':
          description: A paginated list of locations.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Location'
                    description: List of locations.
                  pagination:
                    $ref: '#/components/schemas/Pagination'
              example:
                data:
                  - id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: 'QuickStop #247'
                    address:
                      street: 4521 Congress Ave
                      city: Austin
                      state: TX
                      postal_code: '78745'
                      country: US
                    phone: '+15125551234'
                    timezone: America/Chicago
                    online_ordering_enabled: true
                    business_hours:
                      - day_of_week: MONDAY
                        open_time: '06:00'
                        close_time: '23:00'
                        is_closed: false
                      - day_of_week: TUESDAY
                        open_time: '06:00'
                        close_time: '23:00'
                        is_closed: false
                      - day_of_week: WEDNESDAY
                        open_time: '06:00'
                        close_time: '23:00'
                        is_closed: false
                      - day_of_week: THURSDAY
                        open_time: '06:00'
                        close_time: '23:00'
                        is_closed: false
                      - day_of_week: FRIDAY
                        open_time: '06:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: SATURDAY
                        open_time: '07:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: SUNDAY
                        open_time: '08:00'
                        close_time: '22:00'
                        is_closed: false
                  - id: b2c3d4e5-f6a7-8901-bcde-f12345678901
                    name: 'QuickStop #512'
                    address:
                      street: 2200 S Lamar Blvd
                      city: Austin
                      state: TX
                      postal_code: '78704'
                      country: US
                    phone: '+15125559876'
                    timezone: America/Chicago
                    online_ordering_enabled: false
                    business_hours:
                      - day_of_week: MONDAY
                        open_time: '00:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: TUESDAY
                        open_time: '00:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: WEDNESDAY
                        open_time: '00:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: THURSDAY
                        open_time: '00:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: FRIDAY
                        open_time: '00:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: SATURDAY
                        open_time: '00:00'
                        close_time: '00:00'
                        is_closed: false
                      - day_of_week: SUNDAY
                        open_time: '00:00'
                        close_time: '00:00'
                        is_closed: true
                pagination:
                  has_more: true
                  next_cursor: eyJpZCI6ImIyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYxMjM0NTY3ODkwMSJ9
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /locations/{location_id}:
    get:
      tags:
        - Locations
      operationId: getLocation
      summary: Get a location by ID
      description: >
        Returns a single location by its unique identifier. The response
        includes

        the full location details including address, business hours, and whether

        online ordering is enabled.
      parameters:
        - $ref: '#/components/parameters/locationId'
      responses:
        '200':
          description: The requested location.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Location'
              example:
                id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                name: 'QuickStop #247'
                address:
                  street: 4521 Congress Ave
                  city: Austin
                  state: TX
                  postal_code: '78745'
                  country: US
                phone: '+15125551234'
                timezone: America/Chicago
                online_ordering_enabled: true
                business_hours:
                  - day_of_week: MONDAY
                    open_time: '06:00'
                    close_time: '23:00'
                    is_closed: false
                  - day_of_week: TUESDAY
                    open_time: '06:00'
                    close_time: '23:00'
                    is_closed: false
                  - day_of_week: WEDNESDAY
                    open_time: '06:00'
                    close_time: '23:00'
                    is_closed: false
                  - day_of_week: THURSDAY
                    open_time: '06:00'
                    close_time: '23:00'
                    is_closed: false
                  - day_of_week: FRIDAY
                    open_time: '06:00'
                    close_time: '00:00'
                    is_closed: false
                  - day_of_week: SATURDAY
                    open_time: '07:00'
                    close_time: '00:00'
                    is_closed: false
                  - day_of_week: SUNDAY
                    open_time: '08:00'
                    close_time: '22:00'
                    is_closed: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /locations/{location_id}/menu:
    get:
      tags:
        - Menus
      operationId: getMenu
      summary: Get the full menu for a location
      description: >
        Returns the complete menu for a location, including all categories,

        items, and nested modifier groups.


        **Menu structure:**

        - A menu contains **categories** (e.g., "Deli & Sandwiches",
        "Beverages")

        - Each category contains **items** (e.g., "Build Your Own Sub Sandwich")

        - Each item may have **modifier groups** (e.g., "Bread Choice",
        "Protein")

        - Each modifier group contains **modifiers** (e.g., "White", "Wheat")

        - Modifiers can have nested **modifier groups** (up to 3 levels deep)


        **Caching:** Use the `version_hash` field to detect menu changes. For

        lightweight change detection without downloading the full menu, use

        the GET /locations/{location_id}/menu/metadata endpoint.


        **Response size:** Menu responses can be large (100KB+). Cache
        aggressively

        and use the metadata endpoint for polling.
      parameters:
        - $ref: '#/components/parameters/locationId'
      responses:
        '200':
          description: The complete menu for the location.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Menu'
              example:
                location_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                last_modified: '2026-01-15T14:30:00Z'
                version_hash: sha256:a1b2c3d4e5f6
                categories:
                  - id: c0000001-0000-0000-0000-000000000001
                    name: Deli & Sandwiches
                    description: Fresh-made sandwiches and deli items.
                    sort_order: 1
                    items:
                      - id: d0000001-0000-0000-0000-000000000001
                        name: Build Your Own Sub Sandwich
                        description: Choose your bread, protein, and toppings.
                        base_price:
                          amount: 899
                          currency: USD
                        image_url: https://cdn.tote.ai/images/items/sub-sandwich.jpg
                        available: true
                        modifier_groups:
                          - id: mg000001-0000-0000-0000-000000000001
                            name: Bread Choice
                            min_selections: 1
                            max_selections: 1
                            allows_duplicates: false
                            modifiers:
                              - id: m0000001-0000-0000-0000-000000000001
                                name: White
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: true
                                modifier_groups: []
                              - id: m0000001-0000-0000-0000-000000000002
                                name: Wheat
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000001-0000-0000-0000-000000000003
                                name: Italian Herb & Cheese
                                price_adjustment:
                                  amount: 75
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                          - id: mg000001-0000-0000-0000-000000000002
                            name: Protein
                            min_selections: 1
                            max_selections: 2
                            allows_duplicates: false
                            modifiers:
                              - id: m0000002-0000-0000-0000-000000000001
                                name: Turkey
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000002-0000-0000-0000-000000000002
                                name: Ham
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000002-0000-0000-0000-000000000003
                                name: Steak
                                price_adjustment:
                                  amount: 200
                                  currency: USD
                                is_default: false
                                modifier_groups:
                                  - id: mg000002-0000-0000-0000-000000000001
                                    name: Steak Preparation
                                    min_selections: 1
                                    max_selections: 1
                                    allows_duplicates: false
                                    modifiers:
                                      - id: m0000003-0000-0000-0000-000000000001
                                        name: Rare
                                        price_adjustment:
                                          amount: 0
                                          currency: USD
                                        is_default: false
                                        modifier_groups: []
                                      - id: m0000003-0000-0000-0000-000000000002
                                        name: Medium
                                        price_adjustment:
                                          amount: 0
                                          currency: USD
                                        is_default: true
                                        modifier_groups:
                                          - id: mg000003-0000-0000-0000-000000000001
                                            name: Sauce
                                            min_selections: 0
                                            max_selections: 2
                                            allows_duplicates: false
                                            modifiers:
                                              - id: m0000004-0000-0000-0000-000000000001
                                                name: A1 Steak Sauce
                                                price_adjustment:
                                                  amount: 0
                                                  currency: USD
                                                is_default: false
                                                modifier_groups: []
                                              - id: m0000004-0000-0000-0000-000000000002
                                                name: Peppercorn Ranch
                                                price_adjustment:
                                                  amount: 50
                                                  currency: USD
                                                is_default: false
                                                modifier_groups: []
                                              - id: m0000004-0000-0000-0000-000000000003
                                                name: Chimichurri
                                                price_adjustment:
                                                  amount: 75
                                                  currency: USD
                                                is_default: false
                                                modifier_groups: []
                                      - id: m0000003-0000-0000-0000-000000000003
                                        name: Well Done
                                        price_adjustment:
                                          amount: 0
                                          currency: USD
                                        is_default: false
                                        modifier_groups: []
                          - id: mg000001-0000-0000-0000-000000000003
                            name: Toppings
                            min_selections: 0
                            max_selections: 5
                            allows_duplicates: false
                            modifiers:
                              - id: m0000005-0000-0000-0000-000000000001
                                name: Lettuce
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000005-0000-0000-0000-000000000002
                                name: Tomato
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000005-0000-0000-0000-000000000003
                                name: Pickles
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000005-0000-0000-0000-000000000004
                                name: Jalapenos
                                price_adjustment:
                                  amount: 50
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000005-0000-0000-0000-000000000005
                                name: Avocado
                                price_adjustment:
                                  amount: 150
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                        age_verification_required: false
                        minimum_age: null
                        daily_sales_start_time: null
                        daily_sales_end_time: null
                        max_allowed_quantity: null
                        tender_restriction_level: null
                        allowed_tenders: null
                  - id: c0000001-0000-0000-0000-000000000002
                    name: Beverages
                    description: Cold and hot drinks.
                    sort_order: 2
                    items:
                      - id: d0000002-0000-0000-0000-000000000001
                        name: Bottled Water
                        description: 16.9 oz purified water.
                        base_price:
                          amount: 199
                          currency: USD
                        image_url: https://cdn.tote.ai/images/items/bottled-water.jpg
                        available: true
                        modifier_groups: []
                        age_verification_required: false
                        minimum_age: null
                        daily_sales_start_time: null
                        daily_sales_end_time: null
                        max_allowed_quantity: null
                        tender_restriction_level: null
                        allowed_tenders: null
                      - id: d0000002-0000-0000-0000-000000000002
                        name: Fresh Brewed Coffee
                        description: House blend, available in multiple sizes.
                        base_price:
                          amount: 179
                          currency: USD
                        image_url: https://cdn.tote.ai/images/items/coffee.jpg
                        available: true
                        modifier_groups:
                          - id: mg000004-0000-0000-0000-000000000001
                            name: Size
                            min_selections: 1
                            max_selections: 1
                            allows_duplicates: false
                            modifiers:
                              - id: m0000006-0000-0000-0000-000000000001
                                name: Small (12 oz)
                                price_adjustment:
                                  amount: 0
                                  currency: USD
                                is_default: true
                                modifier_groups: []
                              - id: m0000006-0000-0000-0000-000000000002
                                name: Medium (16 oz)
                                price_adjustment:
                                  amount: 50
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                              - id: m0000006-0000-0000-0000-000000000003
                                name: Large (20 oz)
                                price_adjustment:
                                  amount: 100
                                  currency: USD
                                is_default: false
                                modifier_groups: []
                        age_verification_required: false
                        minimum_age: null
                        daily_sales_start_time: '05:00'
                        daily_sales_end_time: '14:00'
                        max_allowed_quantity: null
                        tender_restriction_level: RESTRICTED
                        allowed_tenders:
                          - CREDIT_CARD
                          - DEBIT_CARD
                          - CASH
                          - GIFT_CARD
                          - LOYALTY_POINTS
                          - DIGITAL_WALLET
                  - id: c0000001-0000-0000-0000-000000000003
                    name: Tobacco & Vape
                    description: Age-restricted tobacco and vape products.
                    sort_order: 3
                    items:
                      - id: d0000003-0000-0000-0000-000000000001
                        name: Premium Cigars
                        description: Assorted premium hand-rolled cigars.
                        base_price:
                          amount: 1499
                          currency: USD
                        image_url: https://cdn.tote.ai/images/items/premium-cigars.jpg
                        available: true
                        modifier_groups: []
                        age_verification_required: true
                        minimum_age: 21
                        daily_sales_start_time: null
                        daily_sales_end_time: null
                        max_allowed_quantity: 2
                        tender_restriction_level: RESTRICTED
                        allowed_tenders:
                          - CREDIT_CARD
                          - DEBIT_CARD
                          - CASH
                          - DIGITAL_WALLET
                      - id: d0000003-0000-0000-0000-000000000002
                        name: Cigarettes - Variety Pack
                        description: Select brand variety pack, 20 count.
                        base_price:
                          amount: 1099
                          currency: USD
                        image_url: null
                        available: true
                        modifier_groups: []
                        age_verification_required: true
                        minimum_age: 21
                        daily_sales_start_time: null
                        daily_sales_end_time: null
                        max_allowed_quantity: 4
                        tender_restriction_level: RESTRICTED
                        allowed_tenders:
                          - CREDIT_CARD
                          - DEBIT_CARD
                          - CASH
                          - DIGITAL_WALLET
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /locations/{location_id}/menu/metadata:
    get:
      tags:
        - Menus
      operationId: getMenuMetadata
      summary: Get menu metadata for change detection
      description: |
        Returns lightweight metadata about a location's menu, including the
        last modification timestamp and a version hash.

        **Use case:** Poll this endpoint to detect menu changes without
        downloading the full menu payload. Compare the `version_hash` with
        your cached version -- if they differ, re-fetch the full menu.

        **Recommended polling interval:** Every 5 minutes during business
        hours, every 30 minutes outside business hours.
      parameters:
        - $ref: '#/components/parameters/locationId'
      responses:
        '200':
          description: Menu metadata for the location.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MenuMetadata'
              example:
                location_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                last_modified: '2026-01-15T14:30:00Z'
                version_hash: sha256:a1b2c3d4e5f6
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts:
    post:
      tags:
        - Carts
      operationId: createCart
      summary: Create a new cart
      description: >
        Creates an empty cart for the specified location. The cart is
        initialized

        with ACTIVE status and zero totals. Items are added via

        POST /carts/{cart_id}/items.


        **Idempotency:** This endpoint supports idempotent requests. Include an

        Idempotency-Key header (UUID v4) to safely retry requests. Successful

        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - location_id
              properties:
                location_id:
                  type: string
                  format: uuid
                  description: |
                    The location this cart is for. Must reference an active
                    location with online_ordering_enabled: true.
                customer_id:
                  type:
                    - string
                    - 'null'
                  description: >
                    Optional partner-provided customer identifier. When set, the
                    server

                    applies member-specific pricing during price calculation.
                    Can also

                    be set or updated later via PATCH /carts/{cart_id}.
                  maxLength: 128
                  example: CUST-12345
            example:
              location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
      responses:
        '201':
          description: Cart created successfully.
          headers:
            Location:
              description: URL of the newly created cart resource.
              schema:
                type: string
                format: uri
              example: /carts/c1d2e3f4-a5b6-7890-cdef-1234567890ab
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}:
    get:
      tags:
        - Carts
      operationId: getCart
      summary: Get cart details
      description: |
        Retrieves the current state of a cart, including all items, computed
        totals, and status. Returns the cart with server-computed subtotal,
        total_tax, total_discount, and total.
      parameters:
        - $ref: '#/components/parameters/cartId'
      responses:
        '200':
          description: The requested cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
              example:
                id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                status: ACTIVE
                items:
                  - id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    item_total:
                      amount: 1399
                      currency: USD
                    modifier_selections:
                      - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                        modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                        quantity: 1
                        nested_selections: []
                      - modifier_group_id: b3c4d5e6-f7a8-9012-cdef-123456789012
                        modifier_id: c4d5e6f7-a8b9-0123-def0-234567890123
                        quantity: 1
                        nested_selections:
                          - modifier_group_id: d5e6f7a8-b9c0-1234-ef01-345678901234
                            modifier_id: e6f7a8b9-c0d1-2345-f012-456789012345
                            quantity: 1
                            nested_selections: []
                    age_verification_required: false
                    minimum_age: null
                  - id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                    menu_item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    quantity: 2
                    base_price:
                      amount: 199
                      currency: USD
                    modifier_total:
                      amount: 0
                      currency: USD
                    item_total:
                      amount: 398
                      currency: USD
                    modifier_selections: []
                    age_verification_required: false
                    minimum_age: null
                age_verification_required: false
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 148
                  currency: USD
                total_discount:
                  amount: 0
                  currency: USD
                total:
                  amount: 1945
                  currency: USD
                created_at: '2026-01-31T10:00:00Z'
                updated_at: '2026-01-31T10:05:30Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Carts
      operationId: deleteCart
      summary: Abandon a cart
      description: |
        Marks a cart as ABANDONED. This is a soft delete -- the cart data is
        retained but can no longer be modified or checked out. Only ACTIVE
        carts can be abandoned.

        **Idempotency:** This endpoint supports idempotent requests. Include an
        Idempotency-Key header (UUID v4) to safely retry requests. Successful
        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          description: Cart abandoned successfully. No content.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    patch:
      tags:
        - Carts
      operationId: updateCart
      summary: Update cart properties
      description: |
        Partially updates a cart's properties. Currently supports updating
        the customer_id field. The cart must be in ACTIVE status.

        **Customer identity:** Setting customer_id enables the server to
        apply member-specific pricing on the next /calculate call. Setting
        customer_id to null reverts to anonymous pricing. After changing
        customer_id, re-calculate prices -- previous pricing may be stale.

        **Idempotency:** This endpoint supports idempotent requests. Include an
        Idempotency-Key header (UUID v4) to safely retry requests.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type:
                    - string
                    - 'null'
                  description: |
                    Partner-provided customer identifier. Set to null to
                    remove customer identity and revert to anonymous pricing.
                  maxLength: 128
                  example: CUST-12345
      responses:
        '200':
          description: Cart updated. Returns the full updated cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/items:
    post:
      tags:
        - Carts
      operationId: addCartItem
      summary: Add an item to the cart
      description: >
        Adds a menu item with optional modifier selections to the cart. The
        server

        validates that the menu item exists, is available, and that modifier

        selections satisfy the min_selections/max_selections constraints of each

        modifier group.


        Age-restricted items can be added freely. The cart's

        age_verification_required flag will be set to true, and the consumer

        will be informed that verification is required at pickup or delivery.


        The response includes the updated cart with recalculated totals.


        **Idempotency:** This endpoint supports idempotent requests. Include an

        Idempotency-Key header (UUID v4) to safely retry requests. Successful

        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CartItemRequest'
            example:
              menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              quantity: 1
              modifier_selections:
                - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                  modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                  quantity: 1
                  nested_selections: []
                - modifier_group_id: b3c4d5e6-f7a8-9012-cdef-123456789012
                  modifier_id: c4d5e6f7-a8b9-0123-def0-234567890123
                  quantity: 1
                  nested_selections:
                    - modifier_group_id: d5e6f7a8-b9c0-1234-ef01-345678901234
                      modifier_id: e6f7a8b9-c0d1-2345-f012-456789012345
                      quantity: 1
                      nested_selections: []
      responses:
        '201':
          description: Item added to the cart. Returns the updated cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
              example:
                id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                status: ACTIVE
                items:
                  - id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    item_total:
                      amount: 1399
                      currency: USD
                    modifier_selections:
                      - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                        modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                        quantity: 1
                        nested_selections: []
                      - modifier_group_id: b3c4d5e6-f7a8-9012-cdef-123456789012
                        modifier_id: c4d5e6f7-a8b9-0123-def0-234567890123
                        quantity: 1
                        nested_selections:
                          - modifier_group_id: d5e6f7a8-b9c0-1234-ef01-345678901234
                            modifier_id: e6f7a8b9-c0d1-2345-f012-456789012345
                            quantity: 1
                            nested_selections: []
                    age_verification_required: false
                    minimum_age: null
                  - id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                    menu_item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    quantity: 2
                    base_price:
                      amount: 199
                      currency: USD
                    modifier_total:
                      amount: 0
                      currency: USD
                    item_total:
                      amount: 398
                      currency: USD
                    modifier_selections: []
                    age_verification_required: false
                    minimum_age: null
                age_verification_required: false
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 148
                  currency: USD
                total_discount:
                  amount: 0
                  currency: USD
                total:
                  amount: 1945
                  currency: USD
                created_at: '2026-01-31T10:00:00Z'
                updated_at: '2026-01-31T10:05:30Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/items/{item_id}:
    put:
      tags:
        - Carts
      operationId: updateCartItem
      summary: Update a cart item
      description: >
        Updates the quantity and/or modifier selections of an existing cart
        item.

        The full CartItemRequest must be provided (this is a full replacement,

        not a patch). The server re-validates modifier constraints against the

        current menu.


        The response includes the updated cart with recalculated totals.


        **Idempotency:** This endpoint supports idempotent requests. Include an

        Idempotency-Key header (UUID v4) to safely retry requests. Successful

        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/cartItemId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CartItemRequest'
      responses:
        '200':
          description: Cart item updated. Returns the updated cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
              example:
                id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                status: ACTIVE
                items:
                  - id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    item_total:
                      amount: 1399
                      currency: USD
                    modifier_selections:
                      - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                        modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                        quantity: 1
                        nested_selections: []
                      - modifier_group_id: b3c4d5e6-f7a8-9012-cdef-123456789012
                        modifier_id: c4d5e6f7-a8b9-0123-def0-234567890123
                        quantity: 1
                        nested_selections:
                          - modifier_group_id: d5e6f7a8-b9c0-1234-ef01-345678901234
                            modifier_id: e6f7a8b9-c0d1-2345-f012-456789012345
                            quantity: 1
                            nested_selections: []
                    age_verification_required: false
                    minimum_age: null
                  - id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                    menu_item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    quantity: 2
                    base_price:
                      amount: 199
                      currency: USD
                    modifier_total:
                      amount: 0
                      currency: USD
                    item_total:
                      amount: 398
                      currency: USD
                    modifier_selections: []
                    age_verification_required: false
                    minimum_age: null
                age_verification_required: false
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 148
                  currency: USD
                total_discount:
                  amount: 0
                  currency: USD
                total:
                  amount: 1945
                  currency: USD
                created_at: '2026-01-31T10:00:00Z'
                updated_at: '2026-01-31T10:05:30Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Carts
      operationId: removeCartItem
      summary: Remove an item from the cart
      description: |
        Removes a specific item from the cart. The response includes the
        updated cart with recalculated totals.

        **Idempotency:** This endpoint supports idempotent requests. Include an
        Idempotency-Key header (UUID v4) to safely retry requests. Successful
        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/cartItemId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Cart item removed. Returns the updated cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/calculate:
    post:
      tags:
        - Carts
      operationId: calculateCart
      summary: Calculate cart prices
      description: >
        Returns an itemized price breakdown for the cart without creating or
        modifying

        an order. This is a **read-only preview** -- it computes prices at
        request time

        based on the current menu, tax configuration, and applicable discounts.


        **Important:** Prices may change between calculate and checkout if the
        menu or

        tax configuration is updated. Always call calculate immediately before
        checkout

        to show the customer the most accurate total.


        This endpoint is analogous to Square's CalculateOrder and Toast's
        /prices endpoint.
      parameters:
        - $ref: '#/components/parameters/cartId'
      responses:
        '200':
          description: Price calculation completed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PriceCalculation'
              example:
                cart_id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                currency: USD
                line_items:
                  - cart_item_id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    discounts: []
                    item_subtotal:
                      amount: 1399
                      currency: USD
                    item_tax:
                      amount: 115
                      currency: USD
                    item_total:
                      amount: 1514
                      currency: USD
                  - cart_item_id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                    menu_item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    quantity: 2
                    base_price:
                      amount: 199
                      currency: USD
                    modifier_total:
                      amount: 0
                      currency: USD
                    discounts: []
                    item_subtotal:
                      amount: 398
                      currency: USD
                    item_tax:
                      amount: 33
                      currency: USD
                    item_total:
                      amount: 431
                      currency: USD
                discounts: []
                fees: []
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 148
                  currency: USD
                total_discount:
                  amount: 0
                  currency: USD
                total_fees:
                  amount: 0
                  currency: USD
                taxable_amount:
                  amount: 1797
                  currency: USD
                total:
                  amount: 1945
                  currency: USD
                age_verification_required: false
                calculated_at: '2026-01-31T10:06:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/checkout:
    post:
      tags:
        - Carts
      operationId: checkoutCart
      summary: Check out a cart
      description: >
        Converts an ACTIVE cart into an order. The cart must have at least one
        item

        and a handoff mode must be available (either stored on the cart via

        PUT /carts/{cart_id}/handoff, or provided as an override in the request
        body).


        **Validation performed at checkout:**

        - Cart is in ACTIVE status

        - Cart has at least one item

        - All items are still available in the current menu

        - Modifier selections satisfy group constraints (min/max)

        - Handoff mode exists (stored on cart OR provided in request body)


        **Age verification:** If the cart contains age-restricted items,
        checkout

        succeeds but the response includes `age_verification_required: true` and

        an `age_verification_notice` explaining that the consumer will be
        verified

        at pickup or delivery.


        **Price changes:** If `expected_total` is provided and the current total

        differs, the server returns 409 Conflict. Omit `expected_total` to
        accept

        the current price.


        After successful checkout, the cart status changes to CHECKED_OUT and

        can no longer be modified. The created order is returned with status
        PENDING.


        **Idempotency:** This endpoint supports idempotent requests. Include an

        Idempotency-Key header (UUID v4) to safely retry requests. If the cart

        has already been checked out with the same idempotency key, the original

        order is returned.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CheckoutRequest'
            examples:
              curbside:
                summary: Curbside pickup with optimistic concurrency
                value:
                  handoff_mode:
                    mode: CURBSIDE
                    vehicle_make: Toyota
                    vehicle_model: Camry
                    vehicle_color: Silver
                    pickup_time: null
                  expected_total: 1945
                  notes: null
              delivery:
                summary: Delivery with address and notes
                value:
                  handoff_mode:
                    mode: DELIVERY
                    delivery_address:
                      street: 123 Main St, Apt 4B
                      city: Austin
                      state: TX
                      postal_code: '78701'
                    delivery_instructions: Leave at the front door
                  expected_total: 1945
                  notes: No onions on the sandwich please
      responses:
        '201':
          description: |
            Order created successfully. The cart status is now CHECKED_OUT.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutResponse'
              example:
                id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                cart_id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                status: PENDING
                payment_status: UNPAID
                fulfillment_status: PENDING
                items:
                  - id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    item_total:
                      amount: 1399
                      currency: USD
                    modifier_selections:
                      - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                        modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                        quantity: 1
                        nested_selections: []
                      - modifier_group_id: b3c4d5e6-f7a8-9012-cdef-123456789012
                        modifier_id: c4d5e6f7-a8b9-0123-def0-234567890123
                        quantity: 1
                        nested_selections:
                          - modifier_group_id: d5e6f7a8-b9c0-1234-ef01-345678901234
                            modifier_id: e6f7a8b9-c0d1-2345-f012-456789012345
                            quantity: 1
                            nested_selections: []
                    age_verification_required: false
                    minimum_age: null
                  - id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                    menu_item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    quantity: 2
                    base_price:
                      amount: 199
                      currency: USD
                    modifier_total:
                      amount: 0
                      currency: USD
                    item_total:
                      amount: 398
                      currency: USD
                    modifier_selections: []
                    age_verification_required: false
                    minimum_age: null
                payments: []
                discounts: []
                handoff:
                  mode: CURBSIDE
                  vehicle_make: Toyota
                  vehicle_model: Camry
                  vehicle_color: Silver
                  pickup_time: null
                notes: null
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 148
                  currency: USD
                total_discount:
                  amount: 0
                  currency: USD
                total:
                  amount: 1945
                  currency: USD
                total_paid:
                  amount: 0
                  currency: USD
                balance_due:
                  amount: 1945
                  currency: USD
                age_verification_required: false
                age_verification_notice: null
                created_at: '2026-01-31T10:07:00Z'
                updated_at: '2026-01-31T10:07:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/handoff:
    put:
      tags:
        - Carts
      operationId: setCartHandoff
      summary: Set or update the cart's handoff mode
      description: >
        Sets how the customer wants to receive their order. The handoff mode is
        stored

        on the cart and used at checkout. Can be called multiple times to change
        the mode

        before checkout (e.g., customer switches from PICKUP to DELIVERY).


        The response returns the updated cart with the stored handoff mode.


        **Idempotency:** This endpoint supports idempotent requests. Include an

        Idempotency-Key header (UUID v4) to safely retry requests. Successful

        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HandoffMode'
      responses:
        '200':
          description: Handoff mode set successfully. Returns the updated cart.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/promo-codes:
    post:
      tags:
        - Carts
      operationId: applyPromoCode
      summary: Apply a promo code to a cart
      description: >
        Applies a promo code to the cart. The server validates the code, checks

        eligibility (expiry, minimum order amount, applicable items), and
        applies

        the discount. Returns the updated Cart with the promo in the
        `promo_codes`

        array and the discount reflected in totals.


        **One promo at a time:** Only one promo code can be active on a cart. If
        a

        promo code is already applied, the server returns 409 Conflict. Remove
        the

        existing code first via DELETE /carts/{cart_id}/promo-codes/{code}.


        **Case-insensitive:** Codes are case-insensitive on input. "summer25"
        and

        "SUMMER25" are treated identically. The code is always returned as
        uppercase.


        **Discount visibility:** The promo discount appears in the
        PriceCalculation

        `discounts` array as a DiscountLineItem with `source: PROMO_CODE`.


        **Idempotency:** This endpoint supports idempotent requests. Include an

        Idempotency-Key header (UUID v4) to safely retry requests. Successful

        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - code
              properties:
                code:
                  type: string
                  description: |
                    The promo code to apply. Case-insensitive.
                  maxLength: 30
                  example: SUMMER25
            example:
              code: SUMMER25
      responses:
        '200':
          description: |
            Promo code applied. Returns the updated cart with the promo in
            `promo_codes` and the discount reflected in totals.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
              example:
                id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                status: ACTIVE
                items:
                  - id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    item_total:
                      amount: 1399
                      currency: USD
                    modifier_selections:
                      - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                        modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                        quantity: 1
                        nested_selections: []
                    age_verification_required: false
                    minimum_age: null
                  - id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                    menu_item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    quantity: 2
                    base_price:
                      amount: 199
                      currency: USD
                    modifier_total:
                      amount: 0
                      currency: USD
                    item_total:
                      amount: 398
                      currency: USD
                    modifier_selections: []
                    age_verification_required: false
                    minimum_age: null
                age_verification_required: false
                promo_codes:
                  - code: SUMMER25
                    status: ACTIVE
                    discount_preview:
                      estimated_discount:
                        amount: 250
                        currency: USD
                      description: 25% off your order (up to $10)
                    applied_at: '2026-01-31T10:06:00Z'
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 127
                  currency: USD
                total_discount:
                  amount: 250
                  currency: USD
                total:
                  amount: 1674
                  currency: USD
                created_at: '2026-01-31T10:00:00Z'
                updated_at: '2026-01-31T10:06:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      tags:
        - Carts
      operationId: listPromoCodes
      summary: List promo codes on a cart
      description: |
        Returns the promo codes currently applied to the cart. This information
        is also included in the Cart response, but this endpoint is useful for
        checking promo status without fetching the full cart.
      parameters:
        - $ref: '#/components/parameters/cartId'
      responses:
        '200':
          description: List of promo codes on the cart.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PromoCode'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/promo-codes/validate:
    get:
      tags:
        - Carts
      operationId: validatePromoCode
      summary: Validate a promo code without applying
      description: >
        Checks if a promo code is valid for this cart without applying it.
        Returns

        a discount preview (estimated amount, description, applicable items) for

        valid codes, or a rejection reason and message for invalid codes.


        Use this endpoint to show the customer what discount they would receive

        before they commit to applying the code.


        **Note:** The discount preview is an estimate. The actual discount is

        computed when the code is applied via POST /carts/{cart_id}/promo-codes.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - name: code
          in: query
          required: true
          schema:
            type: string
            maxLength: 30
          description: The promo code to validate (case-insensitive).
          example: SUMMER25
      responses:
        '200':
          description: Validation result with discount preview or rejection reason.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PromoCodeValidation'
              example:
                code: SUMMER25
                valid: true
                discount_preview:
                  estimated_discount:
                    amount: 250
                    currency: USD
                  description: 25% off your order (up to $10)
                  applicable_items: []
                rejection_reason: null
                rejection_message: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /carts/{cart_id}/promo-codes/{code}:
    delete:
      tags:
        - Carts
      operationId: removePromoCode
      summary: Remove a promo code from a cart
      description: >
        Removes the specified promo code from the cart. The associated discount

        is removed and totals are recalculated. Returns the updated Cart with
        the

        promo removed from `promo_codes` and the discount removed from totals.


        The code path parameter is case-insensitive -- the server normalizes it

        before matching.


        **Idempotency:** This endpoint supports idempotent requests. Include an

        Idempotency-Key header (UUID v4) to safely retry requests. Successful

        responses are cached for 24 hours; error responses are not cached.
      parameters:
        - $ref: '#/components/parameters/cartId'
        - name: code
          in: path
          required: true
          schema:
            type: string
          description: The promo code to remove (case-insensitive).
          example: SUMMER25
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: |
            Promo code removed. Returns the updated cart without the promo.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Cart'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /orders:
    get:
      tags:
        - Orders
      operationId: listOrders
      summary: List orders
      description: >
        Returns a paginated list of orders accessible to your API credentials.

        Results are ordered by creation date (newest first).


        **Pagination:** Results use cursor-based pagination. Use the `cursor`

        parameter from the `pagination.next_cursor` field to retrieve the next
        page.


        **Filtering:** Combine filters to narrow results. All filters are
        optional

        and applied with AND logic:

        - `status`: Filter by order lifecycle status (e.g., CONFIRMED,
        COMPLETED).

        - `fulfillment_status`: Filter by fulfillment stage (e.g., PREPARING,
        READY_FOR_PICKUP).

        - `location_id`: Filter by store location.

        - `customer_id`: Filter by customer identifier (exact match).

        - `date_from` / `date_to`: Filter by creation date range.


        **Response format:** Returns OrderSummary objects (lightweight, no items

        or payments arrays). Use GET /orders/{order_id} for the full order
        detail.
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/orderStatus'
        - $ref: '#/components/parameters/fulfillmentStatus'
        - $ref: '#/components/parameters/locationIdFilter'
        - $ref: '#/components/parameters/dateFrom'
        - $ref: '#/components/parameters/dateTo'
        - $ref: '#/components/parameters/customerIdFilter'
      responses:
        '200':
          description: A paginated list of order summaries.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/OrderSummary'
                    description: List of order summaries.
                  pagination:
                    $ref: '#/components/schemas/Pagination'
              example:
                data:
                  - id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                    location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                    status: CONFIRMED
                    payment_status: PAID
                    fulfillment_status: PREPARING
                    item_count: 2
                    handoff_mode: CURBSIDE
                    total:
                      amount: 1945
                      currency: USD
                    total_paid:
                      amount: 1945
                      currency: USD
                    balance_due:
                      amount: 0
                      currency: USD
                    created_at: '2026-01-31T10:07:00Z'
                    updated_at: '2026-01-31T10:09:00Z'
                  - id: b2c3d4e5-f6a7-8901-bcde-f12345678902
                    location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                    status: COMPLETED
                    payment_status: PAID
                    fulfillment_status: FULFILLED
                    item_count: 1
                    handoff_mode: PICKUP
                    total:
                      amount: 599
                      currency: USD
                    total_paid:
                      amount: 599
                      currency: USD
                    balance_due:
                      amount: 0
                      currency: USD
                    created_at: '2026-01-30T14:22:00Z'
                    updated_at: '2026-01-30T14:45:00Z'
                pagination:
                  has_more: true
                  next_cursor: eyJpZCI6ImIyYzNkNGU1LWY2YTctODkwMS1iY2RlLWYxMjM0NTY3ODkwMiJ9
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /orders/{order_id}:
    get:
      tags:
        - Orders
      operationId: getOrder
      summary: Get order details
      description: >
        Returns the full order resource including line items, payments, all
        three

        status fields, financial totals, and handoff details.


        **Status fields:**

        - `status`: Order lifecycle (PENDING, CONFIRMED, COMPLETED, etc.)

        - `payment_status`: Aggregate payment state (UNPAID, PROCESSING,
        PARTIALLY_PAID, PAID)

        - `fulfillment_status`: Physical fulfillment progress (PENDING through
        FULFILLED/DELIVERED)


        **Polling for updates:** After submitting a payment, poll this endpoint

        to check the updated `payment_status` and `balance_due`. Recommended

        polling interval: 2 seconds, with exponential backoff up to 30 seconds.

        Stop polling when `payment_status` reaches PAID or the payment reaches a

        terminal state.


        **Payments array:** The `payments` array contains all payments attached
        to

        this order, including their individual statuses. Use this to display

        payment history and determine if additional payments are needed for

        split-pay scenarios.
      parameters:
        - $ref: '#/components/parameters/orderId'
      responses:
        '200':
          description: The full order resource.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
              example:
                id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                cart_id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                status: CONFIRMED
                payment_status: PAID
                fulfillment_status: PREPARING
                items:
                  - id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    item_total:
                      amount: 1399
                      currency: USD
                    modifier_selections:
                      - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                        modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                        quantity: 1
                        nested_selections: []
                      - modifier_group_id: b3c4d5e6-f7a8-9012-cdef-123456789012
                        modifier_id: c4d5e6f7-a8b9-0123-def0-234567890123
                        quantity: 1
                        nested_selections:
                          - modifier_group_id: d5e6f7a8-b9c0-1234-ef01-345678901234
                            modifier_id: e6f7a8b9-c0d1-2345-f012-456789012345
                            quantity: 1
                            nested_selections: []
                    age_verification_required: false
                    minimum_age: null
                  - id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                    menu_item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    quantity: 2
                    base_price:
                      amount: 199
                      currency: USD
                    modifier_total:
                      amount: 0
                      currency: USD
                    item_total:
                      amount: 398
                      currency: USD
                    modifier_selections: []
                    age_verification_required: false
                    minimum_age: null
                payments:
                  - id: a1b2c3d4-0001-4000-8000-000000000001
                    order_id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                    status: COMPLETED
                    payment_method: CREDIT_CARD
                    amount:
                      amount: 1945
                      currency: USD
                    tip_amount:
                      amount: 200
                      currency: USD
                    payment_details:
                      last_four: '4242'
                      brand: visa
                      exp_month: 12
                      exp_year: 2027
                    idempotency_key: d7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23
                    created_at: '2026-01-31T10:08:00Z'
                    updated_at: '2026-01-31T10:08:02Z'
                discounts: []
                handoff:
                  mode: CURBSIDE
                  vehicle_make: Toyota
                  vehicle_model: Camry
                  vehicle_color: Silver
                  pickup_time: null
                notes: null
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 148
                  currency: USD
                total_discount:
                  amount: 0
                  currency: USD
                total:
                  amount: 1945
                  currency: USD
                total_paid:
                  amount: 1945
                  currency: USD
                balance_due:
                  amount: 0
                  currency: USD
                age_verification_required: false
                age_verification_notice: null
                created_at: '2026-01-31T10:07:00Z'
                updated_at: '2026-01-31T10:09:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /orders/{order_id}/payments:
    post:
      tags:
        - Payments
      operationId: submitPayment
      summary: Submit a payment for an order
      description: >
        Attaches a payment to an order. Supports credit cards, debit cards, gift
        cards,

        loyalty points, and digital wallets.


        **Idempotency:** This endpoint requires the `Idempotency-Key` header. If
        a request

        with the same key is received within 24 hours, the server returns the
        cached

        response without re-processing the payment. Error responses are NOT
        cached.


        **Split payments:** To pay with multiple tenders, submit separate
        payment requests

        for each tender. Each request must use a different Idempotency-Key.


        **Ordering rules:** Submit tenders in this order: loyalty points first,
        then gift

        cards, then credit/debit cards. This maximizes non-cash tender
        redemption.


        **Sequential submission:** Wait for each payment to reach a terminal
        state

        (COMPLETED or FAILED) before submitting the next. Concurrent submissions
        on

        the same order may produce undefined behavior.


        **Failure handling for split payments:** If a payment fails
        mid-sequence,

        previous successful payments remain. You must either: (a) retry the
        failed

        tender with the same Idempotency-Key, (b) submit a different tender for
        the

        remaining balance, or (c) cancel the order (which triggers void/refund
        of

        all successful payments).
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentRequest'
            example:
              payment_method: CREDIT_CARD
              amount:
                amount: 1945
                currency: USD
              tip_amount:
                amount: 200
                currency: USD
              payment_details:
                token: tok_visa_4242
      responses:
        '201':
          description: |
            Payment accepted and processing complete. The payment has been
            attached to the order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Payment'
              example:
                id: a1b2c3d4-0001-4000-8000-000000000001
                order_id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                status: COMPLETED
                payment_method: CREDIT_CARD
                amount:
                  amount: 1945
                  currency: USD
                tip_amount:
                  amount: 200
                  currency: USD
                payment_details:
                  last_four: '4242'
                  brand: visa
                  exp_month: 12
                  exp_year: 2027
                idempotency_key: d7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23
                created_at: '2026-01-31T10:08:00Z'
                updated_at: '2026-01-31T10:08:02Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /orders/{order_id}/refunds:
    post:
      tags:
        - Payments
      operationId: createRefund
      summary: Refund an order (full or partial)
      description: >
        Processes a full or partial refund for an order. The server determines
        how

        to distribute the refund across the order's payment methods using a

        minimize-cash-refund strategy (non-cash tenders are refunded first).


        **Full refund:** Set `amount` to the order's total paid amount.

        **Partial refund:** Set `amount` to the desired refund amount.

        **Item-level refund:** Include `line_items` to record which items are
        being

        refunded (informational -- the `amount` field determines the refund
        total).


        The refund amount must not exceed the order's refundable balance

        (amount_paid - amount_refunded). Multiple partial refunds can be issued

        until the full amount has been refunded.
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundRequest'
            examples:
              full_refund:
                summary: Full refund for customer request
                value:
                  amount:
                    amount: 1945
                    currency: USD
                  reason: CUSTOMER_REQUEST
                  reason_note: null
              partial_refund_with_items:
                summary: Partial refund for unavailable item
                value:
                  amount:
                    amount: 398
                    currency: USD
                  reason: ITEM_UNAVAILABLE
                  reason_note: Bottled water was out of stock.
                  line_items:
                    - order_item_id: e7f8a9b0-c1d2-3456-7890-abcdef123456
                      quantity: 2
                      reason: null
      responses:
        '201':
          description: |
            Refund created and processing initiated. The refund_allocations
            array shows how the refund was distributed across payment methods.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefundResponse'
              example:
                id: b1c2d3e4-0001-4000-8000-000000000001
                order_id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                status: COMPLETED
                amount:
                  amount: 1945
                  currency: USD
                reason: CUSTOMER_REQUEST
                reason_note: null
                refund_allocations:
                  - payment_id: a1b2c3d4-0002-4000-8000-000000000001
                    payment_method: LOYALTY_POINTS
                    amount:
                      amount: 500
                      currency: USD
                  - payment_id: a1b2c3d4-0003-4000-8000-000000000001
                    payment_method: GIFT_CARD
                    amount:
                      amount: 750
                      currency: USD
                  - payment_id: a1b2c3d4-0004-4000-8000-000000000001
                    payment_method: CREDIT_CARD
                    amount:
                      amount: 695
                      currency: USD
                line_items: []
                created_at: '2026-01-31T11:00:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /orders/{order_id}/cancel:
    post:
      tags:
        - Orders
      operationId: cancelOrder
      summary: Cancel an order
      description: >
        Cancels an order that has not yet started preparation. This endpoint is

        only available when `fulfillment_status` is PENDING or IN_PROGRESS.


        **Fulfillment restriction:** Once the store begins preparing the order

        (fulfillment_status is PREPARING or later), this endpoint returns 409

        Conflict. Cancellation at that stage requires store intervention.


        **Automatic void/refund:** When an order is cancelled, the server

        automatically handles all attached payments:

        - Payments in PENDING or AUTHORIZED status are **voided** (no charge).

        - Payments in CAPTURED or COMPLETED status are **refunded in full**.

        - Refunds follow the minimize-cash-refund strategy: loyalty points are
          refunded first, then gift cards, then credit/debit cards.

        No separate refund request is needed after cancellation.


        **Idempotency:** This endpoint requires the `Idempotency-Key` header.

        If the order was already cancelled with the same key, the server returns

        the current order state without re-processing.


        **Result:** The order's `status` is set to CANCELLED,
        `fulfillment_status`

        is set to CANCELLED, and all payments are voided or refunded.
      parameters:
        - $ref: '#/components/parameters/orderId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelRequest'
            example:
              reason: Customer changed their mind
      responses:
        '200':
          description: |
            Order cancelled successfully. Returns the updated order with
            status CANCELLED and all payments voided/refunded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
              example:
                id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                cart_id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
                location_id: b5a7c8d9-e0f1-4a2b-8c3d-4e5f6a7b8c9d
                status: CANCELLED
                payment_status: UNPAID
                fulfillment_status: CANCELLED
                items:
                  - id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    menu_item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    quantity: 1
                    base_price:
                      amount: 899
                      currency: USD
                    modifier_total:
                      amount: 500
                      currency: USD
                    item_total:
                      amount: 1399
                      currency: USD
                    modifier_selections:
                      - modifier_group_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                        modifier_id: a2b3c4d5-e6f7-8901-bcde-f12345678901
                        quantity: 1
                        nested_selections: []
                    age_verification_required: false
                    minimum_age: null
                payments:
                  - id: a1b2c3d4-0001-4000-8000-000000000001
                    order_id: f9a8b7c6-d5e4-3210-fedc-ba9876543210
                    status: REFUNDED
                    payment_method: CREDIT_CARD
                    amount:
                      amount: 1945
                      currency: USD
                    tip_amount:
                      amount: 200
                      currency: USD
                    payment_details:
                      last_four: '4242'
                      brand: visa
                      exp_month: 12
                      exp_year: 2027
                    idempotency_key: d7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23
                    created_at: '2026-01-31T10:08:00Z'
                    updated_at: '2026-01-31T10:15:00Z'
                discounts: []
                handoff:
                  mode: CURBSIDE
                  vehicle_make: Toyota
                  vehicle_model: Camry
                  vehicle_color: Silver
                  pickup_time: null
                notes: null
                subtotal:
                  amount: 1797
                  currency: USD
                total_tax:
                  amount: 148
                  currency: USD
                total_discount:
                  amount: 0
                  currency: USD
                total:
                  amount: 1945
                  currency: USD
                total_paid:
                  amount: 0
                  currency: USD
                balance_due:
                  amount: 1945
                  currency: USD
                age_verification_required: false
                age_verification_notice: null
                created_at: '2026-01-31T10:07:00Z'
                updated_at: '2026-01-31T10:15:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /webhooks:
    post:
      tags:
        - Webhook Subscriptions
      operationId: createWebhookSubscription
      summary: Create a webhook subscription
      description: |
        Register an HTTPS endpoint to receive webhook events. You can create
        multiple subscriptions to route different event types to different URLs.

        **Signing secret:** The response includes a `signing_secret` field that
        is ONLY returned in this response. Store it securely -- it cannot be
        retrieved again. Use it to verify the `X-Tote-Signature` header on
        incoming webhook deliveries.

        **Idempotency:** This endpoint requires the `Idempotency-Key` header.
        If a request with the same key is received within 24 hours, the server
        returns the cached response without creating a duplicate subscription.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookSubscriptionRequest'
            example:
              url: https://your-app.com/webhooks/tote
              event_types:
                - order.created
                - order.status_changed
                - order.cancelled
                - stock.updated
      responses:
        '201':
          description: |
            Webhook subscription created. The `signing_secret` is included in
            this response only -- store it securely.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
              example:
                id: wh_a1b2c3d4-e5f6-7890-abcd-ef1234567890
                url: https://your-app.com/webhooks/tote
                event_types:
                  - order.created
                  - order.status_changed
                  - order.cancelled
                  - stock.updated
                status: ACTIVE
                signing_secret: whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
                created_at: '2026-02-01T10:00:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    get:
      tags:
        - Webhook Subscriptions
      operationId: listWebhookSubscriptions
      summary: List webhook subscriptions
      description: >
        Returns all webhook subscriptions for the authenticated partner.

        Results are ordered by creation date (newest first).


        **Note:** The `signing_secret` field is NOT included in list responses.

        It is only returned when a subscription is first created via POST.


        **Pagination:** Results use cursor-based pagination. Use the `cursor`

        parameter from the `pagination.next_cursor` field to retrieve the next
        page.
      parameters:
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/limit'
      responses:
        '200':
          description: A paginated list of webhook subscriptions.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookSubscription'
                    description: List of webhook subscriptions.
                  pagination:
                    $ref: '#/components/schemas/Pagination'
              example:
                data:
                  - id: wh_a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    url: https://your-app.com/webhooks/tote
                    event_types:
                      - order.created
                      - order.status_changed
                      - order.cancelled
                      - stock.updated
                    status: ACTIVE
                    created_at: '2026-02-01T10:00:00Z'
                  - id: wh_b2c3d4e5-f6a7-8901-bcde-f12345678901
                    url: https://your-app.com/webhooks/tote-inventory
                    event_types:
                      - menu.changed
                      - location.hours_changed
                    status: ACTIVE
                    created_at: '2026-02-01T10:05:00Z'
                pagination:
                  has_more: false
                  next_cursor: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /webhooks/{webhook_id}:
    delete:
      tags:
        - Webhook Subscriptions
      operationId: deleteWebhookSubscription
      summary: Delete a webhook subscription
      description: |
        Permanently removes a webhook subscription. Events will no longer be
        delivered to the subscription URL. This action cannot be undone.

        After deletion, any in-flight deliveries for this subscription may
        still arrive at the endpoint. Your webhook handler should gracefully
        ignore events for unknown subscriptions.
      parameters:
        - $ref: '#/components/parameters/webhookId'
      responses:
        '204':
          description: Webhook subscription deleted successfully.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /locations/{location_id}/inventory:
    get:
      tags:
        - Inventory
      operationId: getLocationInventory
      summary: Get item availability for a location
      description: >
        Returns the current availability status for all items at a location.

        Use this endpoint to check stock before adding items to a cart, or as

        a polling fallback for the `stock.updated` webhook event.


        Items not returned in this response are not available at this location.


        **Pagination:** Results use cursor-based pagination. Use the `cursor`

        parameter from the `pagination.next_cursor` field to retrieve the next
        page.
      parameters:
        - $ref: '#/components/parameters/locationId'
        - $ref: '#/components/parameters/cursor'
        - name: limit
          in: query
          description: Maximum number of results to return per page.
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 100
      responses:
        '200':
          description: A paginated list of inventory items with availability status.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/InventoryItem'
                    description: List of items with availability status.
                  pagination:
                    $ref: '#/components/schemas/Pagination'
              example:
                data:
                  - item_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                    name: Build Your Own Sub Sandwich
                    availability_status: IN_STOCK
                    updated_at: '2026-02-01T12:00:00Z'
                  - item_id: f8a9b0c1-d2e3-4567-890a-bcdef1234567
                    name: Bottled Water
                    availability_status: LOW_STOCK
                    updated_at: '2026-02-01T13:45:00Z'
                  - item_id: d4e5f6a7-b8c9-0123-4567-890abcdef012
                    name: Marlboro Reds
                    availability_status: OUT_OF_STOCK
                    updated_at: '2026-02-01T14:00:00Z'
                pagination:
                  has_more: false
                  next_cursor: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
webhooks:
  order-created:
    post:
      tags:
        - Events
      operationId: orderCreatedEvent
      summary: Order created event
      security: []
      description: |
        Sent when a new order is placed via checkout. Contains minimal
        order data -- fetch the full order via GET /orders/{order_id}.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEventOrderCreated'
      responses:
        '200':
          description: Event received successfully.
  order-status-changed:
    post:
      tags:
        - Events
      operationId: orderStatusChangedEvent
      summary: Order status changed event
      security: []
      description: |
        Sent when any of the order's three status fields change (lifecycle
        status, fulfillment status, or payment status). Includes previous
        and current values for all three fields.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEventOrderStatusChanged'
      responses:
        '200':
          description: Event received successfully.
  order-cancelled:
    post:
      tags:
        - Events
      operationId: orderCancelledEvent
      summary: Order cancelled event
      security: []
      description: |
        Sent when an order is cancelled. Includes cancellation reason
        if provided. Any pending payments are voided and completed
        payments are refunded.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEventOrderCancelled'
      responses:
        '200':
          description: Event received successfully.
  stock-updated:
    post:
      tags:
        - Events
      operationId: stockUpdatedEvent
      summary: Stock updated event
      security: []
      description: |
        Sent when item availability changes at a location. A single event
        can contain multiple items. Use GET /locations/{location_id}/inventory
        as a polling fallback.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEventStockUpdated'
      responses:
        '200':
          description: Event received successfully.
  menu-changed:
    post:
      tags:
        - Events
      operationId: menuChangedEvent
      summary: Menu changed event
      security: []
      description: |
        Sent when a location's menu is modified. Re-fetch the full menu
        via GET /locations/{location_id}/menu. Use `version_hash` to
        avoid unnecessary re-fetches.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEventMenuChanged'
      responses:
        '200':
          description: Event received successfully.
  location-hours-changed:
    post:
      tags:
        - Events
      operationId: locationHoursChangedEvent
      summary: Location hours changed event
      security: []
      description: |
        Sent when a location's business hours are modified. Includes both
        previous and current hours for comparison.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEventLocationHoursChanged'
      responses:
        '200':
          description: Event received successfully.
components:
  securitySchemes:
    oauth2:
      type: oauth2
      description: |
        OAuth 2.0 client credentials flow for machine-to-machine authentication.
        Use your client_id and client_secret to obtain an access token via
        POST /auth/token, then include the token in the Authorization header
        of all subsequent requests.
      flows:
        clientCredentials:
          tokenUrl: /auth/token
          scopes: {}
  schemas:
    TokenRequest:
      type: object
      required:
        - grant_type
        - client_id
        - client_secret
      properties:
        grant_type:
          type: string
          enum:
            - CLIENT_CREDENTIALS
          description: Must be CLIENT_CREDENTIALS.
        client_id:
          type: string
          format: uuid
          description: Your application's client ID, provided during partner onboarding.
        client_secret:
          type: string
          description: |
            Your application's client secret. Treat this as a password --
            never expose in client-side code or commit to version control.
    TokenResponse:
      type: object
      required:
        - access_token
        - token_type
        - expires_in
      properties:
        access_token:
          type: string
          description: >
            Bearer token for authenticating API requests.

            Include in the Authorization header as: Authorization: Bearer
            {access_token}
        token_type:
          type: string
          enum:
            - BEARER
          description: Token type. Always BEARER.
        expires_in:
          type: integer
          description: |
            Token lifetime in seconds. Default is 86400 (24 hours).
            Cache your token and reuse it until expiry rather than
            requesting a new token for every API call.
          example: 86400
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              enum:
                - AUTHENTICATION_ERROR
                - INVALID_REQUEST_ERROR
                - RATE_LIMIT_ERROR
                - NOT_FOUND_ERROR
                - CONFLICT_ERROR
                - INTERNAL_ERROR
              description: |
                Machine-readable error category. Use this field for programmatic
                error handling (e.g., retry on RATE_LIMIT_ERROR, re-authenticate
                on AUTHENTICATION_ERROR).
            message:
              type: string
              description: |
                Human-readable error description. Safe to display to developers
                in logs or debugging tools. Do not display to end users.
            detail:
              type: string
              description: |
                Additional context about the error, including suggestions for
                resolution. May include specific field values or limits that
                were exceeded.
            request_id:
              type: string
              format: uuid
              description: |
                Unique identifier for this request. Include this value when
                contacting Tote Developer Support for troubleshooting.
            field:
              type:
                - string
                - 'null'
              description: |
                JSON pointer to the field that caused the error.
                Null if the error is not field-specific.
              example: items[0].modifier_groups[1].modifiers
            change_reasons:
              type: array
              items:
                type: string
                enum:
                  - PROMO_EXPIRED
                  - DISCOUNT_CHANGED
                  - ITEM_PRICE_CHANGED
                  - ITEM_UNAVAILABLE
                  - FEE_CHANGED
              description: >
                Machine-readable reasons why the resource state changed, causing

                the conflict. Present on checkout 409 responses when
                expected_total

                does not match the current total. Clients should re-fetch the
                cart

                and call POST /carts/{cart_id}/calculate to get the updated
                total

                before retrying checkout.
    Money:
      type: object
      required:
        - amount
        - currency
      properties:
        amount:
          type: integer
          description: |
            Monetary value in the smallest currency unit (cents for USD).
            Example: 1299 = $12.99.
          example: 1299
        currency:
          type: string
          description: ISO 4217 currency code.
          default: USD
          example: USD
          minLength: 3
          maxLength: 3
    Location:
      type: object
      required:
        - id
        - name
        - address
        - timezone
        - online_ordering_enabled
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this location.
        name:
          type: string
          description: Store name (e.g., "QuickStop
          example: 'QuickStop #247'
        address:
          type: object
          required:
            - street
            - city
            - state
            - postal_code
            - country
          properties:
            street:
              type: string
              description: Street address.
              example: 4521 Congress Ave
            city:
              type: string
              description: City name.
              example: Austin
            state:
              type: string
              description: Two-letter state code (e.g., "TX").
              minLength: 2
              maxLength: 2
              example: TX
            postal_code:
              type: string
              description: ZIP or postal code.
              example: '78745'
            country:
              type: string
              description: Two-letter ISO 3166-1 alpha-2 country code.
              default: US
              example: US
        phone:
          type: string
          description: |
            Store phone number in E.164 format (e.g., "+15125551234").
          example: '+15125551234'
        latitude:
          type:
            - number
            - 'null'
          format: double
          description: |
            Latitude in decimal degrees (WGS 84 datum). Null if the location
            has not been geocoded. Use with longitude for store locator features
            and distance calculations.
          example: 30.2277
        longitude:
          type:
            - number
            - 'null'
          format: double
          description: |
            Longitude in decimal degrees (WGS 84 datum). Null if the location
            has not been geocoded.
          example: -97.7553
        timezone:
          type: string
          description: |
            IANA timezone identifier for this location. Used to interpret
            business_hours and daily_sales_start_time/end_time fields.
          example: America/Chicago
        online_ordering_enabled:
          type: boolean
          description: >
            Whether this location currently accepts online orders. Only
            locations

            with online_ordering_enabled=true should be displayed to customers.
        default_prep_time_minutes:
          type:
            - integer
            - 'null'
          description: |
            Default estimated preparation time in minutes for orders at this
            location. Used to compute estimated_ready_at on orders when no
            item-specific prep time applies. Null if the location has not
            configured a default.
          example: 15
        minimum_order_amounts:
          type:
            - object
            - 'null'
          description: |
            Minimum order subtotal required per handoff mode. If a customer's
            cart subtotal is below the minimum for their selected handoff mode,
            the server automatically applies a SMALL_ORDER fee covering the
            shortfall rather than blocking the order. Null if no minimums are
            configured.

            Keys are lowercase handoff mode identifiers. Only modes with
            minimums are present -- absence means no minimum for that mode.
          properties:
            delivery:
              $ref: '#/components/schemas/Money'
              description: Minimum order subtotal for delivery orders.
            pickup:
              $ref: '#/components/schemas/Money'
              description: Minimum order subtotal for pickup orders.
            curbside:
              $ref: '#/components/schemas/Money'
              description: Minimum order subtotal for curbside orders.
            kiosk:
              $ref: '#/components/schemas/Money'
              description: Minimum order subtotal for kiosk orders.
          example:
            delivery:
              amount: 1500
              currency: USD
            pickup:
              amount: 500
              currency: USD
        business_hours:
          type: array
          description: >
            Weekly business hours for this location. One entry per day of the
            week.

            Hours are in the location's local timezone.
          items:
            type: object
            required:
              - day_of_week
              - open_time
              - close_time
              - is_closed
            properties:
              day_of_week:
                type: string
                enum:
                  - MONDAY
                  - TUESDAY
                  - WEDNESDAY
                  - THURSDAY
                  - FRIDAY
                  - SATURDAY
                  - SUNDAY
                description: Day of the week.
              open_time:
                type: string
                description: |
                  Opening time in HH:MM format (24-hour, location local time).
                example: '06:00'
              close_time:
                type: string
                description: >
                  Closing time in HH:MM format (24-hour, location local time).

                  A close_time of "00:00" with open_time of "00:00" indicates
                  24-hour operation.
                example: '23:00'
              is_closed:
                type: boolean
                default: false
                description: |
                  Whether the location is closed on this day. When true,
                  open_time and close_time are ignored.
    Pagination:
      type: object
      required:
        - has_more
      properties:
        has_more:
          type: boolean
          description: Whether more results exist beyond this page.
        next_cursor:
          type:
            - string
            - 'null'
          description: >
            Opaque cursor for the next page. Pass as the `cursor` query
            parameter.

            Null when there are no more results.
    ModifierGroup:
      type: object
      description: >
        A group of modifiers that can be applied to a menu item or another
        modifier.

        Each group defines selection rules (min/max) that constrain how many

        modifiers the customer must or may choose.


        Modifier groups support recursive nesting up to 3 levels deep:

        - Level 1: Item modifier group (e.g., "Bread Choice")

        - Level 2: Modifier sub-group (e.g., "Steak Preparation")

        - Level 3: Sub-modifier group (e.g., "Sauce Selection")
      required:
        - id
        - name
        - min_selections
        - max_selections
        - modifiers
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this modifier group.
        name:
          type: string
          description: Display name (e.g., "Bread Choice", "Sauce Selection").
          example: Bread Choice
        min_selections:
          type: integer
          minimum: 0
          description: |
            Minimum number of modifiers the customer must select.
            Set to 0 for optional groups, 1 or more for required groups.
          example: 1
        max_selections:
          type: integer
          minimum: 1
          description: |
            Maximum number of modifiers the customer may select.
            Set to 1 for single-select groups, higher for multi-select.
          example: 1
        allows_duplicates:
          type: boolean
          default: false
          description: |
            Whether the same modifier can be selected multiple times.
            Example: selecting "Extra Cheese" twice to get double cheese.
        modifiers:
          type: array
          items:
            $ref: '#/components/schemas/Modifier'
          description: Available modifiers in this group.
    Modifier:
      type: object
      description: |
        An individual modifier within a modifier group. A modifier represents
        a customization option (e.g., "Cheddar Cheese", "Ranch Dressing").

        Modifiers can contain nested modifier_groups to support multi-level
        customization (max 3 levels deep). Example: selecting "Steak" as a
        protein unlocks a "Steak Preparation" sub-group with options like
        "Medium" and "Well Done".
      required:
        - id
        - name
        - price_adjustment
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this modifier.
        name:
          type: string
          description: Display name (e.g., "Cheddar", "Ranch Dressing").
          example: Cheddar
        price_adjustment:
          $ref: '#/components/schemas/Money'
          description: |
            Additional cost for this modifier. Use amount=0 when the modifier
            is included in the base price.
        is_default:
          type: boolean
          default: false
          description: |
            Whether this modifier is pre-selected by default. Useful for
            common selections like "Regular" size.
        modifier_groups:
          type: array
          items:
            $ref: '#/components/schemas/ModifierGroup'
          description: >
            Nested modifier groups for multi-level customization (max 3 levels
            deep).

            Example: Sandwich > Protein > Steak Preparation > Sauce Selection.
    MenuItem:
      type: object
      description: >
        A menu item available for ordering. Includes base pricing, modifier
        groups

        for customization, and c-store-specific fields for age verification,

        time-based sales restrictions, quantity limits, and tender restrictions.
      required:
        - id
        - name
        - base_price
        - available
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this menu item.
        name:
          type: string
          description: Display name of the item.
          example: Build Your Own Sub Sandwich
        description:
          type:
            - string
            - 'null'
          description: Optional description of the item.
          example: Choose your bread, protein, and toppings.
        base_price:
          $ref: '#/components/schemas/Money'
          description: |
            Base price before any modifier adjustments. The final item price
            is base_price + sum of selected modifier price_adjustments.
        image_url:
          type:
            - string
            - 'null'
          format: uri
          description: |
            URL to an image of the item. Null if no image is available.
            Images are served via CDN and are suitable for display in apps.
          example: https://cdn.tote.ai/images/items/sub-sandwich.jpg
        available:
          type: boolean
          description: |
            Whether this item is currently available for ordering. Items may
            become unavailable due to stock levels or time restrictions.
        modifier_groups:
          type: array
          items:
            $ref: '#/components/schemas/ModifierGroup'
          description: |
            Modifier groups available for customizing this item. Groups define
            selection rules (min/max) and contain the available modifiers.
        non_discountable:
          type: boolean
          default: false
          description: |
            Whether this item is excluded from all discount calculations. When
            true, promo codes, automatic discounts, and loyalty rewards will not
            apply to this item. Display "Not eligible for discounts" in your UI
            for these items. The server enforces this during price calculation.
        age_verification_required:
          type: boolean
          default: false
          description: |
            Whether this item requires age verification before purchase.
            Applies to tobacco, alcohol, and other age-restricted products.
        minimum_age:
          type:
            - integer
            - 'null'
          description: |
            Minimum age required to purchase this item (e.g., 21 for alcohol,
            21 for tobacco in most US states). Null if no age restriction.
          example: 21
        daily_sales_start_time:
          type:
            - string
            - 'null'
          description: |
            Earliest time this item can be sold, in HH:MM format (24-hour,
            location local time). Null if no time restriction.
            Example: breakfast items available starting at "06:00".
          example: '06:00'
        daily_sales_end_time:
          type:
            - string
            - 'null'
          description: |
            Latest time this item can be sold, in HH:MM format (24-hour,
            location local time). Null if no time restriction.
            Example: breakfast items stop selling at "10:30".
          example: '10:30'
        max_allowed_quantity:
          type:
            - integer
            - 'null'
          description: |
            Maximum quantity of this item allowed per transaction. Null if
            no limit. Used for regulatory compliance (e.g., tobacco purchase
            limits, pseudoephedrine quantity restrictions).
          example: 2
        tender_restriction_level:
          type:
            - string
            - 'null'
          enum:
            - NONE
            - RESTRICTED
          description: |
            Whether this item has payment method restrictions. When RESTRICTED,
            check the allowed_tenders field for accepted payment methods.

            **Tolerant Reader:** New restriction level values may be added in
            future versions. Treat unknown values as RESTRICTED (check
            allowed_tenders) rather than assuming unrestricted.
          x-status: planned
        allowed_tenders:
          type:
            - array
            - 'null'
          items:
            type: string
            enum:
              - CREDIT_CARD
              - DEBIT_CARD
              - CASH
              - GIFT_CARD
              - LOYALTY_POINTS
              - DIGITAL_WALLET
              - EBT
          description: |
            Payment methods accepted for this item. Null means all payment
            methods are accepted. When populated, only the listed tenders
            can be used to pay for this item.
            Example: tobacco items may exclude EBT per SNAP regulations.

            **Tolerant Reader:** New tender values may be added in future
            versions. Treat unknown values as unrestricted (allow payment)
            rather than restricted.
          x-status: planned
    MenuCategory:
      type: object
      description: >
        A category within a menu (e.g., "Deli & Sandwiches", "Beverages",
        "Tobacco").

        Categories group related items and define display order.
      required:
        - id
        - name
        - sort_order
        - items
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this category.
        name:
          type: string
          description: Display name of the category.
          example: Deli & Sandwiches
        description:
          type:
            - string
            - 'null'
          description: Optional description of the category.
          example: Fresh-made sandwiches and deli items.
        sort_order:
          type: integer
          description: |
            Display order for this category. Lower values appear first.
            Use this to render categories in the intended order.
          example: 1
        items:
          type: array
          items:
            $ref: '#/components/schemas/MenuItem'
          description: Menu items in this category.
    Menu:
      type: object
      description: >
        The complete menu for a location, organized by categories. Each category

        contains its items, and each item includes its available modifier
        groups.


        Use the version_hash to detect menu changes: if the hash differs from

        your cached version, the menu has been updated and should be re-fetched.
      required:
        - location_id
        - last_modified
        - version_hash
        - categories
      properties:
        location_id:
          type: string
          format: uuid
          description: The location this menu belongs to.
        last_modified:
          type: string
          format: date-time
          description: |
            ISO 8601 timestamp of the last menu modification (UTC).
            Use this with the version_hash for cache invalidation.
          example: '2026-01-15T14:30:00Z'
        version_hash:
          type: string
          description: |
            Opaque hash representing the current menu version. Compare against
            your cached hash to detect changes without fetching the full menu.
            Use the GET /locations/{location_id}/menu/metadata endpoint for
            lightweight change detection.
          example: sha256:a1b2c3d4e5f6
        categories:
          type: array
          items:
            $ref: '#/components/schemas/MenuCategory'
          description: Menu categories with their items and modifier groups.
    MenuMetadata:
      type: object
      description: |
        Lightweight metadata about a location's menu. Use this endpoint to check
        if the menu has changed without downloading the full menu payload.

        **Recommended polling strategy:**
        1. Cache the full menu from GET /locations/{location_id}/menu
        2. Periodically call GET /locations/{location_id}/menu/metadata
        3. Compare version_hash with your cached version
        4. Re-fetch the full menu only when the hash changes
      required:
        - location_id
        - last_modified
        - version_hash
      properties:
        location_id:
          type: string
          format: uuid
          description: The location this menu belongs to.
        last_modified:
          type: string
          format: date-time
          description: |
            ISO 8601 timestamp of the last menu modification (UTC).
          example: '2026-01-15T14:30:00Z'
        version_hash:
          type: string
          description: |
            Opaque hash representing the current menu version. Compare against
            your cached hash to detect changes.
          example: sha256:a1b2c3d4e5f6
    ModifierSelection:
      type: object
      required:
        - modifier_group_id
        - modifier_id
      properties:
        modifier_group_id:
          type: string
          format: uuid
          description: ID of the modifier group this selection belongs to.
        modifier_id:
          type: string
          format: uuid
          description: ID of the selected modifier within the group.
        quantity:
          type: integer
          minimum: 1
          default: 1
          description: |
            Quantity of this modifier. Relevant for modifier groups with
            allows_duplicates: true (e.g., "Extra Cheese x2").
        nested_selections:
          type: array
          items:
            $ref: '#/components/schemas/ModifierSelection'
          description: |
            Selections for nested modifier groups. For example, selecting
            "Steak" as a protein, then choosing "Medium" from the steak
            preparation sub-group. Supports up to 3 nesting levels,
            matching the menu's modifier group depth.
    CartItem:
      type: object
      required:
        - id
        - menu_item_id
        - name
        - quantity
        - base_price
        - modifier_total
        - item_total
        - modifier_selections
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this cart item (generated server-side).
        menu_item_id:
          type: string
          format: uuid
          description: Reference to the menu item (from GET /locations/{location_id}/menu).
        name:
          type: string
          description: Display name of the menu item (copied from menu at add time).
        quantity:
          type: integer
          minimum: 1
          description: Number of this item in the cart.
        base_price:
          $ref: '#/components/schemas/Money'
          description: Base price per unit (from the menu item).
        modifier_total:
          $ref: '#/components/schemas/Money'
          description: Total price of all selected modifiers per unit.
        item_total:
          $ref: '#/components/schemas/Money'
          description: |
            Total for this line item: (base_price + modifier_total) * quantity.
        modifier_selections:
          type: array
          items:
            $ref: '#/components/schemas/ModifierSelection'
          description: Modifiers selected for this item.
        special_instructions:
          type:
            - string
            - 'null'
          maxLength: 200
          description: |
            Customer's special instructions for this item (e.g., "no onions",
            "extra ice"). Null if none were provided. Max 200 characters.
        age_verification_required:
          type: boolean
          description: |
            True if this specific item requires age verification.
            Copied from the menu item's age_verification_required flag.
          default: false
        minimum_age:
          type:
            - integer
            - 'null'
          description: >
            Minimum age required to purchase this item (e.g., 21 for
            tobacco/alcohol).

            Null if age verification is not required.
    HandoffModePickup:
      type: object
      required:
        - mode
      properties:
        mode:
          type: string
          const: PICKUP
          description: >-
            In-store pickup. Customer arrives at the store and picks up the
            order at the counter.
        pickup_time:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            Requested pickup time (ISO 8601). If null, the order is prepared
            as soon as possible ("ASAP").
    HandoffModeCurbside:
      type: object
      required:
        - mode
        - vehicle_make
        - vehicle_model
        - vehicle_color
      properties:
        mode:
          type: string
          const: CURBSIDE
          description: Curbside pickup. Staff brings the order to the customer's vehicle.
        vehicle_make:
          type: string
          description: Vehicle manufacturer (e.g., "Toyota").
          example: Toyota
        vehicle_model:
          type: string
          description: Vehicle model (e.g., "Camry").
          example: Camry
        vehicle_color:
          type: string
          description: Vehicle color (e.g., "Silver").
          example: Silver
        pickup_time:
          type:
            - string
            - 'null'
          format: date-time
          description: Requested pickup time. Null for ASAP.
    HandoffModeDelivery:
      type: object
      required:
        - mode
        - delivery_address
      properties:
        mode:
          type: string
          const: DELIVERY
          description: Delivery to customer's address.
        delivery_address:
          type: object
          required:
            - street
            - city
            - state
            - postal_code
          properties:
            street:
              type: string
              description: Street address including apartment/suite number.
              example: 123 Main St, Apt 4B
            city:
              type: string
              example: Austin
            state:
              type: string
              minLength: 2
              maxLength: 2
              description: Two-letter US state code.
              example: TX
            postal_code:
              type: string
              description: ZIP or postal code.
              example: '78701'
            country:
              type: string
              default: US
              description: Two-letter ISO 3166-1 country code.
          description: Delivery destination address.
        delivery_instructions:
          type:
            - string
            - 'null'
          description: |
            Optional instructions for the delivery driver
            (e.g., "Leave at door", "Ring bell").
    HandoffModeKiosk:
      type: object
      required:
        - mode
      properties:
        mode:
          type: string
          const: KIOSK
          description: |
            In-store self-service kiosk order. The customer places the order at
            a self-service terminal and picks it up at the counter. No delivery
            address or vehicle details needed.
        kiosk_id:
          type:
            - string
            - 'null'
          description: Identifier for the specific kiosk terminal (optional, for tracking).
    HandoffMode:
      oneOf:
        - $ref: '#/components/schemas/HandoffModePickup'
        - $ref: '#/components/schemas/HandoffModeCurbside'
        - $ref: '#/components/schemas/HandoffModeDelivery'
        - $ref: '#/components/schemas/HandoffModeKiosk'
      discriminator:
        propertyName: mode
        mapping:
          PICKUP: '#/components/schemas/HandoffModePickup'
          CURBSIDE: '#/components/schemas/HandoffModeCurbside'
          DELIVERY: '#/components/schemas/HandoffModeDelivery'
          KIOSK: '#/components/schemas/HandoffModeKiosk'
    PromoCode:
      type: object
      required:
        - code
        - status
        - applied_at
      properties:
        code:
          type: string
          description: >
            The promo code string, normalized to uppercase. Codes are
            case-insensitive

            on input ("summer25" and "SUMMER25" are treated identically) but
            always

            returned as uppercase in responses.
          maxLength: 30
          pattern: ^[A-Z0-9-]+$
          example: SUMMER25
        status:
          type: string
          enum:
            - ACTIVE
            - EXPIRED
            - REDEEMED
          description: >
            Current status of this promo code on the cart.

            - ACTIVE: The code is applied and its discount is reflected in
            totals.

            - EXPIRED: The code expired after being applied (e.g., time-limited
            promo).

            - REDEEMED: The code was already used (single-use codes) and locked
            at checkout.
        discount_preview:
          type: object
          description: >
            Estimated discount this promo code provides. Present when the code
            is

            ACTIVE. The actual discount amount appears in the PriceCalculation

            discounts array with source: PROMO_CODE.
          properties:
            estimated_discount:
              $ref: '#/components/schemas/Money'
              description: Estimated discount amount in cents.
            description:
              type: string
              description: Human-readable description of the discount.
              example: 25% off your order (up to $10)
        applied_at:
          type: string
          format: date-time
          description: When this promo code was applied to the cart.
    FeeLineItem:
      type: object
      description: |
        A fee applied to the order. Fees are separate from item pricing and
        discounts. Each fee is displayed as a distinct line item so the customer
        can see exactly what they are being charged.
      required:
        - id
        - name
        - fee_type
        - label
        - amount
        - taxable
      properties:
        id:
          type: string
          description: |
            Unique identifier for this fee. Server-generated opaque string
            used for reconciliation and tracking.
        name:
          type: string
          description: |
            Display name of the fee (e.g., "Delivery Fee", "Service Fee",
            "Bag Fee"). Use this for detailed receipt views.
        fee_type:
          type: string
          enum:
            - DELIVERY
            - SERVICE
            - BAG
            - SMALL_ORDER
            - OTHER
          description: |
            Category of the fee.
            - DELIVERY: Fee for delivering the order to the customer.
            - SERVICE: Platform or convenience service fee.
            - BAG: Per-bag charge required by local regulations.
            - SMALL_ORDER: Surcharge applied when the cart subtotal is below
              the location's minimum order amount. The fee covers the shortfall.
            - OTHER: Fees that do not fit the above categories.
        label:
          type: string
          description: |
            Short display label for UI rendering (e.g., "Delivery", "Service",
            "Bag fee"). Use this for compact summary views.
        type:
          type: string
          enum:
            - FLAT
            - PERCENTAGE
          description: |
            How the fee amount was calculated.
            - FLAT: A fixed amount regardless of cart contents.
            - PERCENTAGE: Calculated as a percentage of the subtotal.
        value:
          type:
            - string
            - 'null'
          description: |
            For PERCENTAGE fees, the percentage as a decimal string
            (e.g., "5.00" for 5%). Null for FLAT fees.
        amount:
          $ref: '#/components/schemas/Money'
          description: |
            The calculated fee amount in cents (always positive). For PERCENTAGE
            fees, this is the result of applying the percentage to the subtotal.
        taxable:
          type: boolean
          description: |
            Whether tax applies to this fee. The server decides per fee based
            on local tax rules. When true, this fee's amount is included in the
            taxable_amount on the PriceCalculation response.
    Cart:
      type: object
      required:
        - id
        - location_id
        - status
        - items
        - subtotal
        - total_tax
        - total
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the cart.
        location_id:
          type: string
          format: uuid
          description: The location this cart belongs to.
        customer_id:
          type:
            - string
            - 'null'
          description: |
            Partner-provided customer identifier. When set, the server applies
            member-specific pricing during price calculation. Can be set at
            creation (POST /carts) or updated later (PATCH /carts/{cart_id}).
            Set to null to revert to anonymous pricing.

            After changing customer_id, re-calculate prices -- previous pricing
            may be stale.
          maxLength: 128
          example: CUST-12345
        status:
          type: string
          enum:
            - ACTIVE
            - CHECKED_OUT
            - ABANDONED
          description: |
            Current cart status. ACTIVE carts accept modifications.
            CHECKED_OUT carts have been converted to orders via checkout.
            ABANDONED carts were explicitly deleted by the client.
        items:
          type: array
          items:
            $ref: '#/components/schemas/CartItem'
          description: Line items in the cart.
        handoff_mode:
          oneOf:
            - $ref: '#/components/schemas/HandoffMode'
            - type: 'null'
          description: |
            The handoff mode set for this cart, or null if not yet selected.
            Set via PUT /carts/{cart_id}/handoff. Required before checkout.
        age_verification_required:
          type: boolean
          description: >
            True if any item in the cart requires age verification (e.g.,
            tobacco, alcohol).

            This is an informational flag -- age-restricted items are NOT
            blocked from the cart.

            The consumer will be verified offline at pickup or delivery.
          default: false
        promo_codes:
          type: array
          items:
            $ref: '#/components/schemas/PromoCode'
          description: >
            Promo codes currently applied to this cart. Only one promo code can
            be

            active at a time. Applied via POST /carts/{cart_id}/promo-codes.
        subtotal:
          $ref: '#/components/schemas/Money'
          description: Sum of all item totals before tax and discounts.
        total_tax:
          $ref: '#/components/schemas/Money'
          description: Total tax amount for the cart.
        total_discount:
          $ref: '#/components/schemas/Money'
          description: |
            Total of cart-level discounts applied to the cart. Item-level
            discounts are reflected in each item's total.
        fees:
          type: array
          items:
            $ref: '#/components/schemas/FeeLineItem'
          description: |
            Fees currently applied to this cart. Recalculated on each cart
            modification. Call POST /carts/{cart_id}/calculate for the
            authoritative fee breakdown.
        total_fees:
          $ref: '#/components/schemas/Money'
          description: Sum of all fee amounts currently applied to the cart.
        total:
          $ref: '#/components/schemas/Money'
          description: 'Grand total: subtotal + total_tax + total_fees - total_discount.'
        created_at:
          type: string
          format: date-time
          description: Timestamp when the cart was created.
        updated_at:
          type: string
          format: date-time
          description: Timestamp when the cart was last modified.
    CartItemRequest:
      type: object
      required:
        - menu_item_id
        - quantity
      properties:
        menu_item_id:
          type: string
          format: uuid
          description: |
            ID of the menu item to add (from GET /locations/{location_id}/menu).
            The server validates that this item exists and is available.
        quantity:
          type: integer
          minimum: 1
          description: Number of this item to add.
        modifier_selections:
          type: array
          items:
            $ref: '#/components/schemas/ModifierSelection'
          description: |
            Selected modifiers for this item. Must satisfy the min_selections
            and max_selections constraints of each modifier group defined in
            the menu. If a modifier group has min_selections > 0 and no
            selection is provided for it, the server returns a 422 error.
        special_instructions:
          type:
            - string
            - 'null'
          maxLength: 200
          description: >
            Free-text instructions for this item (e.g., "no onions", "extra
            ice").

            Max 200 characters. Null or omitted if no special instructions.
    DiscountLineItem:
      type: object
      required:
        - id
        - name
        - type
        - amount
        - source
        - application_scope
      properties:
        id:
          type: string
          description: |
            Unique identifier for this discount. Server-generated opaque string
            for reconciliation and tracking.
        name:
          type: string
          description: >
            Display name of the discount (e.g., "10% Off First Order", "$2 Off
            Subs").
        type:
          type: string
          enum:
            - PERCENTAGE
            - FIXED
          description: >
            Whether this discount is a percentage off or a fixed amount.

            PERCENTAGE: the `value` field contains the percentage (e.g., "10.00"
            for 10%).

            FIXED: the `amount` field contains the fixed discount in cents.
        value:
          type:
            - string
            - 'null'
          description: |
            For PERCENTAGE discounts, the percentage value as a decimal string
            (e.g., "10.00" for 10%). Null for FIXED discounts.
        amount:
          $ref: '#/components/schemas/Money'
          description: The calculated discount amount deducted (always positive, in cents).
        source:
          type: string
          enum:
            - AUTOMATIC
            - PROMO_CODE
            - LOYALTY_REWARD
            - MANUAL
          description: >
            Where this discount originated.

            - AUTOMATIC: Discount applied automatically by the server based on
            rules
              (happy hour, spend threshold, item combo, location-specific).
            - PROMO_CODE: Discount from a promo or coupon code entered by the
            customer.

            - LOYALTY_REWARD: Discount from a loyalty program reward redemption.

            - MANUAL: Discount applied manually by store staff or POS override.
        application_scope:
          type: string
          enum:
            - PRE_TAX
            - POST_TAX
          description: |
            Whether this discount is applied before or after tax calculation.
            PRE_TAX discounts reduce the taxable amount. POST_TAX discounts
            reduce the total after tax. Most discounts are PRE_TAX.
    PriceLineItem:
      type: object
      required:
        - cart_item_id
        - menu_item_id
        - name
        - quantity
        - base_price
        - modifier_total
        - item_subtotal
        - item_tax
        - item_total
      properties:
        cart_item_id:
          type: string
          format: uuid
          description: Reference to the cart item.
        menu_item_id:
          type: string
          format: uuid
          description: Reference to the menu item.
        name:
          type: string
          description: Display name of the item.
        quantity:
          type: integer
          minimum: 1
        base_price:
          $ref: '#/components/schemas/Money'
          description: Base price per unit.
        modifier_total:
          $ref: '#/components/schemas/Money'
          description: Total modifier cost per unit.
        discounts:
          type: array
          items:
            $ref: '#/components/schemas/DiscountLineItem'
          description: Item-level discounts applied to this line item.
        item_subtotal:
          $ref: '#/components/schemas/Money'
          description: >
            Subtotal for this line: (base_price + modifier_total) * quantity -
            item-level

            discounts. Item-level discounts are already deducted here, so the
            cart-level

            `total_discount` on PriceCalculation does not include them.
        item_tax:
          $ref: '#/components/schemas/Money'
          description: Tax amount for this line item.
        item_total:
          $ref: '#/components/schemas/Money'
          description: |
            Total for this line item including tax: item_subtotal + item_tax.
    PriceCalculation:
      type: object
      required:
        - cart_id
        - currency
        - line_items
        - subtotal
        - total_tax
        - total_discount
        - total_fees
        - total
        - calculated_at
      properties:
        cart_id:
          type: string
          format: uuid
        currency:
          type: string
          default: USD
          description: ISO 4217 currency code.
        line_items:
          type: array
          items:
            $ref: '#/components/schemas/PriceLineItem'
          description: Price breakdown for each cart item.
        discounts:
          type: array
          items:
            $ref: '#/components/schemas/DiscountLineItem'
          description: |
            Cart-level discounts applied to the order. Item-level discounts
            appear on each line item in the `line_items` array, not here.
            Each discount is listed as a separate line item with name, type,
            source, and amount deducted.
        promo_codes:
          type: array
          items:
            $ref: '#/components/schemas/PromoCode'
          description: |
            Promo codes active on the cart. Their discounts appear in the
            `discounts` array with `source: PROMO_CODE`.
        member_pricing_applied:
          type: boolean
          description: |
            Whether member-specific pricing was applied during this calculation.
            True when the cart has a customer_id and the server found matching
            member pricing rules. False when the cart is anonymous or when the
            customer_id has no associated member pricing.

            Use this field to show customers whether they are receiving member
            prices (e.g., "Member pricing applied" badge in your UI).
          default: false
        fees:
          type: array
          items:
            $ref: '#/components/schemas/FeeLineItem'
          description: |
            Fees applied to this order (delivery, service, bag, small order
            surcharge). Each fee is a separate line item. Display all fees
            to the customer.
        subtotal:
          $ref: '#/components/schemas/Money'
          description: >-
            Sum of all line item subtotals (before tax, after item-level
            discounts).
        total_tax:
          $ref: '#/components/schemas/Money'
          description: Total tax for the entire cart.
        total_discount:
          $ref: '#/components/schemas/Money'
          description: |
            Sum of cart-level discount amounts. Item-level discounts are already
            deducted from each line item's `item_subtotal` and are NOT included
            in this field. This avoids double-counting.
        total_fees:
          $ref: '#/components/schemas/Money'
          description: |
            Sum of all fee amounts. Included in the total formula.
        taxable_amount:
          $ref: '#/components/schemas/Money'
          description: |
            The amount on which tax was computed. Equals subtotal minus any
            pre-tax cart-level discounts plus taxable fee amounts. Exposed for
            transparency so partners can verify tax calculations independently.
        total:
          $ref: '#/components/schemas/Money'
          description: |
            Grand total: subtotal + total_tax + total_fees - total_discount.
        age_verification_required:
          type: boolean
          description: >
            True if any item in the cart requires age verification.

            This is informational -- the consumer will be verified at pickup or
            delivery.
        calculated_at:
          type: string
          format: date-time
          description: >
            When this calculation was performed. Prices are computed at request
            time

            and may change if the menu or tax configuration is updated. Always
            call

            calculate immediately before checkout for the most accurate total.
    CheckoutRequest:
      type: object
      properties:
        handoff_mode:
          $ref: '#/components/schemas/HandoffMode'
          description: >
            Optional override for the handoff mode. If provided, this replaces
            the

            cart's stored handoff mode for this checkout. If omitted, the cart's

            stored handoff mode is used.


            At checkout, a handoff mode must exist either on the cart (set via

            PUT /carts/{cart_id}/handoff) or in this field. If neither is
            present,

            the server returns 422.
        expected_total:
          type:
            - integer
            - 'null'
          description: >
            Optional optimistic concurrency check. If provided (in cents), the
            server

            compares this against the current calculated total. If they don't
            match

            (e.g., prices changed since the last calculate call), the server
            returns

            409 Conflict instead of proceeding with checkout.


            Omit this field to skip the total verification and proceed with the

            current price.
        notes:
          type:
            - string
            - 'null'
          description: Optional order notes from the customer (e.g., "No onions please").
          maxLength: 500
    OrderStatus:
      type: string
      enum:
        - PENDING
        - CONFIRMED
        - COMPLETED
        - FAILED
        - VOIDED
        - CANCELLED
      description: >
        Order lifecycle status. Tracks the high-level state of the order from

        creation through completion or cancellation.


        - PENDING: Order created from cart checkout, awaiting payment.

        - CONFIRMED: At least one payment completed; order accepted by the
        store.

        - COMPLETED: Order fulfilled and closed.

        - FAILED: Order could not be processed (e.g., all payments failed).

        - VOIDED: Order voided before any fulfillment began.

        - CANCELLED: Order cancelled by customer or system.


        State transitions:

        - PENDING -> CONFIRMED, FAILED, VOIDED, CANCELLED

        - CONFIRMED -> COMPLETED, CANCELLED

        - COMPLETED, FAILED, VOIDED, CANCELLED are terminal (no further
        transitions)
    OrderPaymentStatus:
      type: string
      enum:
        - UNPAID
        - PROCESSING
        - PARTIALLY_PAID
        - PAID
      description: >
        Aggregate payment status across all payments on the order. This is a

        server-computed summary -- do not set directly.


        - UNPAID: No payments submitted yet.

        - PROCESSING: At least one payment is in a non-terminal state (PENDING
        or AUTHORIZED).

        - PARTIALLY_PAID: Some payments completed but total paid < order total.

        - PAID: Total paid >= order total. The order is fully covered.


        State transitions:

        - UNPAID -> PROCESSING, PARTIALLY_PAID, PAID

        - PROCESSING -> PARTIALLY_PAID, PAID, UNPAID (if all payments fail)

        - PARTIALLY_PAID -> PROCESSING, PAID

        - PAID is terminal for forward flow (refunds revert to PARTIALLY_PAID or
        UNPAID)
    FulfillmentStatus:
      type: string
      enum:
        - PENDING
        - IN_PROGRESS
        - PREPARING
        - READY_FOR_PICKUP
        - FULFILLED
        - DELIVERED
        - RETURNED
        - CANCELLED
      description: >
        Fulfillment lifecycle status. Tracks the physical preparation and
        handoff

        of the order. Maps directly from the backend fulfillment state machine.


        - PENDING: Order created, not yet accepted by store.

        - IN_PROGRESS: Store accepted the order and is queuing it.

        - PREPARING: Store staff actively assembling the order.

        - READY_FOR_PICKUP: Order assembled and waiting for customer
        (pickup/curbside).

        - FULFILLED: Customer received the order (pickup/curbside/kiosk).

        - DELIVERED: Order delivered to customer address (delivery orders).

        - RETURNED: Order returned after fulfillment.

        - CANCELLED: Fulfillment cancelled before completion.


        State transitions:

        - PENDING -> IN_PROGRESS, CANCELLED

        - IN_PROGRESS -> PREPARING, CANCELLED

        - PREPARING -> READY_FOR_PICKUP, CANCELLED

        - READY_FOR_PICKUP -> FULFILLED, DELIVERED, CANCELLED

        - FULFILLED -> RETURNED

        - DELIVERED -> RETURNED

        - RETURNED, CANCELLED are terminal (no further transitions)


        Cancellation restriction: Orders can only be cancelled via

        POST /orders/{order_id}/cancel when fulfillment_status is PENDING or

        IN_PROGRESS. Once preparation begins, cancellation requires store

        intervention.
    OrderItem:
      type: object
      required:
        - id
        - menu_item_id
        - name
        - quantity
        - base_price
        - modifier_total
        - item_total
        - modifier_selections
      description: >
        A line item in an order. Mirrors the CartItem structure with prices
        locked

        at checkout time. Prices on order items never change after checkout,
        even

        if the menu is updated.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this order item.
        menu_item_id:
          type: string
          format: uuid
          description: Reference to the original menu item.
        name:
          type: string
          description: Display name of the item (locked at checkout).
        quantity:
          type: integer
          minimum: 1
          description: Number of this item in the order.
        base_price:
          $ref: '#/components/schemas/Money'
          description: Base price per unit (locked at checkout).
        modifier_total:
          $ref: '#/components/schemas/Money'
          description: Total price of all selected modifiers per unit (locked at checkout).
        item_total:
          $ref: '#/components/schemas/Money'
          description: |
            Total for this line item: (base_price + modifier_total) * quantity.
            Locked at checkout.
        modifier_selections:
          type: array
          items:
            $ref: '#/components/schemas/ModifierSelection'
          description: Modifiers selected for this item (locked at checkout).
        special_instructions:
          type:
            - string
            - 'null'
          maxLength: 200
          description: |
            Customer's special instructions for this item, locked at checkout.
            Null if none were provided.
        age_verification_required:
          type: boolean
          description: |
            True if this item requires age verification. Copied from the menu
            item at checkout time.
          default: false
        minimum_age:
          type:
            - integer
            - 'null'
          description: |
            Minimum age required to purchase this item (e.g., 21 for alcohol).
            Null if age verification is not required.
    PaymentStatus:
      type: string
      enum:
        - PENDING
        - AUTHORIZED
        - CAPTURED
        - COMPLETED
        - VOIDED
        - REFUNDED
        - PARTIALLY_REFUNDED
        - FAILED
      description: >
        Payment processing status. The typical online ordering flow is:

        PENDING -> COMPLETED (immediate capture).


        For pre-authorization flows: PENDING -> AUTHORIZED -> CAPTURED ->
        COMPLETED.


        Terminal states: COMPLETED, VOIDED, REFUNDED, FAILED.


        State transitions:

        - PENDING -> AUTHORIZED, COMPLETED, FAILED

        - AUTHORIZED -> CAPTURED, VOIDED, FAILED

        - CAPTURED -> COMPLETED, REFUNDED, PARTIALLY_REFUNDED

        - COMPLETED -> REFUNDED, PARTIALLY_REFUNDED

        - VOIDED, REFUNDED, FAILED are terminal (no further transitions)

        - PARTIALLY_REFUNDED -> REFUNDED (when remaining amount is refunded)
    PaymentMethod:
      type: string
      enum:
        - CREDIT_CARD
        - DEBIT_CARD
        - CASH
        - GIFT_CARD
        - LOYALTY_POINTS
        - DIGITAL_WALLET
        - EBT
      description: |
        The payment method used. These are the API-facing values; the server
        maps them to internal payment processing types.

        - CREDIT_CARD: Visa, Mastercard, Amex, Discover credit cards
        - DEBIT_CARD: Debit cards with PIN or signature
        - CASH: Customer pays cash at the pickup counter. Only meaningful for
          PICKUP and DINE_IN handoff modes. The order is submitted online but
          payment is collected in-store.
        - GIFT_CARD: Store gift cards or third-party gift cards
        - LOYALTY_POINTS: Loyalty program points redeemed as payment
        - DIGITAL_WALLET: Apple Pay, Google Pay, or other digital wallets
        - EBT: SNAP/EBT card. Item eligibility applies -- only SNAP-eligible
          items (generally food items, excluding hot prepared foods and tobacco)
          can be paid with EBT. Check `allowed_tenders` on each menu item.

        **Tolerant Reader:** New payment method values may be added in future
        API versions. Your integration should handle unknown enum values
        gracefully (e.g., log a warning and skip) rather than failing.
    Payment:
      type: object
      required:
        - id
        - order_id
        - status
        - payment_method
        - amount
        - created_at
        - updated_at
      description: |
        A payment attached to an order. Each payment represents a single tender
        in a transaction. For split payments, multiple Payment resources exist
        on the same order.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the payment.
        order_id:
          type: string
          format: uuid
          description: The order this payment is attached to.
        status:
          $ref: '#/components/schemas/PaymentStatus'
        payment_method:
          $ref: '#/components/schemas/PaymentMethod'
        amount:
          $ref: '#/components/schemas/Money'
          description: The payment amount in cents.
        tip_amount:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: |
            Tip amount included in this payment, if any. Null if no tip.
        payment_details:
          type:
            - object
            - 'null'
          description: >
            Method-specific details. Shape varies by payment_method:

            - CREDIT_CARD / DEBIT_CARD: `{ "last_four": "4242", "brand": "visa",
            "exp_month": 12, "exp_year": 2027 }`

            - GIFT_CARD: `{ "last_four": "7890", "balance_remaining": {
            "amount": 1500, "currency": "USD" } }`

            - LOYALTY_POINTS: `{ "points_used": 500, "points_remaining": 1200 }`

            - DIGITAL_WALLET: `{ "wallet_type": "apple_pay" }`
          additionalProperties: true
        idempotency_key:
          type: string
          format: uuid
          description: The idempotency key used when this payment was submitted.
        created_at:
          type: string
          format: date-time
          description: When the payment was created.
        updated_at:
          type: string
          format: date-time
          description: When the payment status was last updated.
    Order:
      type: object
      required:
        - id
        - cart_id
        - location_id
        - status
        - payment_status
        - fulfillment_status
        - items
        - payments
        - subtotal
        - total_tax
        - total
        - total_paid
        - balance_due
        - created_at
        - updated_at
      description: >
        Full order resource. Created from cart checkout and enriched with
        payment

        and fulfillment data as the order progresses.


        The order has three independent status fields:

        - **status**: High-level order lifecycle (PENDING -> CONFIRMED ->
        COMPLETED).

        - **payment_status**: Aggregate payment state across all tenders (UNPAID
        -> PAID).

        - **fulfillment_status**: Physical preparation and handoff tracking
        (PENDING -> FULFILLED).


        These three fields are independent because payment and fulfillment
        progress

        at different rates. An order can be PAID but still PREPARING, or
        CONFIRMED

        but UNPAID (if using pay-at-pickup).
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the order.
        cart_id:
          type: string
          format: uuid
          description: The cart this order was created from.
        location_id:
          type: string
          format: uuid
          description: The location (store) this order belongs to.
        customer_id:
          type:
            - string
            - 'null'
          description: |
            Customer identifier from the cart at checkout time. Null for
            anonymous orders. Locked at checkout -- cannot be changed after
            the order is created.
          maxLength: 128
          example: CUST-12345
        status:
          $ref: '#/components/schemas/OrderStatus'
        payment_status:
          $ref: '#/components/schemas/OrderPaymentStatus'
        fulfillment_status:
          $ref: '#/components/schemas/FulfillmentStatus'
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
          description: Line items in the order (locked at checkout).
        payments:
          type: array
          items:
            $ref: '#/components/schemas/Payment'
          description: |
            Payments attached to this order. Empty array until the first payment
            is submitted via POST /orders/{order_id}/payments.
        discounts:
          type: array
          items:
            $ref: '#/components/schemas/DiscountLineItem'
          description: Discounts applied to the order (locked at checkout).
        promo_codes:
          type: array
          items:
            $ref: '#/components/schemas/PromoCode'
          description: >
            Promo codes that were active on the cart at checkout (locked at
            checkout).
        handoff:
          $ref: '#/components/schemas/HandoffMode'
          description: The handoff mode and details for this order.
        notes:
          type:
            - string
            - 'null'
          description: |
            Customer notes from checkout (e.g., "No onions please").
            Null if no notes were provided.
          maxLength: 500
        subtotal:
          $ref: '#/components/schemas/Money'
          description: Sum of all item totals before tax and discounts.
        total_tax:
          $ref: '#/components/schemas/Money'
          description: Total tax amount for the order.
        total_discount:
          $ref: '#/components/schemas/Money'
          description: >
            Total of cart-level discounts applied to the order (locked at
            checkout).

            Item-level discounts are reflected in each item's total.
        fees:
          type: array
          items:
            $ref: '#/components/schemas/FeeLineItem'
          description: Fees applied to this order (locked at checkout).
        total_fees:
          $ref: '#/components/schemas/Money'
          description: Sum of all fee amounts (locked at checkout).
        total:
          $ref: '#/components/schemas/Money'
          description: >-
            Grand total: subtotal + total_tax + total_fees - total_discount.
            Locked at checkout.
        total_paid:
          $ref: '#/components/schemas/Money'
          description: |
            Sum of all completed payment amounts. Updated as payments reach
            COMPLETED status.
        balance_due:
          $ref: '#/components/schemas/Money'
          description: |
            Remaining amount to be paid (total - total_paid). Zero when the
            order is fully paid.
        age_verification_required:
          type: boolean
          description: |
            True if any item requires age verification. When true, the consumer
            will be verified at pickup or delivery (offline verification).
          default: false
        age_verification_notice:
          type:
            - string
            - 'null'
          description: |
            Human-readable notice about age verification requirements.
            Null when no age-restricted items are in the order.
        estimated_ready_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >
            Estimated time the order will be ready for pickup or delivery, as an

            ISO 8601 datetime (e.g., "2026-03-15T14:30:00-05:00"). Null when the

            estimate is not yet available (e.g., before the order is confirmed
            by

            the store). Updated as the order progresses -- poll the order or

            listen for order.status.changed webhooks to get updated estimates.
        created_at:
          type: string
          format: date-time
          description: When the order was created (checkout time).
        updated_at:
          type: string
          format: date-time
          description: When the order was last modified.
    CheckoutResponse:
      description: >
        The order created from the cart checkout. Returns the full Order
        resource.

        The order is created with status PENDING, payment_status UNPAID, and

        fulfillment_status PENDING.


        After checkout, submit payments via POST /orders/{order_id}/payments.
      allOf:
        - $ref: '#/components/schemas/Order'
    PromoCodeValidation:
      type: object
      required:
        - code
        - valid
      properties:
        code:
          type: string
          description: >
            The promo code, normalized to uppercase. Always returned as
            uppercase

            regardless of input casing.
          example: SUMMER25
        valid:
          type: boolean
          description: |
            Whether the code is valid for this cart. When true, discount_preview
            contains the estimated discount. When false, rejection_reason and
            rejection_message explain why.
        discount_preview:
          type:
            - object
            - 'null'
          description: |
            Estimated discount this code would provide if applied. Present when
            valid is true. Null when valid is false.
          properties:
            estimated_discount:
              $ref: '#/components/schemas/Money'
              description: Estimated discount amount in cents.
            description:
              type: string
              description: Human-readable description of the discount.
              example: 25% off your order (up to $10)
            applicable_items:
              type: array
              items:
                type: string
              description: |
                Cart item IDs this discount applies to. An empty array means the
                discount applies to the entire cart (cart-level discount).
        rejection_reason:
          type:
            - string
            - 'null'
          enum:
            - INVALID_CODE
            - EXPIRED
            - ALREADY_USED
            - MINIMUM_NOT_MET
            - NOT_APPLICABLE
            - ALREADY_APPLIED
            - null
          description: >
            Machine-readable reason why the code is invalid. Present when valid

            is false. Null when valid is true.

            - INVALID_CODE: The code does not exist or is not recognized.

            - EXPIRED: The code has passed its expiration date.

            - ALREADY_USED: The code has already been redeemed (single-use
            codes).

            - MINIMUM_NOT_MET: The cart does not meet the minimum order amount
            for this code.

            - NOT_APPLICABLE: No items in the cart are eligible for this code.

            - ALREADY_APPLIED: A promo code is already active on this cart.
            Remove it first.
        rejection_message:
          type:
            - string
            - 'null'
          description: |
            Human-readable rejection message. Present when valid is false.
            Safe to display to the end user.
          example: This promo code has expired.
    OrderSummary:
      type: object
      required:
        - id
        - location_id
        - status
        - payment_status
        - fulfillment_status
        - item_count
        - total
        - created_at
        - updated_at
      description: >
        Lightweight order summary for list endpoints. Excludes items and
        payments

        arrays to keep list responses compact. Use GET /orders/{order_id} to

        retrieve the full order with items and payments.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the order.
        location_id:
          type: string
          format: uuid
          description: The location (store) this order belongs to.
        customer_id:
          type:
            - string
            - 'null'
          description: |
            Customer identifier for this order. Null for anonymous orders.
            Use GET /orders?customer_id=X to find orders by customer.
          maxLength: 128
        status:
          $ref: '#/components/schemas/OrderStatus'
        payment_status:
          $ref: '#/components/schemas/OrderPaymentStatus'
        fulfillment_status:
          $ref: '#/components/schemas/FulfillmentStatus'
        item_count:
          type: integer
          minimum: 0
          description: Total number of line items in the order.
        handoff_mode:
          type: string
          enum:
            - PICKUP
            - CURBSIDE
            - DELIVERY
            - KIOSK
          description: The handoff mode for this order.
        promo_codes:
          type: array
          items:
            $ref: '#/components/schemas/PromoCode'
          description: Promo codes applied to this order.
        total:
          $ref: '#/components/schemas/Money'
          description: Grand total for the order.
        total_paid:
          $ref: '#/components/schemas/Money'
          description: Sum of all completed payment amounts.
        balance_due:
          $ref: '#/components/schemas/Money'
          description: Remaining amount to be paid.
        created_at:
          type: string
          format: date-time
          description: When the order was created.
        updated_at:
          type: string
          format: date-time
          description: When the order was last modified.
    PaymentRequest:
      type: object
      required:
        - payment_method
        - amount
      description: |
        Request to submit a payment for an order. For split payments, submit
        multiple payment requests against the same order, each with its own
        Idempotency-Key header.

        **Ordering rules for split payments:**
        Submit loyalty points first, then gift cards, then credit/debit cards.
        This ensures maximum redemption of non-cash tenders before charging
        the customer's card.

        **Sequential submission required:** Wait for each payment to reach a
        terminal state (COMPLETED or FAILED) before submitting the next tender.
        Concurrent split payment submissions may produce undefined behavior.
      properties:
        payment_method:
          $ref: '#/components/schemas/PaymentMethod'
        amount:
          $ref: '#/components/schemas/Money'
          description: |
            Amount to charge for this tender. For a single payment, this equals
            the order total. For split payments, this is the portion covered by
            this tender. The sum of all successful payment amounts must equal
            the order total.
        tip_amount:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
          description: |
            Optional tip amount. Typically included on the final tender in a
            split payment sequence. Null or omit if no tip.
        payment_details:
          type:
            - object
            - 'null'
          description: >
            Method-specific payment details. Required fields vary by
            payment_method:


            **CREDIT_CARD / DEBIT_CARD:**

            ```json

            { "token": "tok_visa_4242" }

            ```

            Use a tokenized card reference from your payment form. Never send

            raw card numbers through this API.


            **GIFT_CARD:**

            ```json

            { "card_number": "6789012345678901", "pin": "1234" }

            ```


            **LOYALTY_POINTS:**

            ```json

            { "loyalty_account_id": "LOY-123456" }

            ```


            **DIGITAL_WALLET:**

            ```json

            { "wallet_token": "dw_applepay_abc123", "wallet_type": "apple_pay" }

            ```
          additionalProperties: true
    RefundLineItem:
      type: object
      required:
        - order_item_id
        - quantity
      properties:
        order_item_id:
          type: string
          format: uuid
          description: ID of the order item being refunded.
        quantity:
          type: integer
          minimum: 1
          description: Number of units being refunded for this item.
        reason:
          type:
            - string
            - 'null'
          description: >-
            Optional item-specific reason (overrides the top-level reason for
            this item).
    RefundRequest:
      type: object
      required:
        - amount
        - reason
      description: |
        Request a full or partial refund for an order. The server calculates
        how to distribute the refund across payment methods (e.g., refund to
        gift card first, then credit card) using an internal strategy that
        minimizes cash refunds.

        The refund amount must not exceed the order's refundable balance
        (amount_paid - amount_refunded). If it does, the server returns 422.
      properties:
        amount:
          $ref: '#/components/schemas/Money'
          description: |
            Refund amount in cents. For a full refund, set this to the order's
            total paid amount. For a partial refund, set this to the desired
            refund amount.
        reason:
          type: string
          description: |
            Reason for the refund. This is stored for record-keeping and may
            be visible to the customer.
          enum:
            - CUSTOMER_REQUEST
            - ITEM_UNAVAILABLE
            - INCORRECT_ORDER
            - QUALITY_ISSUE
            - DUPLICATE_CHARGE
            - OTHER
        reason_note:
          type:
            - string
            - 'null'
          description: |
            Free-text note providing additional context for the refund reason.
            Required when reason is OTHER.
          maxLength: 500
        line_items:
          type: array
          items:
            $ref: '#/components/schemas/RefundLineItem'
          description: >
            Optional item-level refund detail. When provided, specifies which

            items are being refunded and their quantities. This is informational

            for record-keeping -- the refund amount is determined by the
            `amount`

            field, not by summing line item prices.
    RefundResponse:
      type: object
      required:
        - id
        - order_id
        - status
        - amount
        - reason
        - created_at
      description: |
        A processed refund. The server determines how to distribute the refund
        across the order's payment methods. The refund_allocations array shows
        how the refund was split.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the refund.
        order_id:
          type: string
          format: uuid
          description: The order this refund applies to.
        status:
          type: string
          enum:
            - PENDING
            - COMPLETED
            - FAILED
          description: |
            Refund processing status. PENDING means the refund has been accepted
            and is being processed. COMPLETED means all refund allocations have
            been processed. FAILED means the refund could not be processed.
        amount:
          $ref: '#/components/schemas/Money'
          description: Total refund amount.
        reason:
          type: string
          enum:
            - CUSTOMER_REQUEST
            - ITEM_UNAVAILABLE
            - INCORRECT_ORDER
            - QUALITY_ISSUE
            - DUPLICATE_CHARGE
            - OTHER
        reason_note:
          type:
            - string
            - 'null'
        refund_allocations:
          type: array
          items:
            type: object
            required:
              - payment_id
              - payment_method
              - amount
            properties:
              payment_id:
                type: string
                format: uuid
                description: The original payment being refunded.
              payment_method:
                $ref: '#/components/schemas/PaymentMethod'
              amount:
                $ref: '#/components/schemas/Money'
                description: Amount refunded to this payment method.
          description: |
            How the refund was distributed across original payments. The server
            uses a minimize-cash-refund strategy: non-cash tenders (gift cards,
            loyalty points) are refunded first, then card payments.
        line_items:
          type: array
          items:
            $ref: '#/components/schemas/RefundLineItem'
          description: Item-level refund details, if provided in the request.
        created_at:
          type: string
          format: date-time
          description: When the refund was created.
    CancelRequest:
      type: object
      properties:
        reason:
          type:
            - string
            - 'null'
          description: |
            Optional human-readable reason for cancellation. Stored on the order
            for reporting and analytics.
          maxLength: 500
          example: Customer changed their mind
      description: |
        Request body for cancelling an order via POST /orders/{order_id}/cancel.

        **Cancellation is only allowed when fulfillment_status is PENDING or
        IN_PROGRESS.** Once the store begins preparing the order (PREPARING or
        later), cancellation requires store intervention and cannot be performed
        via this endpoint.

        **Automatic void/refund:** When an order is cancelled, the server
        automatically voids or refunds all payments attached to the order:
        - Payments in PENDING or AUTHORIZED status are voided.
        - Payments in CAPTURED or COMPLETED status are refunded in full.
        - The refund follows the minimize-cash-refund strategy (loyalty points
          refunded first, then gift cards, then credit/debit).

        No separate refund request is needed after cancellation.
    WebhookSubscription:
      type: object
      required:
        - id
        - url
        - event_types
        - status
        - created_at
      description: |
        A webhook subscription. Represents a registered endpoint that receives
        webhook event deliveries.

        The `signing_secret` field is only included in the POST response when
        the subscription is first created. Store it securely -- it cannot be
        retrieved again. Use it to verify the `X-Tote-Signature` header on
        incoming webhook deliveries.
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this webhook subscription.
        url:
          type: string
          format: uri
          description: The HTTPS URL that receives webhook event deliveries.
        event_types:
          type: array
          items:
            type: string
            enum:
              - order.created
              - order.status_changed
              - order.cancelled
              - stock.updated
              - menu.changed
              - location.hours_changed
          minItems: 1
          description: |
            Event types this subscription receives. Only events matching these
            types are delivered to the subscription URL.
        status:
          type: string
          enum:
            - ACTIVE
            - DISABLED
          description: >
            Subscription status.


            - ACTIVE: Subscription is receiving events normally.

            - DISABLED: Subscription has been disabled due to consecutive
            delivery
              failures. Create a new subscription or contact support to re-enable.
        signing_secret:
          type: string
          writeOnly: true
          description: |
            HMAC-SHA256 signing secret for verifying webhook deliveries. Only
            returned in the POST response when the subscription is created. Not
            included in GET responses. Store this value securely.
          example: whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
        created_at:
          type: string
          format: date-time
          description: When the subscription was created.
    WebhookSubscriptionRequest:
      type: object
      required:
        - url
        - event_types
      description: |
        Request body for creating a webhook subscription.
      properties:
        url:
          type: string
          format: uri
          description: |
            HTTPS URL to receive webhook events. Must use HTTPS -- HTTP URLs
            are rejected with a 422 error.
        event_types:
          type: array
          items:
            type: string
            enum:
              - order.created
              - order.status_changed
              - order.cancelled
              - stock.updated
              - menu.changed
              - location.hours_changed
          minItems: 1
          description: |
            Event types this subscription should receive. At least one event
            type is required.
    AvailabilityStatus:
      type: string
      enum:
        - IN_STOCK
        - LOW_STOCK
        - OUT_OF_STOCK
      description: |
        Item availability status at a location.

        - IN_STOCK: Item is available for ordering.
        - LOW_STOCK: Item is available but quantities are limited. The threshold
          for LOW_STOCK is configured server-side per location.
        - OUT_OF_STOCK: Item is not available for ordering. Do not display this
          item as orderable to customers.
    InventoryItem:
      type: object
      required:
        - item_id
        - name
        - availability_status
        - updated_at
      description: |
        An item's availability status at a specific location. Items not returned
        by the inventory endpoint are not available at that location.
      properties:
        item_id:
          type: string
          format: uuid
          description: Unique identifier for the menu item.
        name:
          type: string
          description: Item name for display convenience.
        availability_status:
          $ref: '#/components/schemas/AvailabilityStatus'
        updated_at:
          type: string
          format: date-time
          description: When this item's availability status was last updated.
    WebhookEvent:
      type: object
      required:
        - event_id
        - event_type
        - created_at
        - data
      description: |
        Base webhook event envelope. All webhook deliveries use this structure,
        with the `data` field containing event-specific payload.

        Use `event_id` for idempotent processing -- store processed IDs and skip
        duplicates caused by retry deliveries.
      properties:
        event_id:
          type: string
          format: uuid
          description: Unique identifier for this event. Use for idempotent processing.
        event_type:
          type: string
          description: >
            The type of event that occurred. Determines the shape of the `data`
            field.
        created_at:
          type: string
          format: date-time
          description: When the event occurred (ISO 8601 UTC).
        data:
          type: object
          description: |
            Event-specific payload. Shape depends on `event_type`.
            See individual event schemas for details.
    WebhookEventOrderCreated:
      allOf:
        - $ref: '#/components/schemas/WebhookEvent'
        - type: object
          properties:
            event_type:
              type: string
              const: order.created
            data:
              type: object
              required:
                - order_id
                - location_id
                - status
                - handoff_mode
                - total
                - created_at
              properties:
                order_id:
                  type: string
                  format: uuid
                  description: Unique identifier for the created order.
                location_id:
                  type: string
                  format: uuid
                  description: The location (store) this order belongs to.
                status:
                  $ref: '#/components/schemas/OrderStatus'
                handoff_mode:
                  type: string
                  enum:
                    - PICKUP
                    - CURBSIDE
                    - DELIVERY
                    - KIOSK
                  description: The handoff mode selected for this order.
                total:
                  $ref: '#/components/schemas/Money'
                  description: Grand total for the order.
                created_at:
                  type: string
                  format: date-time
                  description: When the order was created (checkout time).
              description: |
                Minimal order data. Fetch the full order via
                GET /orders/{order_id} for complete details.
      description: |
        Sent when a new order is placed via checkout. Contains enough data to
        identify the order and its basic attributes. Consumers should fetch the
        full order resource for complete details including items and payments.
    WebhookEventOrderStatusChanged:
      allOf:
        - $ref: '#/components/schemas/WebhookEvent'
        - type: object
          properties:
            event_type:
              type: string
              const: order.status_changed
            data:
              type: object
              required:
                - order_id
                - location_id
                - previous_status
                - current_status
                - previous_fulfillment_status
                - current_fulfillment_status
                - previous_payment_status
                - current_payment_status
                - updated_at
              properties:
                order_id:
                  type: string
                  format: uuid
                  description: Unique identifier for the order.
                location_id:
                  type: string
                  format: uuid
                  description: The location (store) this order belongs to.
                previous_status:
                  $ref: '#/components/schemas/OrderStatus'
                current_status:
                  $ref: '#/components/schemas/OrderStatus'
                previous_fulfillment_status:
                  $ref: '#/components/schemas/FulfillmentStatus'
                current_fulfillment_status:
                  $ref: '#/components/schemas/FulfillmentStatus'
                previous_payment_status:
                  $ref: '#/components/schemas/OrderPaymentStatus'
                current_payment_status:
                  $ref: '#/components/schemas/OrderPaymentStatus'
                updated_at:
                  type: string
                  format: date-time
                  description: When the status change occurred.
              description: |
                All three status fields are included (any of them could change).
                Compare previous and current values to determine what changed.
      description: >
        Sent when any of the order's three status fields change: lifecycle
        status,

        fulfillment status, or payment status. Includes previous and current
        values

        for all three fields so consumers can determine exactly what changed
        without

        fetching the full order.
    WebhookEventOrderCancelled:
      allOf:
        - $ref: '#/components/schemas/WebhookEvent'
        - type: object
          properties:
            event_type:
              type: string
              const: order.cancelled
            data:
              type: object
              required:
                - order_id
                - location_id
                - cancelled_at
              properties:
                order_id:
                  type: string
                  format: uuid
                  description: Unique identifier for the cancelled order.
                location_id:
                  type: string
                  format: uuid
                  description: The location (store) this order belongs to.
                reason:
                  type:
                    - string
                    - 'null'
                  description: >
                    Human-readable cancellation reason. Null if no reason was
                    provided.
                  maxLength: 500
                cancelled_at:
                  type: string
                  format: date-time
                  description: When the order was cancelled.
              description: >
                Cancellation details. Any pending payments are automatically
                voided

                and completed payments are refunded.
      description: >
        Sent when an order is cancelled. This is a separate event from

        `order.status_changed` because cancellation is a distinct business event

        that typically requires different consumer handling (e.g., refund
        processing,

        customer notification, inventory adjustment).
    WebhookEventStockUpdated:
      allOf:
        - $ref: '#/components/schemas/WebhookEvent'
        - type: object
          properties:
            event_type:
              type: string
              const: stock.updated
            data:
              type: object
              required:
                - location_id
                - items
              properties:
                location_id:
                  type: string
                  format: uuid
                  description: The location where stock changed.
                items:
                  type: array
                  items:
                    type: object
                    required:
                      - item_id
                      - name
                      - previous_status
                      - current_status
                      - updated_at
                    properties:
                      item_id:
                        type: string
                        format: uuid
                        description: Unique identifier for the menu item.
                      name:
                        type: string
                        description: Item name for display/logging convenience.
                      previous_status:
                        $ref: '#/components/schemas/AvailabilityStatus'
                      current_status:
                        $ref: '#/components/schemas/AvailabilityStatus'
                      updated_at:
                        type: string
                        format: date-time
                        description: When this item's stock status changed.
                  minItems: 1
                  description: |
                    Items whose availability status changed. A single event can
                    report multiple item changes at the same location.
              description: |
                Stock change details. Use GET /locations/{location_id}/inventory
                as a polling fallback to reconcile.
      description: |
        Sent when item availability changes at a location. A single event can
        contain multiple items that changed status simultaneously (e.g., batch
        inventory update). Use the polling endpoint
        GET /locations/{location_id}/inventory to reconcile.
    WebhookEventMenuChanged:
      allOf:
        - $ref: '#/components/schemas/WebhookEvent'
        - type: object
          properties:
            event_type:
              type: string
              const: menu.changed
            data:
              type: object
              required:
                - location_id
                - version_hash
                - change_type
                - changed_at
              properties:
                location_id:
                  type: string
                  format: uuid
                  description: The location whose menu changed.
                version_hash:
                  type: string
                  description: >
                    New menu version hash. Compare with your cached version to

                    determine if a re-fetch is needed. Matches the
                    `version_hash`

                    field from GET /locations/{location_id}/menu/metadata.
                  example: sha256:b2c3d4e5f6a7
                change_type:
                  type: string
                  enum:
                    - ITEMS_ADDED
                    - ITEMS_REMOVED
                    - ITEMS_MODIFIED
                    - CATEGORIES_CHANGED
                    - FULL_UPDATE
                  description: >
                    The type of menu change that occurred.


                    - ITEMS_ADDED: New items added to the menu.

                    - ITEMS_REMOVED: Items removed from the menu.

                    - ITEMS_MODIFIED: Existing items changed (price, name,
                    modifiers, etc.).

                    - CATEGORIES_CHANGED: Category structure modified (added,
                    removed, reordered).

                    - FULL_UPDATE: Bulk menu update affecting multiple aspects.
                changed_at:
                  type: string
                  format: date-time
                  description: When the menu change occurred.
              description: |
                Menu change details. Re-fetch the full menu via
                GET /locations/{location_id}/menu after receiving this event.
      description: |
        Sent when a location's menu is modified. The consumer should re-fetch
        the full menu via GET /locations/{location_id}/menu. Use `version_hash`
        to avoid unnecessary re-fetches if you already have the latest version.
    WebhookEventLocationHoursChanged:
      allOf:
        - $ref: '#/components/schemas/WebhookEvent'
        - type: object
          properties:
            event_type:
              type: string
              const: location.hours_changed
            data:
              type: object
              required:
                - location_id
                - previous_hours
                - current_hours
                - changed_at
              properties:
                location_id:
                  type: string
                  format: uuid
                  description: The location whose hours changed.
                previous_hours:
                  type: array
                  items:
                    type: object
                    required:
                      - day_of_week
                      - open_time
                      - close_time
                      - is_closed
                    properties:
                      day_of_week:
                        type: string
                        enum:
                          - MONDAY
                          - TUESDAY
                          - WEDNESDAY
                          - THURSDAY
                          - FRIDAY
                          - SATURDAY
                          - SUNDAY
                        description: Day of the week.
                      open_time:
                        type: string
                        description: >-
                          Opening time in HH:MM format (24-hour, location local
                          time).
                      close_time:
                        type: string
                        description: >-
                          Closing time in HH:MM format (24-hour, location local
                          time).
                      is_closed:
                        type: boolean
                        description: Whether the location is closed on this day.
                  description: Business hours before the change.
                current_hours:
                  type: array
                  items:
                    type: object
                    required:
                      - day_of_week
                      - open_time
                      - close_time
                      - is_closed
                    properties:
                      day_of_week:
                        type: string
                        enum:
                          - MONDAY
                          - TUESDAY
                          - WEDNESDAY
                          - THURSDAY
                          - FRIDAY
                          - SATURDAY
                          - SUNDAY
                        description: Day of the week.
                      open_time:
                        type: string
                        description: >-
                          Opening time in HH:MM format (24-hour, location local
                          time).
                      close_time:
                        type: string
                        description: >-
                          Closing time in HH:MM format (24-hour, location local
                          time).
                      is_closed:
                        type: boolean
                        description: Whether the location is closed on this day.
                  description: Business hours after the change.
                effective_date:
                  type:
                    - string
                    - 'null'
                  format: date
                  description: |
                    Date when the new hours take effect (YYYY-MM-DD). Null means
                    the change is effective immediately.
                changed_at:
                  type: string
                  format: date-time
                  description: When the hours change was made.
              description: |
                Includes both previous and current hours so consumers can diff
                without fetching the location resource.
      description: |
        Sent when a location's business hours are modified. Includes both
        previous and current hours for comparison. Use `effective_date` to
        determine when the new hours take effect (null means immediately).
  responses:
    BadRequest:
      description: |
        The request was invalid. Check the `field` property for the specific
        field that caused the error, and `detail` for resolution guidance.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INVALID_REQUEST_ERROR
              message: Request validation failed.
              detail: The field 'quantity' must be a positive integer.
              request_id: b2c3d4e5-f6a7-8901-bcde-f12345678901
              field: items[0].quantity
    Unauthorized:
      description: |
        Authentication failed. The access token is missing, invalid, or expired.
        Request a new token via POST /auth/token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: AUTHENTICATION_ERROR
              message: Invalid or expired access token.
              detail: >-
                The provided Bearer token has expired. Request a new token via
                POST /auth/token.
              request_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              field: null
    TooManyRequests:
      description: |
        Rate limit exceeded. Wait for the duration specified in the
        Retry-After header before making another request.
      headers:
        Retry-After:
          description: |
            Number of seconds to wait before retrying. Use exponential
            backoff with jitter if multiple requests are queued.
          schema:
            type: integer
          example: 60
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: RATE_LIMIT_ERROR
              message: Rate limit exceeded.
              detail: >-
                You have exceeded 100 requests per minute. Wait 60 seconds
                before retrying.
              request_id: e5f6a7b8-c9d0-1234-efab-345678901234
              field: null
    InternalServerError:
      description: |
        An unexpected error occurred on the server. If this persists, contact
        Tote Developer Support with the request_id from the error response.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INTERNAL_ERROR
              message: An unexpected error occurred.
              detail: >-
                Please retry your request. If the problem persists, contact
                developer@totepos.com with the request_id.
              request_id: f6a7b8c9-d0e1-2345-fabc-456789012345
              field: null
    NotFound:
      description: |
        The requested resource does not exist. Verify the resource ID
        and that it belongs to a location your credentials can access.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: NOT_FOUND_ERROR
              message: Resource not found.
              detail: >-
                No location found with ID
                'd7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23'.
              request_id: d4e5f6a7-b8c9-0123-defa-234567890123
              field: null
    UnprocessableEntity:
      description: >
        The request is well-formed but violates a business rule. Common causes
        include:

        modifier selection constraints not met (too few or too many), item not
        available

        or out of stock, quantity exceeds max_allowed_quantity, empty cart at
        calculate

        or checkout, handoff mode missing at checkout, or cart is not in ACTIVE
        status.


        The error response may include multiple validation issues in the detail
        field.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            modifier_validation:
              summary: Missing required modifier selections
              value:
                error:
                  code: INVALID_REQUEST_ERROR
                  message: Cart validation failed.
                  detail: >-
                    Modifier group 'Bread Choice' requires exactly 1 selection
                    but 0 were provided.
                  request_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
                  field: items[0].modifier_selections
            item_unavailable:
              summary: Item no longer available
              value:
                error:
                  code: INVALID_REQUEST_ERROR
                  message: Item is not available.
                  detail: Menu item 'Seasonal Pumpkin Latte' is currently unavailable.
                  request_id: a2b3c4d5-e6f7-8901-bcde-f23456789012
                  field: menu_item_id
    Conflict:
      description: >
        The request conflicts with the current state of the resource. Common
        causes:

        attempting to modify a cart that is already CHECKED_OUT or ABANDONED,

        or submitting an idempotency key that was previously used with a
        different

        request payload.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: CONFLICT_ERROR
              message: Cart has already been checked out.
              detail: >-
                Cart c1d2e3f4-a5b6-7890-cdef-1234567890ab is in CHECKED_OUT
                status and cannot be modified.
              request_id: b3c4d5e6-f7a8-9012-cdef-345678901234
              field: null
    PaymentRequired:
      description: >
        The order requires payment before it can proceed. The order total has
        not

        been fully covered by submitted payments. This response includes the

        remaining balance.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INVALID_REQUEST_ERROR
              message: Payment required.
              detail: >-
                Order total is $19.45 but only $5.00 has been paid. Remaining
                balance: $14.45.
              request_id: c1d2e3f4-a5b6-7890-cdef-1234567890ab
              field: null
  parameters:
    limit:
      name: limit
      in: query
      description: Maximum number of results to return per page.
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    cursor:
      name: cursor
      in: query
      description: >-
        Opaque cursor for pagination. Obtained from the `next_cursor` field of a
        previous response.
      required: false
      schema:
        type: string
    locationId:
      name: location_id
      in: path
      required: true
      description: Unique identifier for the location.
      schema:
        type: string
        format: uuid
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: |
        A unique key (UUID v4) to ensure idempotent request processing.
        Required on all POST, PUT, and DELETE requests.

        If a request is retried with the same key within 24 hours,
        the server returns the cached success response without re-processing.
        Error responses are NOT cached -- retrying after an error with the
        same key will re-execute the request.

        Generate a new UUID v4 for each unique operation. Reuse the same key
        only when retrying a failed or timed-out request.
      required: true
      schema:
        type: string
        format: uuid
        maxLength: 40
      example: d7a8fbb3-07d4-4e3c-b5f2-9a6c8b1e0f23
    cartId:
      name: cart_id
      in: path
      required: true
      description: Unique identifier for the cart.
      schema:
        type: string
        format: uuid
    cartItemId:
      name: item_id
      in: path
      required: true
      description: Unique identifier for the cart item.
      schema:
        type: string
        format: uuid
    orderStatus:
      name: status
      in: query
      description: |
        Filter orders by lifecycle status. Accepts a single value.
        See OrderStatus enum for valid values.
      required: false
      schema:
        $ref: '#/components/schemas/OrderStatus'
    fulfillmentStatus:
      name: fulfillment_status
      in: query
      description: |
        Filter orders by fulfillment status. Accepts a single value.
        See FulfillmentStatus enum for valid values.
      required: false
      schema:
        $ref: '#/components/schemas/FulfillmentStatus'
    locationIdFilter:
      name: location_id
      in: query
      description: |
        Filter orders by location (store). Returns only orders placed at
        the specified location.
      required: false
      schema:
        type: string
        format: uuid
    dateFrom:
      name: date_from
      in: query
      description: |
        Filter orders created on or after this date-time (inclusive).
        ISO 8601 format (e.g., 2026-01-01T00:00:00Z).
      required: false
      schema:
        type: string
        format: date-time
    dateTo:
      name: date_to
      in: query
      description: |
        Filter orders created on or before this date-time (inclusive).
        ISO 8601 format (e.g., 2026-01-31T23:59:59Z).
      required: false
      schema:
        type: string
        format: date-time
    customerIdFilter:
      name: customer_id
      in: query
      required: false
      schema:
        type: string
        maxLength: 128
      description: |
        Filter orders by customer identifier. Returns only orders where
        customer_id matches exactly (case-sensitive). Use this to look up
        a customer's order history.
      example: CUST-12345
    orderId:
      name: order_id
      in: path
      required: true
      description: Unique identifier for the order.
      schema:
        type: string
        format: uuid
    webhookId:
      name: webhook_id
      in: path
      required: true
      description: Unique identifier for the webhook subscription.
      schema:
        type: string
        format: uuid
