openapi: 3.1.0
info:
  title: TheBooking Public API
  version: 4.0.0
  description: Public REST API shipped by TheBooking 4.0.0. WordPress administration routes under /backend are not part of this contract.
servers:
  - url: https://{site}/wp-json/thebooking/v1/API
    variables:
      site:
        default: example.com
        description: WordPress site host
security:
  - BearerToken: []
  - ApiKeyHeader: []
tags:
  - name: Authentication
  - name: Availability
  - name: Services
  - name: Providers
  - name: Customers
  - name: Reservations
paths:
  /auth/verify/:
    get:
      tags: [Authentication]
      operationId: verifyToken
      summary: Verify the current API token
      responses:
        '200':
          description: Token details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthVerifyEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /availability/get/:
    get:
      tags: [Availability]
      operationId: getAvailability
      summary: Get canonical availability slots
      parameters:
        - $ref: '#/components/parameters/MinTimestamp'
        - $ref: '#/components/parameters/MaxTimestamp'
        - $ref: '#/components/parameters/ServicesFilter'
        - $ref: '#/components/parameters/ProvidersFilter'
      responses:
        '200':
          description: Available slots
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      response:
                        type: array
                        items:
                          $ref: '#/components/schemas/Slot'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /services/get/:
    get:
      tags: [Services]
      operationId: getServices
      summary: List services
      parameters:
        - $ref: '#/components/parameters/Full'
      responses:
        '200':
          description: Services
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      response:
                        type: array
                        items:
                          $ref: '#/components/schemas/Service'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /services/add/:
    post:
      tags: [Services]
      operationId: addService
      summary: Create an appointment service
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ServiceCreate'
      responses:
        '200':
          $ref: '#/components/responses/CreatedId'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /services/edit/:
    post:
      tags: [Services]
      operationId: editService
      summary: Update service properties
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ServiceEdit'
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /services/remove/:
    post:
      tags: [Services]
      operationId: removeService
      summary: Remove a service without reservations
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /providers/get/:
    get:
      tags: [Providers]
      operationId: getProviders
      summary: List providers
      responses:
        '200':
          description: Providers
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      response:
                        type: array
                        items:
                          $ref: '#/components/schemas/Provider'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /providers/edit/:
    post:
      tags: [Providers]
      operationId: editProvider
      summary: Update supported provider restrictions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id]
              properties:
                id:
                  oneOf:
                    - type: integer
                    - type: string
                allowedServices:
                  type: array
                  items:
                    type: string
                restrictServices:
                  type: boolean
              additionalProperties: true
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /providers/edit/service/:
    post:
      tags: [Providers]
      operationId: editProviderServiceSettings
      summary: Update supported provider/service properties
      parameters:
        - name: serviceId
          in: query
          required: true
          schema:
            type: string
        - name: providerId
          in: query
          required: true
          schema:
            oneOf:
              - type: integer
              - type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /customers/get/:
    get:
      tags: [Customers]
      operationId: getCustomers
      summary: List customers
      responses:
        '200':
          description: Customers
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      response:
                        type: array
                        items:
                          $ref: '#/components/schemas/Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /customers/add/:
    post:
      tags: [Customers]
      operationId: addCustomer
      summary: Create a customer
      parameters:
        - name: createUser
          in: query
          schema:
            type: boolean
          description: Create a linked WordPress user when wpUserId is not supplied.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerCreate'
      responses:
        '201':
          $ref: '#/components/responses/CreatedId'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /customers/edit/:
    post:
      tags: [Customers]
      operationId: editCustomer
      summary: Update a customer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerEdit'
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /customers/remove/:
    post:
      tags: [Customers]
      operationId: removeCustomer
      summary: Remove a customer without reservations
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /reservations/get/:
    get:
      tags: [Reservations]
      operationId: getReservations
      summary: List reservations
      parameters:
        - $ref: '#/components/parameters/Full'
        - name: past
          in: query
          schema:
            type: boolean
          description: Include past reservations.
        - name: customers
          in: query
          schema:
            type: string
          description: Comma-separated customer IDs.
        - $ref: '#/components/parameters/ServicesFilter'
        - $ref: '#/components/parameters/ProvidersFilter'
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, confirmed, cancelled]
      responses:
        '200':
          description: Reservations
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      response:
                        type: array
                        items:
                          $ref: '#/components/schemas/Reservation'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /reservations/add/:
    post:
      tags: [Reservations]
      operationId: addReservation
      summary: Create one reservation from a canonical slot
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReservationCreate'
      responses:
        '200':
          $ref: '#/components/responses/CreatedId'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Invalid, stale, unbookable, or over-capacity slot; or insufficient token scope.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ErrorEnvelope'
                  - $ref: '#/components/schemas/WpRestError'
        '404':
          $ref: '#/components/responses/NotFound'
  /reservations/edit/:
    post:
      tags: [Reservations]
      operationId: editReservation
      summary: Update or reschedule a reservation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReservationEdit'
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '400':
          $ref: '#/components/responses/ApplicationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /reservations/remove/:
    post:
      tags: [Reservations]
      operationId: removeReservation
      summary: Remove a reservation allowed by the route policy
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    BearerToken:
      type: http
      scheme: bearer
      bearerFormat: TheBooking API token
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-TBK-API-Key
  parameters:
    ResourceId:
      name: id
      in: query
      required: true
      schema:
        type: string
    MinTimestamp:
      name: min_timestamp
      in: query
      required: true
      schema:
        type: integer
        format: int64
    MaxTimestamp:
      name: max_timestamp
      in: query
      required: true
      schema:
        type: integer
        format: int64
    ServicesFilter:
      name: services
      in: query
      schema:
        type: string
      description: Comma-separated service IDs.
    ProvidersFilter:
      name: providers
      in: query
      schema:
        type: string
      description: Comma-separated provider IDs.
    Full:
      name: full
      in: query
      schema:
        type: boolean
      description: Include stored data properties.
  responses:
    Ok:
      description: Operation completed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OkEnvelope'
    CreatedId:
      description: Resource created
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/OkEnvelope'
              - type: object
                required: [id]
                properties:
                  id:
                    type: string
    ApplicationError:
      description: Invalid request or application rule failure
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Unauthorized:
      description: Missing or invalid API token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/WpRestError'
    Forbidden:
      description: Token lacks write scope or the operation is forbidden
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/WpRestError'
              - $ref: '#/components/schemas/ErrorEnvelope'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  schemas:
    OkEnvelope:
      type: object
      required: [status]
      properties:
        status:
          type: string
          const: OK
    ErrorEnvelope:
      type: object
      required: [status, message]
      properties:
        status:
          type: string
          const: KO
        message:
          type: string
    WpRestError:
      type: object
      required: [code, message, data]
      properties:
        code:
          type: string
        message:
          type: string
        data:
          type: object
          properties:
            status:
              type: integer
    AuthVerifyEnvelope:
      allOf:
        - $ref: '#/components/schemas/OkEnvelope'
        - type: object
          required: [response]
          properties:
            response:
              type: object
              required: [name, scopes, siteUrl]
              properties:
                name:
                  type: string
                scopes:
                  type: array
                  items:
                    type: string
                    enum: [read, write]
                siteUrl:
                  type: string
                  format: uri
    Service:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        class:
          type: string
          const: appointment
        color:
          type: [string, 'null']
        data:
          type: object
          additionalProperties: true
      additionalProperties: true
    ServiceCreate:
      type: object
      required: [name]
      properties:
        name:
          type: string
        description:
          type: string
        class:
          type: string
          const: appointment
        color:
          type: [string, 'null']
      additionalProperties: false
    ServiceEdit:
      type: object
      required: [id]
      properties:
        id:
          type: string
        data:
          type: object
          additionalProperties: true
      additionalProperties: true
    Provider:
      type: object
      properties:
        id:
          oneOf:
            - type: integer
            - type: string
        name:
          type: string
        email:
          type: string
          format: email
      additionalProperties: true
    Customer:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        wp_user:
          type: integer
        timezone:
          type: [string, 'null']
        language:
          type: [string, 'null']
      additionalProperties: true
    CustomerCreate:
      type: object
      required: [name, email]
      properties:
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        wpUserId:
          type: integer
        timezone:
          type: [string, 'null']
        language:
          type: string
      additionalProperties: false
    CustomerEdit:
      type: object
      required: [id]
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        wpUserId:
          type: integer
        status:
          type: integer
        timezone:
          type: [string, 'null']
      additionalProperties: false
    Reservation:
      type: object
      properties:
        id:
          type: string
        serviceId:
          type: string
        customerId:
          type: string
        providerId:
          oneOf:
            - type: integer
            - type: string
        start:
          type: integer
          format: int64
        end:
          type: integer
          format: int64
        status:
          type: string
          enum: [pending, confirmed, cancelled]
      additionalProperties: true
    ReservationCreate:
      type: object
      required: [serviceId, customerId, providerId, availabilityId, start, end]
      properties:
        serviceId:
          type: string
        customerId:
          type: string
        providerId:
          oneOf:
            - type: integer
            - type: string
        availabilityId:
          type: string
        start:
          type: integer
          format: int64
        end:
          type: integer
          format: int64
        data:
          type: object
          additionalProperties: true
      additionalProperties: false
    ReservationEdit:
      type: object
      required: [id]
      properties:
        id:
          type: string
        serviceId:
          type: string
        customerId:
          type: string
        providerId:
          oneOf:
            - type: integer
            - type: string
        start:
          type: integer
          format: int64
        end:
          type: integer
          format: int64
        status:
          type: string
          enum: [pending, confirmed, cancelled]
        data:
          type: object
          additionalProperties: true
      additionalProperties: true
    Slot:
      type: object
      properties:
        availabilityId:
          type: string
        serviceId:
          type: string
        providerId:
          oneOf:
            - type: integer
            - type: string
        start:
          type: integer
          format: int64
        end:
          type: integer
          format: int64
        bookable:
          type: boolean
        routingMode:
          type: string
          enum: [manual, auto]
        eligibleProviderIds:
          type: array
          items:
            oneOf:
              - type: integer
              - type: string
        reservationsCount:
          type: integer
        overrides:
          type: object
          additionalProperties: true
      additionalProperties: true
