openapi: 3.1.0
info:
  title: Booking API
  version: 1.0.0
  description: >
    Full API surface documented from routes/api.php, including info endpoints,
    widget booking endpoints, voucher/payment endpoints, signed calendar endpoint,
    and public API v1 endpoints.
servers:
  - url: https://resevu.com/api
tags:
  - name: Info
  - name: Widget Booking
  - name: Vouchers
  - name: Payments
  - name: Calendar
  - name: Public API v1
paths:
  /:
    get:
      tags: [Info]
      summary: API overview page
      responses:
        '200':
          description: HTML API index page

  /info:
    get:
      tags: [Info]
      summary: API info payload
      responses:
        '200':
          description: JSON metadata and endpoint list

  /public/bookings/bootstrap:
    get:
      tags: [Widget Booking]
      summary: Bootstrap payload for initial widget load
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
      responses:
        '200':
          description: Widget config + employees + massage types with initial pricing

  /public/bookings/employees:
    get:
      tags: [Widget Booking]
      summary: List active bookable employees with available dates
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: days_ahead
          in: query
          required: false
          schema:
            type: integer
        - name: massage_type_id
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Employees with booking-related data and available dates
        '422':
          description: Validation error

  /public/bookings/massage-types:
    get:
      tags: [Widget Booking]
      summary: List active massage types for tenant
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
      responses:
        '200':
          description: Massage types list

  /public/bookings/available-durations:
    get:
      tags: [Widget Booking]
      summary: Get available durations for selected massage type
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: massage_type_id
          in: query
          required: true
          schema:
            type: integer
        - name: is_duo
          in: query
          required: false
          schema:
            type: boolean
        - name: second_massage_type_id
          in: query
          required: false
          schema:
            type: integer
        - name: date
          in: query
          required: false
          schema:
            type: string
            format: date
      responses:
        '200':
          description: Duration options and deposit configuration
        '422':
          description: Validation error

  /public/bookings/available-time-slots:
    get:
      tags: [Widget Booking]
      summary: Get available booking time slots
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: massage_type_id
          in: query
          required: true
          schema:
            type: integer
        - name: duration_id
          in: query
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/DateQuery'
        - name: is_duo
          in: query
          required: false
          schema:
            type: boolean
        - name: second_massage_type_id
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Time slots and employee assignments
        '422':
          description: Validation error

  /public/bookings/price-estimate:
    get:
      tags: [Widget Booking]
      summary: Estimate booking price for selected duration
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: duration_id
          in: query
          required: true
          schema:
            type: integer
        - name: massage_type_id
          in: query
          required: true
          schema:
            type: integer
        - name: coupon_code
          in: query
          required: false
          schema:
            type: string
        - name: date
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: is_duo
          in: query
          required: false
          schema:
            type: boolean
        - name: second_massage_type_id
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Price and deposit estimation
        '422':
          description: Validation or business rule error

  /public/bookings/widget-config:
    get:
      tags: [Widget Booking]
      summary: Widget configuration for current tenant
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
      responses:
        '200':
          description: Currency and widget configuration

  /public/bookings/store:
    post:
      tags: [Widget Booking]
      summary: Create a booking request
      description: CSRF exempt endpoint intended for third-party widget clients.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - firstname
                - massage_type_id
                - duration
                - date
                - start_time
              properties:
                name:
                  type: string
                firstname:
                  type: string
                email:
                  type: string
                  format: email
                  nullable: true
                phone:
                  type: string
                  nullable: true
                massage_type_id:
                  type: integer
                duration:
                  oneOf:
                    - type: integer
                    - type: string
                date:
                  type: string
                  format: date
                start_time:
                  type: string
                  pattern: '^\d{2}:\d{2}$'
                coupon_code:
                  type: string
                  nullable: true
                is_duo:
                  type: boolean
                second_massage_type_id:
                  type: integer
                  nullable: true
                redirect_domain:
                  type: string
                  nullable: true
                tracking_url:
                  type: string
                  nullable: true
                payment_type:
                  type: string
                  enum: [deposit, full]
                language:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Booking accepted
        '422':
          description: Validation or business rule error
        '500':
          description: Unexpected error

  /public/bookings/available-days:
    get:
      tags: [Widget Booking]
      summary: Get available booking days
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: is_duo
          in: query
          required: false
          schema:
            type: boolean
        - name: massage_type_id
          in: query
          required: false
          schema:
            type: integer
        - name: duration_id
          in: query
          required: false
          schema:
            type: integer
        - name: second_massage_type_id
          in: query
          required: false
          schema:
            type: integer
        - name: employee_id
          in: query
          required: false
          schema:
            type: integer
        - name: days_ahead
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Available dates list
        '422':
          description: Validation error

  /public/bookings/coupon/validate:
    get:
      tags: [Widget Booking]
      summary: Validate a coupon code for widget flow
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: code
          in: query
          required: true
          schema:
            type: string
        - name: massage_type_id
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Coupon is valid
        '404':
          description: Coupon not found or invalid
        '422':
          description: Validation error

  /public/vouchers/initiate:
    post:
      tags: [Vouchers]
      summary: Initiate gift voucher payment
      description: CSRF exempt endpoint intended for third-party clients.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [buyer_name, buyer_email, amount]
              properties:
                buyer_name:
                  type: string
                buyer_email:
                  type: string
                  format: email
                amount:
                  type: number
                redirect_domain:
                  type: string
                  nullable: true
                tracking_url:
                  type: string
                  nullable: true
                meta:
                  type: object
                  additionalProperties: true
                language:
                  type: string
                  nullable: true
      responses:
        '200':
          description: Voucher order initiated
        '422':
          description: Validation error

  /payment/start/{booking}:
    get:
      tags: [Payments]
      summary: Start booking payment
      description: Creates a Mollie payment and redirects to checkout.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: booking
          in: path
          required: true
          schema:
            type: integer
        - name: type
          in: query
          required: false
          schema:
            type: string
            enum: [deposit, full]
        - name: redirect_domain
          in: query
          required: false
          schema:
            type: string
      responses:
        '303':
          description: Redirect to Mollie checkout
        '400':
          description: Missing or invalid payment configuration

  /payment/start-voucher/{order}:
    get:
      tags: [Payments]
      summary: Start voucher payment
      description: Creates a Mollie payment and redirects to checkout.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: order
          in: path
          required: true
          schema:
            type: integer
      responses:
        '303':
          description: Redirect to Mollie checkout
        '400':
          description: Missing or invalid payment configuration

  /payment/webhook:
    post:
      tags: [Payments]
      summary: Mollie payment webhook
      description: CSRF exempt callback endpoint.
      requestBody:
        required: false
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                id:
                  type: string
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: string
      responses:
        '200':
          description: Payment status processed
        '404':
          description: Payment or tenant not found

  /public/tenants/{tenant}/bookings/{booking}/calendar.ics:
    get:
      tags: [Calendar]
      summary: Download booking calendar invite
      description: Requires a valid signed URL.
      parameters:
        - name: tenant
          in: path
          required: true
          schema:
            type: string
        - name: booking
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: ICS file
          content:
            text/calendar:
              schema:
                type: string
        '403':
          description: Invalid signature
        '404':
          description: Tenant/booking mismatch

  /public/config:
    get:
      tags: [Public API v1]
      summary: Tenant configuration payload
      description: Protected by initializeTenantFromRequest, publicApiKey and throttle middleware.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/ApiKeyQuery'
        - $ref: '#/components/parameters/ApiKeyHeader'
        - $ref: '#/components/parameters/OriginHeader'
      responses:
        '200':
          description: Tenant/public API configuration payload
        '401':
          description: API key required or invalid

  /public/opening-hours:
    get:
      tags: [Public API v1]
      summary: Opening hours by weekday
      description: Protected by initializeTenantFromRequest, publicApiKey and throttle middleware.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/ApiKeyQuery'
        - $ref: '#/components/parameters/ApiKeyHeader'
        - $ref: '#/components/parameters/OriginHeader'
      responses:
        '200':
          description: Opening hours payload

  /public/employees:
    get:
      tags: [Public API v1]
      summary: List active employees
      description: Protected by initializeTenantFromRequest, publicApiKey and throttle middleware.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/ApiKeyQuery'
        - $ref: '#/components/parameters/ApiKeyHeader'
        - $ref: '#/components/parameters/OriginHeader'
      responses:
        '200':
          description: Employees payload

  /public/employees/available:
    get:
      tags: [Public API v1]
      summary: Available employees and slots for date/duration
      description: Protected by initializeTenantFromRequest, publicApiKey and throttle middleware.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/ApiKeyQuery'
        - $ref: '#/components/parameters/ApiKeyHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - $ref: '#/components/parameters/DateQuery'
        - $ref: '#/components/parameters/DurationMinutes'
        - name: is_duo
          in: query
          required: false
          schema:
            type: boolean
        - name: massage_type_id
          in: query
          required: false
          schema:
            type: integer
        - name: second_massage_type_id
          in: query
          required: false
          schema:
            type: integer
        - name: time
          in: query
          required: false
          schema:
            type: string
            pattern: '^\d{2}:\d{2}$'
      responses:
        '200':
          description: Availability payload
        '422':
          description: Validation or business rule error

  /public/employees/{employee}/availability:
    get:
      tags: [Public API v1]
      summary: Available slots for one employee
      description: Protected by initializeTenantFromRequest, publicApiKey and throttle middleware.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/ApiKeyQuery'
        - $ref: '#/components/parameters/ApiKeyHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: employee
          in: path
          required: true
          schema:
            type: integer
        - $ref: '#/components/parameters/DateQuery'
        - $ref: '#/components/parameters/DurationMinutes'
        - name: massage_type_id
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Employee availability payload
        '404':
          description: Employee not found for tenant
        '422':
          description: Validation or business rule error

  /public/pricing/estimate:
    get:
      tags: [Public API v1]
      summary: Price estimate
      description: Protected by initializeTenantFromRequest, publicApiKey and throttle middleware.
      parameters:
        - $ref: '#/components/parameters/TenantQuery'
        - $ref: '#/components/parameters/TenantHeader'
        - $ref: '#/components/parameters/ApiKeyQuery'
        - $ref: '#/components/parameters/ApiKeyHeader'
        - $ref: '#/components/parameters/OriginHeader'
        - name: duration_id
          in: query
          required: true
          schema:
            type: integer
        - name: coupon_code
          in: query
          required: false
          schema:
            type: string
        - name: massage_type_id
          in: query
          required: false
          schema:
            type: integer
        - name: date
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: is_duo
          in: query
          required: false
          description: >
            Duo flag. If omitted and second_massage_type_id is present,
            duo mode is inferred automatically.
          schema:
            type: boolean
        - name: second_massage_type_id
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: Price estimate payload
        '422':
          description: Validation or business rule error

components:
  parameters:
    TenantQuery:
      name: tenant
      in: query
      required: false
      description: Tenant slug. Required unless X-Tenant header is provided.
      schema:
        type: string
    TenantHeader:
      name: X-Tenant
      in: header
      required: false
      description: Tenant slug header alternative to tenant query parameter.
      schema:
        type: string
    OriginHeader:
      name: Origin
      in: header
      required: false
      description: Request origin. Recommended for all browser/widget use.
      schema:
        type: string
    ApiKeyHeader:
      name: X-Api-Key
      in: header
      required: false
      description: Public API key, required only when tenant setting enforces it.
      schema:
        type: string
    ApiKeyQuery:
      name: api_key
      in: query
      required: false
      description: Query fallback for API key.
      schema:
        type: string
    DateQuery:
      name: date
      in: query
      required: true
      schema:
        type: string
        format: date
    DurationMinutes:
      name: duration_minutes
      in: query
      required: true
      schema:
        type: integer
