openapi: 3.1.0
info:
  title: Booking API
  version: 0.1.0
  description: |
    Initial OpenAPI contract for the Booking application API.

    Canonical paths are documented without the .php suffix and assume the server base URL ends with /api.
    Some routes currently accept legacy aliases such as /booking/create or /rate/list; those aliases are noted
    in operation descriptions but are not duplicated everywhere in this first version.

    Public API calls are authenticated with the pair property + X-API-Key.
    When allowed_domains is configured for a property, the request Origin/Referer host must match exactly,
    except for entries that explicitly use a wildcard such as *.example.com.
    Public signed requests must include property in the actual query string and in the canonical query signed by /sign.
servers:
  - url: http://localhost:8000/api
    description: Local development server
tags:
  - name: booking
  - name: rate
  - name: unit
  - name: quote
  - name: signup
  - name: subscription
  - name: stripe
  - name: onboarding
  - name: companies
  - name: config
  - name: admin
  - name: diagnostics
components:
  securitySchemes:
    CookieAuth:
      type: apiKey
      in: cookie
      name: PHPSESSID
      description: Back-office session cookie.
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Public API key for external callers. Used together with the property query parameter.
    TimestampHeader:
      type: apiKey
      in: header
      name: X-Timestamp
      description: Unix timestamp returned by /sign. Public signed requests expire after a short tolerance window.
    SignatureHeader:
      type: apiKey
      in: header
      name: X-Signature
      description: HMAC signature returned by /sign for the target path and canonical query string, including property for public calls.
    PropertyQuery:
      type: apiKey
      in: query
      name: property
      description: Property identifier required with X-API-Key on external calls. If allowed_domains is configured, Origin or Referer must match an exact configured host or an explicit wildcard entry.
    StripeSignature:
      type: apiKey
      in: header
      name: Stripe-Signature
      description: Stripe webhook signature header.
  schemas:
    OkEnvelope:
      type: object
      required: [ok]
      properties:
        ok:
          type: boolean
          const: true
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
      additionalProperties: false
      example:
        error: Parametres manquants
    BookingUnitInput:
      type: object
      properties:
        id:
          type: integer
        adults:
          type: integer
        children:
          type: integer
        babies:
          type: integer
        pets:
          type: integer
        charge_ids:
          type: array
          items:
            type: integer
        charge_catalog_ids:
          type: array
          items:
            type: integer
      additionalProperties: true
    BookingCreateRequest:
      type: object
      required: [name, email, channel_id, unit, checkin, checkout]
      properties:
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        channel_id:
          type: integer
        unit:
          oneOf:
            - type: integer
            - type: array
              items:
                $ref: '#/components/schemas/BookingUnitInput'
        checkin:
          type: string
          format: date
        checkout:
          type: string
          format: date
      additionalProperties: true
    QuoteCalculateRequest:
      type: object
      required: [checkin, checkout, unit]
      properties:
        checkin:
          type: string
          format: date
        checkout:
          type: string
          format: date
        unit:
          type: array
          items:
            $ref: '#/components/schemas/BookingUnitInput'
      additionalProperties: true
    QuoteCheckoutsRequest:
      type: object
      required: [checkin, max_nights, unit]
      properties:
        checkin:
          type: string
          format: date
        max_nights:
          type: integer
          minimum: 1
          maximum: 62
        unit:
          type: array
          items:
            $ref: '#/components/schemas/BookingUnitInput'
      additionalProperties: true
    SignupRegisterRequest:
      type: object
      required: [csrf, property_name, user_name, email, password]
      properties:
        csrf:
          type: string
        property_name:
          type: string
        user_name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        password:
          type: string
          minLength: 8
      additionalProperties: false
    CsrfRequest:
      type: object
      required: [csrf]
      properties:
        csrf:
          type: string
      additionalProperties: true
    SubscriptionCheckoutRequest:
      type: object
      required: [csrf, plan_slug, billing_interval]
      properties:
        csrf:
          type: string
        plan_slug:
          type: string
        billing_interval:
          type: string
          enum: [monthly, annual]
      additionalProperties: false
    StripeOnboardRequest:
      type: object
      required: [csrf]
      properties:
        csrf:
          type: string
        return_url:
          type: string
          format: uri
        refresh_url:
          type: string
          format: uri
      additionalProperties: false
    StripePaymentCreateRequest:
      type: object
      required: [booking_id]
      properties:
        booking_id:
          type: integer
        amount:
          oneOf:
            - type: number
            - type: string
      additionalProperties: false
    StripePaymentRecordRequest:
      type: object
      required: [booking_id, payment_intent_id]
      properties:
        booking_id:
          type: integer
        payment_intent_id:
          type: string
      additionalProperties: false
    RateBulkUpdateRequest:
      type: object
      required: [csrf, property_id, unit_id, from, to, days_of_week, fields]
      properties:
        csrf:
          type: string
        property_id:
          type: integer
        unit_id:
          type: integer
        channel_id:
          type: integer
        from:
          type: string
          format: date
        to:
          type: string
          format: date
        days_of_week:
          type: array
          items:
            type: integer
            minimum: 1
            maximum: 7
        fields:
          type: object
          additionalProperties: true
      additionalProperties: false
    UpdateFieldRequest:
      type: object
      required: [csrf, table, id, field]
      properties:
        csrf:
          type: string
        table:
          type: string
        id:
          type: integer
        field:
          type: string
        value: {}
      additionalProperties: false
    LogErrorRequest:
      type: object
      required: [level, message]
      properties:
        timestamp:
          type: string
        session_id:
          type: string
        level:
          type: string
        message:
          type: string
        context:
          type: object
          additionalProperties: true
        recent_logs:
          type: array
          items: {}
      additionalProperties: true
    SignRequest:
      type: object
      required: [path, queryParams]
      properties:
        path:
          type: string
          enum:
            - /unit
            - /unit/availability
            - /rate
            - /quote/checkouts
            - /quote/calculate
            - /booking
        queryParams:
          type: object
          description: Canonical query parameters to sign. For all public requests, include property here as well as in the actual request URL.
          additionalProperties:
            oneOf:
              - type: string
              - type: integer
              - type: number
              - type: boolean
      additionalProperties: false
    CompanyUpsertRequest:
      type: object
      required: [csrf, company]
      properties:
        csrf:
          type: string
        company:
          type: object
          additionalProperties: true
      additionalProperties: false
    AppConfigUpsertRequest:
      type: object
      required: [csrf, property_id, name, value]
      properties:
        csrf:
          type: string
        id:
          type: integer
        property_id:
          type: integer
        unit_id:
          type: integer
          nullable: true
        name:
          type: string
        value:
          type: string
        description:
          type: string
      additionalProperties: false
    AppConfigDeleteRequest:
      type: object
      required: [csrf, id]
      properties:
        csrf:
          type: string
        id:
          type: integer
      additionalProperties: false
    BookingAmountsRecalcRequest:
      type: object
      required: [csrf, booking_id]
      properties:
        csrf:
          type: string
        property:
          type: integer
          description: Optional back-office property id. If provided, it must match the authenticated session property.
        booking_id:
          type: integer
      additionalProperties: false
    ChannelImportRunRequest:
      type: object
      required: [csrf, unit_id, channel_id]
      properties:
        csrf:
          type: string
        unit_id:
          type: integer
        channel_id:
          type: integer
      additionalProperties: false
    OnboardingStepRequest:
      type: object
      required: [csrf, step]
      properties:
        csrf:
          type: string
        step:
          type: string
        meta:
          type: object
          additionalProperties: true
      additionalProperties: false
    OnboardingPdpChooseRequest:
      type: object
      required: [csrf, mode]
      properties:
        csrf:
          type: string
        mode:
          type: string
          enum: [included, byo]
        provider_name:
          type: string
      additionalProperties: false
    OnboardingCompanyRequest:
      type: object
      required: [csrf, company]
      properties:
        csrf:
          type: string
        company_id:
          type: integer
        company:
          type: object
          additionalProperties: true
      additionalProperties: false
    OnboardingFiscalProfileRequest:
      type: object
      required: [csrf, fiscal_profile]
      properties:
        csrf:
          type: string
        fiscal_profile:
          type: object
          additionalProperties: true
      additionalProperties: false
    OnboardingPdpConnectionRequest:
      type: object
      required: [csrf, pdp_connection]
      properties:
        csrf:
          type: string
        pdp_connection:
          type: object
          additionalProperties: true
      additionalProperties: false
    OnboardingDirectoryRoutingRequest:
      type: object
      required: [csrf, directory_routing]
      properties:
        csrf:
          type: string
        directory_routing:
          type: object
          additionalProperties: true
      additionalProperties: false
    OnboardingUnitsImportRequest:
      type: object
      required: [csrf, units]
      properties:
        csrf:
          type: string
        units:
          type: array
          items:
            type: object
            additionalProperties: true
      additionalProperties: false
  responses:
    Error400:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Error401:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Error403:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Error404:
      description: Not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Error409:
      description: Conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Error422:
      description: Unprocessable entity
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Error500:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
paths:
  /booking:
    post:
      tags: [booking]
      summary: Create a pre-booking
      description: "Public booking creation endpoint. Alias currently accepted by code: /booking/create."
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingCreateRequest'
      responses:
        '200':
          description: Booking created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '409':
          $ref: '#/components/responses/Error409'
        '500':
          $ref: '#/components/responses/Error500'
  /booking/charge/recalc:
    post:
      tags: [booking]
      summary: Recalculate booking charges
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [csrf, booking_id]
              properties:
                csrf:
                  type: string
                booking_id:
                  type: integer
              additionalProperties: false
      responses:
        '200':
          description: Charges recalculated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '403':
          $ref: '#/components/responses/Error403'
  /booking/charge/choice:
    post:
      tags: [booking]
      summary: Enable or disable an optional booking charge
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [csrf, booking_unit_id, charge_catalog_id, enabled]
              properties:
                csrf:
                  type: string
                booking_unit_id:
                  type: integer
                charge_catalog_id:
                  type: integer
                enabled:
                  type: integer
                  enum: [0, 1]
              additionalProperties: false
      responses:
        '200':
          description: Choice updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '404':
          $ref: '#/components/responses/Error404'
  /booking/status:
    post:
      tags: [booking]
      summary: Change booking unit status
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [csrf, booking_unit_id, to_status_id]
              properties:
                csrf:
                  type: string
                booking_unit_id:
                  type: integer
                to_status_id:
                  type: integer
              additionalProperties: false
      responses:
        '200':
          description: Status updated
          content:
            application/json:
              schema:
                type: object
                required: [ok]
                properties:
                  ok:
                    type: boolean
                  unchanged:
                    type: boolean
                  updated:
                    type: integer
        '400':
          $ref: '#/components/responses/Error400'
        '404':
          $ref: '#/components/responses/Error404'
  /booking/amounts/recalc:
    post:
      tags: [booking]
      summary: Recalculate booking amounts
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingAmountsRecalcRequest'
      responses:
        '200':
          description: Amounts recalculated
          content:
            application/json:
              schema:
                type: object
                required: [ok, data]
                properties:
                  ok:
                    type: boolean
                  data:
                    type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '403':
          $ref: '#/components/responses/Error403'
  /rate:
    get:
      tags: [rate]
      summary: Get frontend rates for a unit and date range
      description: "Canonical documented path. Alias currently accepted by code: /rate/list."
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      parameters:
        - name: date_from
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: unit_id
          in: query
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Rates retrieved
          content:
            application/json:
              schema:
                type: object
                required: [ok, params, units]
                properties:
                  ok:
                    type: boolean
                  params:
                    type: object
                    additionalProperties: true
                  units:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: integer
                        name:
                          type: string
                          nullable: true
                        rates:
                          type: array
                          items: {}
                      additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
  /rate/calendar:
    get:
      tags: [rate]
      summary: Get back-office rate calendar
      security:
        - CookieAuth: []
      parameters:
        - name: property_id
          in: query
          schema:
            type: integer
        - name: start_date
          in: query
          schema:
            type: string
            format: date
        - name: end_date
          in: query
          schema:
            type: string
            format: date
        - name: month_start
          in: query
          schema:
            type: string
            format: date
        - name: channel_id
          in: query
          schema:
            type: integer
        - name: q
          in: query
          schema:
            type: string
        - name: unit_ids[]
          in: query
          schema:
            type: array
            items:
              type: integer
      responses:
        '200':
          description: Calendar retrieved
          content:
            application/json:
              schema:
                type: object
                required: [ok, months, units]
                properties:
                  ok:
                    type: boolean
                  months:
                    type: array
                    items:
                      type: object
                      properties:
                        label:
                          type: string
                        start:
                          type: string
                          format: date
                  units:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '403':
          $ref: '#/components/responses/Error403'
  /rate/bulk_update:
    post:
      tags: [rate]
      summary: Bulk update rate calendar fields
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RateBulkUpdateRequest'
      responses:
        '200':
          description: Bulk update applied
          content:
            application/json:
              schema:
                type: object
                required: [ok, success, affected]
                properties:
                  ok:
                    type: boolean
                  success:
                    type: boolean
                  affected:
                    type: integer
        '400':
          $ref: '#/components/responses/Error400'
        '403':
          $ref: '#/components/responses/Error403'
  /unit:
    get:
      tags: [unit]
      summary: List units with charges
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      responses:
        '200':
          description: Units retrieved
          content:
            application/json:
              schema:
                type: object
                required: [ok, count, rows]
                properties:
                  ok:
                    type: boolean
                  count:
                    type: integer
                  rows:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
  /unit/availability:
    get:
      tags: [unit]
      summary: Get availability for a date range
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      parameters:
        - name: date_from
          in: query
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          schema:
            type: string
            format: date
      responses:
        '200':
          description: Availability retrieved
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
  /unit/check_available:
    post:
      tags: [unit]
      summary: Check unit availability from back office
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id_unit, checkin, checkout]
              properties:
                id_unit:
                  type: integer
                checkin:
                  type: string
                  format: date
                checkout:
                  type: string
                  format: date
              additionalProperties: false
      responses:
        '200':
          description: Availability check result
          content:
            application/json:
              schema:
                type: object
                required: [ok, available]
                properties:
                  ok:
                    type: boolean
                  available:
                    type: boolean
        '400':
          $ref: '#/components/responses/Error400'
  /quote/calculate:
    post:
      tags: [quote]
      summary: Calculate a quote for one or more units
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteCalculateRequest'
      responses:
        '200':
          description: Quote calculated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '409':
          $ref: '#/components/responses/Error409'
  /quote/checkouts:
    post:
      tags: [quote]
      summary: Compute possible checkout dates from a check-in date
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuoteCheckoutsRequest'
      responses:
        '200':
          description: Checkout options calculated
          content:
            application/json:
              schema:
                type: object
                required: [ok, property_id, checkin, max_nights, unit_id, adults, children, checkouts]
                properties:
                  ok:
                    type: boolean
                  property_id:
                    type: integer
                  checkin:
                    type: string
                    format: date
                  max_nights:
                    type: integer
                  unit_id:
                    type: integer
                  adults:
                    type: integer
                  children:
                    type: integer
                  checkouts:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
  /signup/register:
    post:
      tags: [signup]
      summary: Register a property and its first user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignupRegisterRequest'
      responses:
        '200':
          description: Account created and logged in
          content:
            application/json:
              schema:
                type: object
                required: [ok, redirect, property_id]
                properties:
                  ok:
                    type: boolean
                  redirect:
                    type: string
                  property_id:
                    type: integer
        '400':
          $ref: '#/components/responses/Error400'
        '409':
          $ref: '#/components/responses/Error409'
        '500':
          $ref: '#/components/responses/Error500'
  /subscription/checkout:
    post:
      tags: [subscription]
      summary: Start Stripe Checkout for a subscription plan
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscriptionCheckoutRequest'
      responses:
        '200':
          description: Checkout started or downgrade scheduled
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      url:
                        type: string
                        format: uri
                      scheduled:
                        type: boolean
                      effective_date:
                        type: string
                      message:
                        type: string
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '404':
          $ref: '#/components/responses/Error404'
        '422':
          $ref: '#/components/responses/Error422'
  /subscription/portal:
    post:
      tags: [subscription]
      summary: Create a Stripe customer portal session
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [csrf]
              properties:
                csrf:
                  type: string
              additionalProperties: false
      responses:
        '200':
          description: Portal URL generated
          content:
            application/json:
              schema:
                type: object
                required: [ok, url]
                properties:
                  ok:
                    type: boolean
                  url:
                    type: string
                    format: uri
        '404':
          $ref: '#/components/responses/Error404'
  /subscription/invoices:
    get:
      tags: [subscription]
      summary: List subscription invoices
      security:
        - CookieAuth: []
      responses:
        '200':
          description: Invoices retrieved
          content:
            application/json:
              schema:
                type: object
                required: [ok, invoices]
                properties:
                  ok:
                    type: boolean
                  invoices:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
  /subscription/cancel:
    post:
      tags: [subscription]
      summary: Cancel subscription at period end
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CsrfRequest'
      responses:
        '200':
          description: Cancellation scheduled
          content:
            application/json:
              schema:
                type: object
                required: [ok, message]
                properties:
                  ok:
                    type: boolean
                  message:
                    type: string
  /subscription/current:
    get:
      tags: [subscription]
      summary: Get current subscription
      security:
        - CookieAuth: []
      responses:
        '200':
          description: Current subscription returned
          content:
            application/json:
              schema:
                type: object
                required: [ok, subscription]
                properties:
                  ok:
                    type: boolean
                  subscription:
                    oneOf:
                      - type: object
                        additionalProperties: true
                      - type: 'null'
  /subscription/cancel_downgrade:
    post:
      tags: [subscription]
      summary: Cancel a scheduled downgrade
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CsrfRequest'
      responses:
        '200':
          description: Downgrade cancellation confirmed
          content:
            application/json:
              schema:
                type: object
                required: [ok, message]
                properties:
                  ok:
                    type: boolean
                  message:
                    type: string
  /stripe/onboard:
    post:
      tags: [stripe]
      summary: Start Stripe Connect onboarding
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StripeOnboardRequest'
      responses:
        '200':
          description: Onboarding link created
          content:
            application/json:
              schema:
                type: object
                required: [ok, stripe]
                properties:
                  ok:
                    type: boolean
                  stripe:
                    type: object
                    required: [connected_account_id, onboarding_url]
                    properties:
                      connected_account_id:
                        type: string
                      onboarding_url:
                        type: string
                        format: uri
        '400':
          $ref: '#/components/responses/Error400'
  /stripe/payment/create:
    post:
      tags: [stripe]
      summary: Create a Stripe PaymentIntent for a booking
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StripePaymentCreateRequest'
      responses:
        '200':
          description: Payment intent created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
  /stripe/payment/record:
    post:
      tags: [stripe]
      summary: Record an existing PaymentIntent in the database
      security:
        - CookieAuth: []
        - ApiKeyAuth: []
          PropertyQuery: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StripePaymentRecordRequest'
      responses:
        '200':
          description: Payment recorded
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      recorded:
                        type: boolean
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
  /stripe/webhook:
    post:
      tags: [stripe]
      summary: Handle Stripe payment webhook via routed API endpoint
      security:
        - StripeSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: Webhook processed
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
  /stripe_webhook:
    post:
      tags: [stripe]
      summary: Handle Stripe SaaS subscription webhook via standalone file endpoint
      description: Legacy/standalone endpoint served by stripe_webhook.php.
      security:
        - StripeSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: Webhook received
          content:
            application/json:
              schema:
                type: object
                required: [received]
                properties:
                  received:
                    type: boolean
        '400':
          $ref: '#/components/responses/Error400'
        '405':
          description: Method not allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/Error500'
  /onboarding/start:
    post:
      tags: [onboarding]
      summary: Start or resume onboarding for the current property
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CsrfRequest'
      responses:
        '200':
          description: Onboarding loaded
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      onboarding:
                        type: object
                        additionalProperties: true
                      checks:
                        type: object
                        additionalProperties: true
                      meta:
                        type: object
                        additionalProperties: true
  /onboarding/{onboardingId}:
    get:
      tags: [onboarding]
      summary: Get an onboarding by id
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Onboarding returned
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      onboarding:
                        type: object
                        additionalProperties: true
                      checks:
                        type: object
                        additionalProperties: true
                      meta:
                        type: object
                        additionalProperties: true
        '404':
          $ref: '#/components/responses/Error404'
  /onboarding/{onboardingId}/step:
    post:
      tags: [onboarding]
      summary: Move onboarding to a step and merge metadata
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingStepRequest'
      responses:
        '200':
          description: Onboarding updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    properties:
                      onboarding:
                        type: object
                        additionalProperties: true
                      checks:
                        type: object
                        additionalProperties: true
                      meta:
                        type: object
                        additionalProperties: true
  /onboarding/{onboardingId}/pdp/choose:
    post:
      tags: [onboarding]
      summary: Select included PDP or external PDP provider
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingPdpChooseRequest'
      responses:
        '200':
          description: PDP choice saved
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
  /onboarding/{onboardingId}/company:
    post:
      tags: [onboarding]
      summary: Create or update the company selected for onboarding
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingCompanyRequest'
      responses:
        '200':
          description: Company saved and linked to onboarding
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
  /onboarding/{onboardingId}/fiscal_profile:
    post:
      tags: [onboarding]
      summary: Save onboarding fiscal profile
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingFiscalProfileRequest'
      responses:
        '200':
          description: Fiscal profile saved
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
  /onboarding/{onboardingId}/pdp/connection:
    post:
      tags: [onboarding]
      summary: Save PDP connection settings
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingPdpConnectionRequest'
      responses:
        '200':
          description: PDP connection saved
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
  /onboarding/{onboardingId}/pdp/directory_routing:
    post:
      tags: [onboarding]
      summary: Save PDP directory routing settings
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingDirectoryRoutingRequest'
      responses:
        '200':
          description: PDP directory routing saved
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
  /onboarding/{onboardingId}/units/import:
    post:
      tags: [onboarding]
      summary: Import units into the current property during onboarding
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OnboardingUnitsImportRequest'
      responses:
        '200':
          description: Units imported
          content:
            application/json:
              schema:
                type: object
                required: [ok, created, skipped, checks]
                properties:
                  ok:
                    type: boolean
                  created:
                    type: integer
                  skipped:
                    type: integer
                  checks:
                    type: object
                    additionalProperties: true
  /onboarding/{onboardingId}/complete:
    post:
      tags: [onboarding]
      summary: Mark onboarding as complete
      security:
        - CookieAuth: []
      parameters:
        - name: onboardingId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CsrfRequest'
      responses:
        '200':
          description: Onboarding completed
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OkEnvelope'
                  - type: object
                    additionalProperties: true
        '400':
          $ref: '#/components/responses/Error400'
        '404':
          $ref: '#/components/responses/Error404'
  /companies:
    get:
      tags: [companies]
      summary: List active companies for the current property
      security:
        - CookieAuth: []
      responses:
        '200':
          description: Company list returned
          content:
            application/json:
              schema:
                type: object
                required: [ok, companies]
                properties:
                  ok:
                    type: boolean
                  companies:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
    post:
      tags: [companies]
      summary: Create a company
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyUpsertRequest'
      responses:
        '200':
          description: Company created
          content:
            application/json:
              schema:
                type: object
                required: [ok, company]
                properties:
                  ok:
                    type: boolean
                  company:
                    type: object
                    additionalProperties: true
  /companies/{companyId}:
    post:
      tags: [companies]
      summary: Update a company
      security:
        - CookieAuth: []
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompanyUpsertRequest'
      responses:
        '200':
          description: Company updated
          content:
            application/json:
              schema:
                type: object
                required: [ok, company]
                properties:
                  ok:
                    type: boolean
                  company:
                    type: object
                    additionalProperties: true
  /config/upsert:
    post:
      tags: [config]
      summary: Create or update an application config value
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AppConfigUpsertRequest'
      responses:
        '200':
          description: Config value persisted
          content:
            application/json:
              schema:
                type: object
                required: [ok, id]
                properties:
                  ok:
                    type: boolean
                  id:
                    type: integer
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
  /config/delete:
    post:
      tags: [config]
      summary: Delete an application config value
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AppConfigDeleteRequest'
      responses:
        '200':
          description: Config deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkEnvelope'
        '400':
          $ref: '#/components/responses/Error400'
        '404':
          $ref: '#/components/responses/Error404'
  /update_field:
    post:
      tags: [admin]
      summary: Update one field in an allowed table
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateFieldRequest'
      responses:
        '200':
          description: Field updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OkEnvelope'
        '400':
          $ref: '#/components/responses/Error400'
        '500':
          $ref: '#/components/responses/Error500'
  /channel_import_run:
    post:
      tags: [admin]
      summary: Trigger a channel import for one unit
      security:
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChannelImportRunRequest'
      responses:
        '200':
          description: Import completed
          content:
            application/json:
              schema:
                type: object
                required: [ok, count]
                properties:
                  ok:
                    type: boolean
                  count:
                    type: integer
        '400':
          $ref: '#/components/responses/Error400'
        '404':
          $ref: '#/components/responses/Error404'
        '500':
          $ref: '#/components/responses/Error500'
  /sign:
    post:
      tags: [diagnostics]
      summary: Generate an HMAC signature for allowed frontend API routes
      security:
        - ApiKeyAuth: []
          PropertyQuery: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignRequest'
      responses:
        '200':
          description: Signature generated
          content:
            application/json:
              schema:
                type: object
                required: [ok, timestamp, signature]
                properties:
                  ok:
                    type: boolean
                  timestamp:
                    type: integer
                  signature:
                    type: string
        '400':
          $ref: '#/components/responses/Error400'
        '403':
          $ref: '#/components/responses/Error403'
        '500':
          $ref: '#/components/responses/Error500'
  /log_error:
    post:
      tags: [diagnostics]
      summary: Store frontend error logs for support
      parameters:
        - name: property
          in: query
          required: false
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LogErrorRequest'
      responses:
        '200':
          description: Error logged
          content:
            application/json:
              schema:
                type: object
                required: [ok, logged_at, session_id]
                properties:
                  ok:
                    type: boolean
                  logged_at:
                    type: string
                  session_id:
                    type: string
        '400':
          $ref: '#/components/responses/Error400'
        '500':
          $ref: '#/components/responses/Error500'