# HollyHR Public API - OpenAPI 3.1 (GENERATED)
# Do not edit by hand. Run pnpm openapi:generate.
# The paths listed in this file are implemented public API routes.
# Webhook management, synthetic test deliveries, public API-origin webhook events,
# integration readiness, and the safe payroll readiness export are implemented.
# Reports, async bulk exports, signed document downloads, payroll/salary integrations,
# SCIM/OAuth partner apps, native expense management, and native accounting sync are
# tracked as future milestones outside this generated contract.
openapi: 3.1.0
info:
  title: HollyHR API Reference
  version: 1.0.0-foundation
  description: Governed beta public API with approved organisation-scoped reads available on every
    plan, signed-webhook management on Standard, and other programmatic writes requiring Standard
    plus exact HollyHR approval. This version covers API-key context, people directory sync, safe
    people setup, conditional setup-field updates, safe lifecycle actions, safe employment writes,
    canonical org-unit reads and person org links, working-pattern reads and writes, safe time-off
    writes, time-off sync, public holidays, document metadata sync, reference data, and outbound
    webhook management. Responses use explicit deny-by-default projections and opaque public ids;
    person ids are stored random tenant-person identifiers while other resource ids remain
    encrypted. orgId is always resolved from the bearer key and is never accepted from request
    input.
servers:
  - url: /api/v1
    description: Public API path on the current HollyHR app origin.
  - url: https://sandbox.hollyhr.com/api/v1
    description: Seeded HollyHR sandbox tenant for hhr_test_ API keys.
security:
  - ApiKeyAuth: []
tags:
  - name: Meta
    description: API-key context.
  - name: Organisation
    description: Safe company profile and active location reads.
  - name: People
    description: People directory projections, safe people setup, safe setup-field updates, and safe
      lifecycle actions.
  - name: Org Units
    description:
      Canonical organisation-structure reads for departments, teams, divisions, cost centres,
      legal entities, and location groups.
  - name: Working Patterns
    description: Reusable working-pattern templates, contracted hours, and FTE assignment data.
  - name: Time Off
    description: Time-off records, safe time-off writes, and public holidays. Notes, reasons, and
      approval decisions are excluded.
  - name: Documents
    description: Read-only document metadata; file bytes are not exposed.
  - name: Custom Fields
    description: Custom field definitions and person custom-field values for provider and partner mappings.
  - name: Reference Data
    description: Read-only low-PII lookup catalogues.
  - name: Provider Mappings
    description:
      Active external/provider ID reconciliation metadata. Requires provider_mappings:read
      plus the relevant resource read scope.
  - name: Exports
    description:
      Safe export packages for operational handoff. Native payroll/accounting connectors and
      raw payroll-sensitive values are not exposed.
  - name: Webhooks
    description: Outbound webhook management, signing, and test delivery.
components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
  schemas:
    ErrorEnvelope:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - invalid_request
                - authentication_required
                - permission_denied
                - not_found
                - rate_limited
                - idempotency_required
                - idempotency_conflict
                - conflict
                - precondition_required
                - precondition_failed
                - internal_error
            message:
              type: string
            request_id:
              type: string
              pattern: ^[a-zA-Z0-9._:-]{8,64}$
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  issue:
                    type: string
                required:
                  - field
                  - issue
                additionalProperties: false
          required:
            - code
            - message
            - request_id
          additionalProperties: false
      required:
        - error
      additionalProperties: false
    MeResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            organisation:
              type: object
              properties:
                id:
                  type: string
                  pattern: ^org_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              required:
                - id
              additionalProperties: false
            api_key:
              type: object
              properties:
                key_id:
                  type: string
                scopes:
                  type: array
                  items:
                    type: string
                    enum:
                      - organisation:read
                      - people:read
                      - people:personal:read
                      - people:avatar:read
                      - people:write
                      - custom_fields:read
                      - custom_fields:write
                      - reference:read
                      - org_units:read
                      - org_units:write
                      - working_patterns:read
                      - working_patterns:write
                      - time_off:read
                      - time_off:write
                      - time_off:balance:write
                      - time_off:config:write
                      - documents:read
                      - provider_mappings:read
                      - payroll_exports:read
                      - webhooks:manage
                      - mcp:write
              required:
                - key_id
                - scopes
              additionalProperties: false
            environment:
              type: object
              properties:
                type:
                  type: string
                  enum:
                    - live
                    - sandbox
                  description: API-key environment. Live keys report live; hhr_test_ keys report sandbox.
                sandbox:
                  type: boolean
                  description: True only when type is sandbox; false when type is live.
              required:
                - type
                - sandbox
              additionalProperties: false
            actor:
              type: object
              properties:
                type:
                  type: string
                  enum:
                    - api_key
                key_id:
                  type: string
              required:
                - type
                - key_id
              additionalProperties: false
          required:
            - organisation
            - api_key
            - environment
            - actor
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    MetadataResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            metadata_version:
              type: string
              enum:
                - v1
            api_version:
              type: string
            generated_from:
              type: string
            routes:
              type: array
              items:
                type: object
                properties:
                  method:
                    type: string
                  path:
                    type: string
                  operation_id:
                    type: string
                  summary:
                    type: string
                  tags:
                    type: array
                    items:
                      type: string
                  required_scope:
                    type:
                      - string
                      - "null"
                    enum:
                      - organisation:read
                      - people:read
                      - people:personal:read
                      - people:avatar:read
                      - people:write
                      - custom_fields:read
                      - custom_fields:write
                      - reference:read
                      - org_units:read
                      - org_units:write
                      - working_patterns:read
                      - working_patterns:write
                      - time_off:read
                      - time_off:write
                      - time_off:balance:write
                      - time_off:config:write
                      - documents:read
                      - provider_mappings:read
                      - payroll_exports:read
                      - webhooks:manage
                      - mcp:write
                      - null
                  path_parameters:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        required:
                          type: boolean
                        type:
                          type: string
                        enum_values:
                          type: array
                          items:
                            type: string
                      required:
                        - name
                        - required
                        - type
                        - enum_values
                      additionalProperties: false
                  query_parameters:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        required:
                          type: boolean
                        type:
                          type: string
                        enum_values:
                          type: array
                          items:
                            type: string
                      required:
                        - name
                        - required
                        - type
                        - enum_values
                      additionalProperties: false
                  response_statuses:
                    type: array
                    items:
                      type: string
                required:
                  - method
                  - path
                  - operation_id
                  - summary
                  - tags
                  - required_scope
                  - path_parameters
                  - query_parameters
                  - response_statuses
                additionalProperties: false
            schemas:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                  fields:
                    type: array
                    items:
                      type: object
                      properties:
                        path:
                          type: string
                        type:
                          type: string
                        required:
                          type: boolean
                        enum_values:
                          type: array
                          items:
                            type: string
                      required:
                        - path
                        - type
                        - required
                        - enum_values
                      additionalProperties: false
                required:
                  - name
                  - fields
                additionalProperties: false
            scopes:
              type: array
              items:
                type: object
                properties:
                  scope:
                    type: string
                    enum:
                      - organisation:read
                      - people:read
                      - people:personal:read
                      - people:avatar:read
                      - people:write
                      - custom_fields:read
                      - custom_fields:write
                      - reference:read
                      - org_units:read
                      - org_units:write
                      - working_patterns:read
                      - working_patterns:write
                      - time_off:read
                      - time_off:write
                      - time_off:balance:write
                      - time_off:config:write
                      - documents:read
                      - provider_mappings:read
                      - payroll_exports:read
                      - webhooks:manage
                      - mcp:write
                  field_categories:
                    type: array
                    items:
                      type: string
                required:
                  - scope
                  - field_categories
                additionalProperties: false
            field_categories:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  label:
                    type: string
                  sensitivity:
                    type: string
                    enum:
                      - standard
                      - standard_hr
                      - elevated
                required:
                  - id
                  - label
                  - sensitivity
                additionalProperties: false
            webhook_events:
              type: array
              items:
                type: object
                properties:
                  event_type:
                    type: string
                    enum:
                      - person.created
                      - person.updated
                      - person.ended
                      - person.reactivated
                      - time_off.created
                      - time_off.updated
                      - time_off.cancelled
                      - time_off_category.created
                      - time_off_category.updated
                      - time_off_category.archived
                      - time_off_settings.updated
                      - org_unit.created
                      - org_unit.updated
                      - org_unit.archived
                      - org_unit.reactivated
                      - working_pattern.created
                      - working_pattern.updated
                      - working_pattern.archived
                      - webhook.test
                  required_scopes:
                    type: array
                    items:
                      type: string
                      enum:
                        - organisation:read
                        - people:read
                        - people:personal:read
                        - people:avatar:read
                        - people:write
                        - custom_fields:read
                        - custom_fields:write
                        - reference:read
                        - org_units:read
                        - org_units:write
                        - working_patterns:read
                        - working_patterns:write
                        - time_off:read
                        - time_off:write
                        - time_off:balance:write
                        - time_off:config:write
                        - documents:read
                        - provider_mappings:read
                        - payroll_exports:read
                        - webhooks:manage
                        - mcp:write
                required:
                  - event_type
                  - required_scopes
                additionalProperties: false
          required:
            - metadata_version
            - api_version
            - generated_from
            - routes
            - schemas
            - scopes
            - field_categories
            - webhook_events
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    Organisation:
      type: object
      properties:
        id:
          type: string
          pattern: ^org_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type:
            - string
            - "null"
        subdomain:
          type:
            - string
            - "null"
        country:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            country_code:
              type: string
          required:
            - id
            - name
            - country_code
          additionalProperties: false
        plan:
          type: string
        logo_url:
          type:
            - string
            - "null"
        primary_color_light:
          type:
            - string
            - "null"
        primary_color_dark:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - subdomain
        - country
        - plan
        - logo_url
        - primary_color_light
        - primary_color_dark
        - created_at
        - updated_at
      additionalProperties: false
    OrganisationResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^org_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type:
                - string
                - "null"
            subdomain:
              type:
                - string
                - "null"
            country:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                name:
                  type: string
                country_code:
                  type: string
              required:
                - id
                - name
                - country_code
              additionalProperties: false
            plan:
              type: string
            logo_url:
              type:
                - string
                - "null"
            primary_color_light:
              type:
                - string
                - "null"
            primary_color_dark:
              type:
                - string
                - "null"
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - subdomain
            - country
            - plan
            - logo_url
            - primary_color_light
            - primary_color_dark
            - created_at
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    OrganisationLocation:
      type: object
      properties:
        id:
          type: string
          pattern: ^loc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        label:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type:
            - string
            - "null"
        address_line_3:
          type:
            - string
            - "null"
        city:
          type: string
        region_state:
          type: string
        postal_code:
          type: string
        country:
          type: object
          properties:
            id:
              type: string
              pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            country_code:
              type: string
          required:
            - id
            - name
            - country_code
          additionalProperties: false
        capacity:
          type:
            - integer
            - "null"
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - label
        - address_line_1
        - address_line_2
        - address_line_3
        - city
        - region_state
        - postal_code
        - country
        - capacity
        - is_active
        - created_at
        - updated_at
      additionalProperties: false
    OrganisationLocationList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^loc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              label:
                type: string
              address_line_1:
                type: string
              address_line_2:
                type:
                  - string
                  - "null"
              address_line_3:
                type:
                  - string
                  - "null"
              city:
                type: string
              region_state:
                type: string
              postal_code:
                type: string
              country:
                type: object
                properties:
                  id:
                    type: string
                    pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  name:
                    type: string
                  country_code:
                    type: string
                required:
                  - id
                  - name
                  - country_code
                additionalProperties: false
              capacity:
                type:
                  - integer
                  - "null"
              is_active:
                type: boolean
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - label
              - address_line_1
              - address_line_2
              - address_line_3
              - city
              - region_state
              - postal_code
              - country
              - capacity
              - is_active
              - created_at
              - updated_at
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    Person:
      type: object
      properties:
        id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        work_email:
          type: string
          format: email
        first_name:
          type:
            - string
            - "null"
        preferred_name:
          type:
            - string
            - "null"
        last_name:
          type:
            - string
            - "null"
        display_name:
          type: string
        staff_id:
          type:
            - string
            - "null"
        job_title:
          type:
            - string
            - "null"
        department:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
          required:
            - id
            - name
          additionalProperties: false
        manager:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            display_name:
              type: string
          required:
            - id
            - display_name
          additionalProperties: false
        work_place:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        work_type:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        working_pattern:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        contracted_weekly_minutes:
          type:
            - integer
            - "null"
          minimum: 0
          maximum: 10080
        full_time_weekly_minutes:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 10080
        fte:
          type:
            - number
            - "null"
        status:
          type:
            - string
            - "null"
        start_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - work_email
        - first_name
        - preferred_name
        - last_name
        - display_name
        - staff_id
        - job_title
        - department
        - manager
        - work_place
        - work_type
        - working_pattern
        - contracted_weekly_minutes
        - full_time_weekly_minutes
        - fte
        - status
        - start_date
        - end_date
        - created_at
        - updated_at
      additionalProperties: false
    PersonPersonalProfile:
      type: object
      properties:
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        middle_names:
          type:
            - string
            - "null"
        date_of_birth:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        personal_email:
          type:
            - string
            - "null"
          format: email
        personal_phone_number:
          type:
            - string
            - "null"
        work_phone_number:
          type:
            - string
            - "null"
        home_address:
          type:
            - object
            - "null"
          properties:
            address_line_1:
              type: string
            address_line_2:
              type:
                - string
                - "null"
            address_line_3:
              type:
                - string
                - "null"
            city:
              type: string
            region_state:
              type: string
            postal_code:
              type: string
            country:
              type: object
              properties:
                id:
                  type: string
                  pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                name:
                  type: string
                country_code:
                  type: string
              required:
                - id
                - name
                - country_code
              additionalProperties: false
          required:
            - address_line_1
            - address_line_2
            - address_line_3
            - city
            - region_state
            - postal_code
            - country
          additionalProperties: false
        updated_at:
          type: string
          format: date-time
      required:
        - person_id
        - middle_names
        - date_of_birth
        - personal_email
        - personal_phone_number
        - work_phone_number
        - home_address
        - updated_at
      additionalProperties: false
    PersonPersonalProfileResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            middle_names:
              type:
                - string
                - "null"
            date_of_birth:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            personal_email:
              type:
                - string
                - "null"
              format: email
            personal_phone_number:
              type:
                - string
                - "null"
            work_phone_number:
              type:
                - string
                - "null"
            home_address:
              type:
                - object
                - "null"
              properties:
                address_line_1:
                  type: string
                address_line_2:
                  type:
                    - string
                    - "null"
                address_line_3:
                  type:
                    - string
                    - "null"
                city:
                  type: string
                region_state:
                  type: string
                postal_code:
                  type: string
                country:
                  type: object
                  properties:
                    id:
                      type: string
                      pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                    name:
                      type: string
                    country_code:
                      type: string
                  required:
                    - id
                    - name
                    - country_code
                  additionalProperties: false
              required:
                - address_line_1
                - address_line_2
                - address_line_3
                - city
                - region_state
                - postal_code
                - country
              additionalProperties: false
            updated_at:
              type: string
              format: date-time
          required:
            - person_id
            - middle_names
            - date_of_birth
            - personal_email
            - personal_phone_number
            - work_phone_number
            - home_address
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    PersonAvatar:
      type: object
      properties:
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        download_url:
          type: string
          format: uri
        expires_at:
          type: string
          format: date-time
        content_type:
          type: string
          enum:
            - image/jpeg
            - image/png
            - image/webp
        byte_size:
          type: integer
          minimum: 1
          maximum: 2097152
        width_px:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 4096
        height_px:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 4096
        updated_at:
          type: string
          format: date-time
      required:
        - person_id
        - download_url
        - expires_at
        - content_type
        - byte_size
        - width_px
        - height_px
        - updated_at
      additionalProperties: false
    PersonAvatarResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            download_url:
              type: string
              format: uri
            expires_at:
              type: string
              format: date-time
            content_type:
              type: string
              enum:
                - image/jpeg
                - image/png
                - image/webp
            byte_size:
              type: integer
              minimum: 1
              maximum: 2097152
            width_px:
              type:
                - integer
                - "null"
              minimum: 1
              maximum: 4096
            height_px:
              type:
                - integer
                - "null"
              minimum: 1
              maximum: 4096
            updated_at:
              type: string
              format: date-time
          required:
            - person_id
            - download_url
            - expires_at
            - content_type
            - byte_size
            - width_px
            - height_px
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    CustomFieldOption:
      type: object
      properties:
        id:
          type: string
          pattern: ^cfo_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        value:
          type: string
        label:
          type: string
        display_order:
          type: integer
          minimum: 0
      required:
        - id
        - value
        - label
        - display_order
      additionalProperties: false
    CustomFieldDefinition:
      type: object
      properties:
        id:
          type: string
          pattern: ^cfd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        key:
          type: string
          minLength: 2
          maxLength: 64
          pattern: ^[a-z][a-z0-9_]{1,63}$
        label:
          type: string
        description:
          type:
            - string
            - "null"
        target_resource:
          type: string
          enum:
            - person
        field_type:
          type: string
          enum:
            - text
            - number
            - date
            - boolean
            - single_select
        sensitivity:
          type: string
          enum:
            - operational
            - personal
            - demographic
            - payroll
        api_access:
          type: string
          enum:
            - read
            - read_write
        display_order:
          type: integer
          minimum: 0
        options:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^cfo_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              value:
                type: string
              label:
                type: string
              display_order:
                type: integer
                minimum: 0
            required:
              - id
              - value
              - label
              - display_order
            additionalProperties: false
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - key
        - label
        - description
        - target_resource
        - field_type
        - sensitivity
        - api_access
        - display_order
        - options
        - created_at
        - updated_at
      additionalProperties: false
    CustomFieldDefinitionList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^cfd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              key:
                type: string
                minLength: 2
                maxLength: 64
                pattern: ^[a-z][a-z0-9_]{1,63}$
              label:
                type: string
              description:
                type:
                  - string
                  - "null"
              target_resource:
                type: string
                enum:
                  - person
              field_type:
                type: string
                enum:
                  - text
                  - number
                  - date
                  - boolean
                  - single_select
              sensitivity:
                type: string
                enum:
                  - operational
                  - personal
                  - demographic
                  - payroll
              api_access:
                type: string
                enum:
                  - read
                  - read_write
              display_order:
                type: integer
                minimum: 0
              options:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      pattern: ^cfo_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                    value:
                      type: string
                    label:
                      type: string
                    display_order:
                      type: integer
                      minimum: 0
                  required:
                    - id
                    - value
                    - label
                    - display_order
                  additionalProperties: false
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - key
              - label
              - description
              - target_resource
              - field_type
              - sensitivity
              - api_access
              - display_order
              - options
              - created_at
              - updated_at
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    PersonCustomFieldValue:
      type: object
      properties:
        field_id:
          type: string
          pattern: ^cfd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        key:
          type: string
          minLength: 2
          maxLength: 64
          pattern: ^[a-z][a-z0-9_]{1,63}$
        label:
          type: string
        description:
          type:
            - string
            - "null"
        field_type:
          type: string
          enum:
            - text
            - number
            - date
            - boolean
            - single_select
        sensitivity:
          type: string
          enum:
            - operational
            - personal
            - demographic
            - payroll
        value:
          anyOf:
            - type: string
            - type: number
            - type: boolean
            - type: "null"
        option:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^cfo_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            value:
              type: string
            label:
              type: string
          required:
            - id
            - value
            - label
          additionalProperties: false
        updated_at:
          type:
            - string
            - "null"
          format: date-time
      required:
        - field_id
        - key
        - label
        - description
        - field_type
        - sensitivity
        - value
        - option
        - updated_at
      additionalProperties: false
    PersonCustomFieldValuesResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            values:
              type: array
              items:
                type: object
                properties:
                  field_id:
                    type: string
                    pattern: ^cfd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  key:
                    type: string
                    minLength: 2
                    maxLength: 64
                    pattern: ^[a-z][a-z0-9_]{1,63}$
                  label:
                    type: string
                  description:
                    type:
                      - string
                      - "null"
                  field_type:
                    type: string
                    enum:
                      - text
                      - number
                      - date
                      - boolean
                      - single_select
                  sensitivity:
                    type: string
                    enum:
                      - operational
                      - personal
                      - demographic
                      - payroll
                  value:
                    anyOf:
                      - type: string
                      - type: number
                      - type: boolean
                      - type: "null"
                  option:
                    type:
                      - object
                      - "null"
                    properties:
                      id:
                        type: string
                        pattern: ^cfo_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                      value:
                        type: string
                      label:
                        type: string
                    required:
                      - id
                      - value
                      - label
                    additionalProperties: false
                  updated_at:
                    type:
                      - string
                      - "null"
                    format: date-time
                required:
                  - field_id
                  - key
                  - label
                  - description
                  - field_type
                  - sensitivity
                  - value
                  - option
                  - updated_at
                additionalProperties: false
          required:
            - person_id
            - values
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    UpdatePersonCustomFieldsRequest:
      type: object
      properties:
        values:
          type: object
          additionalProperties:
            anyOf:
              - type: string
                maxLength: 4000
              - type: number
              - type: boolean
              - type: "null"
      required:
        - values
      additionalProperties: false
    CreatePersonRequest:
      type: object
      properties:
        work_email:
          type: string
          maxLength: 320
          format: email
        first_name:
          type: string
          minLength: 1
          maxLength: 64
        last_name:
          type: string
          minLength: 1
          maxLength: 64
        preferred_name:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 64
        job_title:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 128
        department_org_unit_id:
          type:
            - string
            - "null"
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        manager_id:
          type:
            - string
            - "null"
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        work_place_id:
          type:
            - string
            - "null"
          pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        work_type_id:
          type:
            - string
            - "null"
          pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        start_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
          description: Employment start date. A future date creates invited tenant access until that
            Europe/London calendar date.
      required:
        - work_email
        - first_name
        - last_name
      additionalProperties: false
    UpdatePersonRequest:
      type: object
      properties:
        first_name:
          type: string
          minLength: 1
          maxLength: 64
        last_name:
          type: string
          minLength: 1
          maxLength: 64
        preferred_name:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 64
        job_title:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 128
        department_org_unit_id:
          type:
            - string
            - "null"
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        manager_id:
          type:
            - string
            - "null"
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        work_place_id:
          type:
            - string
            - "null"
          pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        work_type_id:
          type:
            - string
            - "null"
          pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        start_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
          description:
            Employment start date. An invited person's access activates when this Europe/London
            calendar date arrives; existing active access is preserved.
      additionalProperties: false
      description: At least one supported safe setup field must be provided.
      minProperties: 1
    EndPersonRequest:
      type: object
      properties:
        end_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
      required:
        - end_date
      additionalProperties: false
    Employment:
      type: object
      properties:
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        job_title:
          type:
            - string
            - "null"
        department:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        manager:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            display_name:
              type: string
          required:
            - id
            - display_name
          additionalProperties: false
        work_place:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        work_type:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        working_pattern:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        contracted_weekly_minutes:
          type:
            - integer
            - "null"
          minimum: 0
          maximum: 10080
        full_time_weekly_minutes:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 10080
        fte:
          type:
            - number
            - "null"
        start_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        updated_at:
          type: string
          format: date-time
      required:
        - person_id
        - job_title
        - department
        - manager
        - work_place
        - work_type
        - working_pattern
        - contracted_weekly_minutes
        - full_time_weekly_minutes
        - fte
        - start_date
        - end_date
        - updated_at
      additionalProperties: false
    EmploymentResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            job_title:
              type:
                - string
                - "null"
            department:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            manager:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                display_name:
                  type: string
              required:
                - id
                - display_name
              additionalProperties: false
            work_place:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            work_type:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            working_pattern:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            contracted_weekly_minutes:
              type:
                - integer
                - "null"
              minimum: 0
              maximum: 10080
            full_time_weekly_minutes:
              type:
                - integer
                - "null"
              minimum: 1
              maximum: 10080
            fte:
              type:
                - number
                - "null"
            start_date:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            end_date:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            updated_at:
              type: string
              format: date-time
          required:
            - person_id
            - job_title
            - department
            - manager
            - work_place
            - work_type
            - working_pattern
            - contracted_weekly_minutes
            - full_time_weekly_minutes
            - fte
            - start_date
            - end_date
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    UpdateEmploymentRequest:
      type: object
      properties:
        job_title:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 128
        department_org_unit_id:
          type:
            - string
            - "null"
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        manager_id:
          type:
            - string
            - "null"
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        work_place_id:
          type:
            - string
            - "null"
          pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        work_type_id:
          type:
            - string
            - "null"
          pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        working_pattern_id:
          type:
            - string
            - "null"
          pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        contracted_weekly_minutes:
          type: integer
          minimum: 0
          maximum: 10080
        start_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
          description:
            Effective employment start date. An invited person's access activates when this
            Europe/London calendar date arrives; existing active access is preserved.
      additionalProperties: false
      description: At least one supported safe employment field must be provided.
    EmploymentHistory:
      type: object
      properties:
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        job_title:
          type:
            - string
            - "null"
        department:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        manager:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            display_name:
              type: string
          required:
            - id
            - display_name
          additionalProperties: false
        work_place:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        work_type:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        working_pattern:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            label:
              type: string
          required:
            - id
            - label
          additionalProperties: false
        contracted_weekly_minutes:
          type:
            - integer
            - "null"
          minimum: 0
          maximum: 10080
        full_time_weekly_minutes:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 10080
        fte:
          type:
            - number
            - "null"
        start_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        updated_at:
          type: string
          format: date-time
        id:
          type: string
          pattern: ^eh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        created_at:
          type: string
          format: date-time
      required:
        - person_id
        - job_title
        - department
        - manager
        - work_place
        - work_type
        - working_pattern
        - contracted_weekly_minutes
        - full_time_weekly_minutes
        - fte
        - start_date
        - end_date
        - updated_at
        - id
        - created_at
      additionalProperties: false
    EmploymentHistoryPage:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              person_id:
                type: string
                pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
              job_title:
                type:
                  - string
                  - "null"
              department:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  label:
                    type: string
                required:
                  - id
                  - label
                additionalProperties: false
              manager:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                  display_name:
                    type: string
                required:
                  - id
                  - display_name
                additionalProperties: false
              work_place:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  label:
                    type: string
                required:
                  - id
                  - label
                additionalProperties: false
              work_type:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  label:
                    type: string
                required:
                  - id
                  - label
                additionalProperties: false
              working_pattern:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  label:
                    type: string
                required:
                  - id
                  - label
                additionalProperties: false
              contracted_weekly_minutes:
                type:
                  - integer
                  - "null"
                minimum: 0
                maximum: 10080
              full_time_weekly_minutes:
                type:
                  - integer
                  - "null"
                minimum: 1
                maximum: 10080
              fte:
                type:
                  - number
                  - "null"
              start_date:
                type:
                  - string
                  - "null"
                pattern: ^\d{4}-\d{2}-\d{2}$
              end_date:
                type:
                  - string
                  - "null"
                pattern: ^\d{4}-\d{2}-\d{2}$
              updated_at:
                type: string
                format: date-time
              id:
                type: string
                pattern: ^eh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              created_at:
                type: string
                format: date-time
            required:
              - person_id
              - job_title
              - department
              - manager
              - work_place
              - work_type
              - working_pattern
              - contracted_weekly_minutes
              - full_time_weekly_minutes
              - fte
              - start_date
              - end_date
              - updated_at
              - id
              - created_at
            additionalProperties: false
        pagination:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
            has_more:
              type: boolean
            limit:
              type: integer
              minimum: 1
              maximum: 100
          required:
            - next_cursor
            - has_more
            - limit
          additionalProperties: false
      required:
        - data
        - pagination
      additionalProperties: false
    EmploymentHistoryResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            job_title:
              type:
                - string
                - "null"
            department:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            manager:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                display_name:
                  type: string
              required:
                - id
                - display_name
              additionalProperties: false
            work_place:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            work_type:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            working_pattern:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            contracted_weekly_minutes:
              type:
                - integer
                - "null"
              minimum: 0
              maximum: 10080
            full_time_weekly_minutes:
              type:
                - integer
                - "null"
              minimum: 1
              maximum: 10080
            fte:
              type:
                - number
                - "null"
            start_date:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            end_date:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            updated_at:
              type: string
              format: date-time
            id:
              type: string
              pattern: ^eh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            created_at:
              type: string
              format: date-time
          required:
            - person_id
            - job_title
            - department
            - manager
            - work_place
            - work_type
            - working_pattern
            - contracted_weekly_minutes
            - full_time_weekly_minutes
            - fte
            - start_date
            - end_date
            - updated_at
            - id
            - created_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    EmploymentHistoryQuery:
      type: object
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 25
        cursor:
          type: string
          minLength: 1
      additionalProperties: false
      description: Cursor pagination parameters for a person's safe employment-history rows.
    CreateEmploymentHistoryRequest:
      type: object
      properties:
        job_title:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 128
        department_org_unit_id:
          type:
            - string
            - "null"
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        manager_id:
          type:
            - string
            - "null"
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        work_place_id:
          type:
            - string
            - "null"
          pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        work_type_id:
          type:
            - string
            - "null"
          pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        working_pattern_id:
          type:
            - string
            - "null"
          pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        contracted_weekly_minutes:
          type: integer
          minimum: 0
          maximum: 10080
        start_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
      required:
        - start_date
      additionalProperties: false
    UpdateEmploymentHistoryRequest:
      type: object
      properties:
        job_title:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 128
        department_org_unit_id:
          type:
            - string
            - "null"
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        manager_id:
          type:
            - string
            - "null"
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        work_place_id:
          type:
            - string
            - "null"
          pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        work_type_id:
          type:
            - string
            - "null"
          pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        start_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
      additionalProperties: false
      description: At least one supported safe employment-history correction field must be provided.
    ReactivatePersonRequest:
      type: object
      properties: {}
      additionalProperties: false
      description: No request fields are accepted for reactivation.
    OrgUnit:
      type: object
      properties:
        id:
          type: string
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        type:
          type: string
          enum:
            - department
            - team
            - division
            - cost_centre
            - legal_entity
            - location_group
        name:
          type: string
        code:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        parent:
          type:
            - object
            - "null"
          properties:
            id:
              type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            type:
              type: string
              enum:
                - department
                - team
                - division
                - cost_centre
                - legal_entity
                - location_group
            name:
              type: string
            code:
              type:
                - string
                - "null"
          required:
            - id
            - type
            - name
            - code
          additionalProperties: false
        active_from:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        active_to:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        is_archived:
          type: boolean
        archived_at:
          type:
            - string
            - "null"
          format: date-time
        source:
          type: string
          enum:
            - manual
            - public_api
            - import
            - provider
        provider:
          type: object
          properties:
            provider_name:
              type:
                - string
                - "null"
            external_id:
              type:
                - string
                - "null"
            remote_id:
              type:
                - string
                - "null"
            remote_deleted_at:
              type:
                - string
                - "null"
              format: date-time
          required:
            - provider_name
            - external_id
            - remote_id
            - remote_deleted_at
          additionalProperties: false
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - type
        - name
        - code
        - description
        - parent
        - active_from
        - active_to
        - is_archived
        - archived_at
        - source
        - provider
        - created_at
        - updated_at
      additionalProperties: false
    OrgUnitList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              type:
                type: string
                enum:
                  - department
                  - team
                  - division
                  - cost_centre
                  - legal_entity
                  - location_group
              name:
                type: string
              code:
                type:
                  - string
                  - "null"
              description:
                type:
                  - string
                  - "null"
              parent:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  type:
                    type: string
                    enum:
                      - department
                      - team
                      - division
                      - cost_centre
                      - legal_entity
                      - location_group
                  name:
                    type: string
                  code:
                    type:
                      - string
                      - "null"
                required:
                  - id
                  - type
                  - name
                  - code
                additionalProperties: false
              active_from:
                type: string
                pattern: ^\d{4}-\d{2}-\d{2}$
              active_to:
                type:
                  - string
                  - "null"
                pattern: ^\d{4}-\d{2}-\d{2}$
              is_archived:
                type: boolean
              archived_at:
                type:
                  - string
                  - "null"
                format: date-time
              source:
                type: string
                enum:
                  - manual
                  - public_api
                  - import
                  - provider
              provider:
                type: object
                properties:
                  provider_name:
                    type:
                      - string
                      - "null"
                  external_id:
                    type:
                      - string
                      - "null"
                  remote_id:
                    type:
                      - string
                      - "null"
                  remote_deleted_at:
                    type:
                      - string
                      - "null"
                    format: date-time
                required:
                  - provider_name
                  - external_id
                  - remote_id
                  - remote_deleted_at
                additionalProperties: false
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - type
              - name
              - code
              - description
              - parent
              - active_from
              - active_to
              - is_archived
              - archived_at
              - source
              - provider
              - created_at
              - updated_at
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    OrgUnitResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            type:
              type: string
              enum:
                - department
                - team
                - division
                - cost_centre
                - legal_entity
                - location_group
            name:
              type: string
            code:
              type:
                - string
                - "null"
            description:
              type:
                - string
                - "null"
            parent:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                type:
                  type: string
                  enum:
                    - department
                    - team
                    - division
                    - cost_centre
                    - legal_entity
                    - location_group
                name:
                  type: string
                code:
                  type:
                    - string
                    - "null"
              required:
                - id
                - type
                - name
                - code
              additionalProperties: false
            active_from:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            active_to:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            is_archived:
              type: boolean
            archived_at:
              type:
                - string
                - "null"
              format: date-time
            source:
              type: string
              enum:
                - manual
                - public_api
                - import
                - provider
            provider:
              type: object
              properties:
                provider_name:
                  type:
                    - string
                    - "null"
                external_id:
                  type:
                    - string
                    - "null"
                remote_id:
                  type:
                    - string
                    - "null"
                remote_deleted_at:
                  type:
                    - string
                    - "null"
                  format: date-time
              required:
                - provider_name
                - external_id
                - remote_id
                - remote_deleted_at
              additionalProperties: false
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - type
            - name
            - code
            - description
            - parent
            - active_from
            - active_to
            - is_archived
            - archived_at
            - source
            - provider
            - created_at
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    CreateOrgUnitRequest:
      type: object
      properties:
        type:
          type: string
          enum:
            - department
            - team
            - division
            - cost_centre
            - legal_entity
            - location_group
        name:
          type: string
          minLength: 1
          maxLength: 128
        code:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 64
        description:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 2000
        parent_org_unit_id:
          type:
            - string
            - "null"
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        active_from:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        active_to:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        provider:
          type:
            - object
            - "null"
          properties:
            provider_name:
              type: string
              minLength: 1
              maxLength: 64
              pattern: ^[a-z0-9][a-z0-9._:-]*$
            external_id:
              type: string
              minLength: 1
              maxLength: 255
            remote_id:
              type:
                - string
                - "null"
              minLength: 1
              maxLength: 255
          required:
            - provider_name
            - external_id
          additionalProperties: false
      required:
        - type
        - name
      additionalProperties: false
    UpdateOrgUnitRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 128
        code:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 64
        description:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 2000
        parent_org_unit_id:
          type:
            - string
            - "null"
          pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        active_from:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        active_to:
          type:
            - string
            - "null"
          pattern: ^\d{4}-\d{2}-\d{2}$
        provider:
          type:
            - object
            - "null"
          properties:
            provider_name:
              type: string
              minLength: 1
              maxLength: 64
              pattern: ^[a-z0-9][a-z0-9._:-]*$
            external_id:
              type: string
              minLength: 1
              maxLength: 255
            remote_id:
              type:
                - string
                - "null"
              minLength: 1
              maxLength: 255
          required:
            - provider_name
            - external_id
          additionalProperties: false
      additionalProperties: false
    ArchiveOrgUnitResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            type:
              type: string
              enum:
                - department
                - team
                - division
                - cost_centre
                - legal_entity
                - location_group
            name:
              type: string
            code:
              type:
                - string
                - "null"
            description:
              type:
                - string
                - "null"
            parent:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                type:
                  type: string
                  enum:
                    - department
                    - team
                    - division
                    - cost_centre
                    - legal_entity
                    - location_group
                name:
                  type: string
                code:
                  type:
                    - string
                    - "null"
              required:
                - id
                - type
                - name
                - code
              additionalProperties: false
            active_from:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            active_to:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            is_archived:
              type: boolean
            archived_at:
              type:
                - string
                - "null"
              format: date-time
            source:
              type: string
              enum:
                - manual
                - public_api
                - import
                - provider
            provider:
              type: object
              properties:
                provider_name:
                  type:
                    - string
                    - "null"
                external_id:
                  type:
                    - string
                    - "null"
                remote_id:
                  type:
                    - string
                    - "null"
                remote_deleted_at:
                  type:
                    - string
                    - "null"
                  format: date-time
              required:
                - provider_name
                - external_id
                - remote_id
                - remote_deleted_at
              additionalProperties: false
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - type
            - name
            - code
            - description
            - parent
            - active_from
            - active_to
            - is_archived
            - archived_at
            - source
            - provider
            - created_at
            - updated_at
          additionalProperties: false
        archived:
          type: boolean
          enum:
            - true
      required:
        - data
        - archived
      additionalProperties: false
    ReactivateOrgUnitResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            type:
              type: string
              enum:
                - department
                - team
                - division
                - cost_centre
                - legal_entity
                - location_group
            name:
              type: string
            code:
              type:
                - string
                - "null"
            description:
              type:
                - string
                - "null"
            parent:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                type:
                  type: string
                  enum:
                    - department
                    - team
                    - division
                    - cost_centre
                    - legal_entity
                    - location_group
                name:
                  type: string
                code:
                  type:
                    - string
                    - "null"
              required:
                - id
                - type
                - name
                - code
              additionalProperties: false
            active_from:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            active_to:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            is_archived:
              type: boolean
            archived_at:
              type:
                - string
                - "null"
              format: date-time
            source:
              type: string
              enum:
                - manual
                - public_api
                - import
                - provider
            provider:
              type: object
              properties:
                provider_name:
                  type:
                    - string
                    - "null"
                external_id:
                  type:
                    - string
                    - "null"
                remote_id:
                  type:
                    - string
                    - "null"
                remote_deleted_at:
                  type:
                    - string
                    - "null"
                  format: date-time
              required:
                - provider_name
                - external_id
                - remote_id
                - remote_deleted_at
              additionalProperties: false
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - type
            - name
            - code
            - description
            - parent
            - active_from
            - active_to
            - is_archived
            - archived_at
            - source
            - provider
            - created_at
            - updated_at
          additionalProperties: false
        reactivated:
          type: boolean
          enum:
            - true
      required:
        - data
        - reactivated
      additionalProperties: false
    PersonOrgLinksResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            memberships:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    pattern: ^org_unit_membership_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  person_id:
                    type: string
                    pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                  org_unit:
                    type: object
                    properties:
                      id:
                        type: string
                        pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                      type:
                        type: string
                        enum:
                          - department
                          - team
                          - division
                          - cost_centre
                          - legal_entity
                          - location_group
                      name:
                        type: string
                      code:
                        type:
                          - string
                          - "null"
                      archived_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                    required:
                      - id
                      - type
                      - name
                      - code
                      - archived_at
                    additionalProperties: false
                  role:
                    type: string
                    enum:
                      - member
                      - manager
                      - owner
                  is_primary:
                    type: boolean
                  is_current:
                    type: boolean
                    description:
                      "Whether today is inside the membership window: effective_from is on or before today
                      and effective_to is null or after today."
                  effective_from:
                    type: string
                    pattern: ^\d{4}-\d{2}-\d{2}$
                    description: First date the membership is effective.
                  effective_to:
                    type:
                      - string
                      - "null"
                    pattern: ^\d{4}-\d{2}-\d{2}$
                    description: First date the membership is no longer effective (exclusive), or null.
                  source:
                    type: string
                    enum:
                      - manual
                      - public_api
                      - import
                      - provider
                  provider:
                    type: object
                    properties:
                      provider_name:
                        type:
                          - string
                          - "null"
                      external_id:
                        type:
                          - string
                          - "null"
                      remote_id:
                        type:
                          - string
                          - "null"
                      remote_deleted_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                    required:
                      - provider_name
                      - external_id
                      - remote_id
                      - remote_deleted_at
                    additionalProperties: false
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
                required:
                  - id
                  - person_id
                  - org_unit
                  - role
                  - is_primary
                  - is_current
                  - effective_from
                  - effective_to
                  - source
                  - provider
                  - created_at
                  - updated_at
                additionalProperties: false
            relationships:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    pattern: ^person_relationship_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  type:
                    type: string
                    enum:
                      - line_manager
                      - dotted_line_manager
                      - approver
                      - hr_partner
                  related_person:
                    type: object
                    properties:
                      id:
                        type: string
                        pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                      display_name:
                        type: string
                    required:
                      - id
                      - display_name
                    additionalProperties: false
                  is_primary:
                    type: boolean
                  is_current:
                    type: boolean
                    description:
                      "Whether today is inside the relationship window: effective_from is on or before today
                      and effective_to is null or after today."
                  effective_from:
                    type: string
                    pattern: ^\d{4}-\d{2}-\d{2}$
                    description: First date the relationship is effective.
                  effective_to:
                    type:
                      - string
                      - "null"
                    pattern: ^\d{4}-\d{2}-\d{2}$
                    description: First date the relationship is no longer effective (exclusive), or null.
                  source:
                    type: string
                    enum:
                      - manual
                      - public_api
                      - import
                      - provider
                  provider:
                    type: object
                    properties:
                      provider_name:
                        type:
                          - string
                          - "null"
                      external_id:
                        type:
                          - string
                          - "null"
                      remote_id:
                        type:
                          - string
                          - "null"
                      remote_deleted_at:
                        type:
                          - string
                          - "null"
                        format: date-time
                    required:
                      - provider_name
                      - external_id
                      - remote_id
                      - remote_deleted_at
                    additionalProperties: false
                  created_at:
                    type: string
                    format: date-time
                  updated_at:
                    type: string
                    format: date-time
                required:
                  - id
                  - type
                  - related_person
                  - is_primary
                  - is_current
                  - effective_from
                  - effective_to
                  - source
                  - provider
                  - created_at
                  - updated_at
                additionalProperties: false
          required:
            - person_id
            - memberships
            - relationships
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    ProviderMapping:
      type: object
      properties:
        provider_name:
          type: string
        provider_account_id:
          type: string
        remote_object_type:
          type: string
        external_id:
          type: string
        remote_id:
          type:
            - string
            - "null"
        resource_type:
          type: string
          enum:
            - person
            - org_unit
            - person_org_unit_membership
            - person_relationship
            - custom_field_definition
            - time_off
            - working_pattern
        resource_id:
          anyOf:
            - type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            - type: string
              pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            - type: string
              pattern: ^org_unit_membership_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            - type: string
              pattern: ^person_relationship_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            - type: string
              pattern: ^cfd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            - type: string
              pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            - type: string
              pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        sync_status:
          type: string
          enum:
            - linked
            - pending
            - conflict
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - provider_name
        - provider_account_id
        - remote_object_type
        - external_id
        - remote_id
        - resource_type
        - resource_id
        - sync_status
        - created_at
        - updated_at
      additionalProperties: false
    ProviderMappingList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              provider_name:
                type: string
              provider_account_id:
                type: string
              remote_object_type:
                type: string
              external_id:
                type: string
              remote_id:
                type:
                  - string
                  - "null"
              resource_type:
                type: string
                enum:
                  - person
                  - org_unit
                  - person_org_unit_membership
                  - person_relationship
                  - custom_field_definition
                  - time_off
                  - working_pattern
              resource_id:
                anyOf:
                  - type: string
                    pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                  - type: string
                    pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  - type: string
                    pattern: ^org_unit_membership_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  - type: string
                    pattern: ^person_relationship_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  - type: string
                    pattern: ^cfd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  - type: string
                    pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  - type: string
                    pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              sync_status:
                type: string
                enum:
                  - linked
                  - pending
                  - conflict
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - provider_name
              - provider_account_id
              - remote_object_type
              - external_id
              - remote_id
              - resource_type
              - resource_id
              - sync_status
              - created_at
              - updated_at
            additionalProperties: false
        pagination:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
            has_more:
              type: boolean
            limit:
              type: integer
              minimum: 1
              maximum: 100
          required:
            - next_cursor
            - has_more
            - limit
          additionalProperties: false
      required:
        - data
        - pagination
      additionalProperties: false
    IntegrationReadiness:
      type: object
      properties:
        contract_version:
          type: string
          enum:
            - 2026-07-07.integration-control-plane.v1
        generated_at:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - ready
            - needs_attention
            - not_configured
        domains:
          type: object
          properties:
            people_lifecycle:
              type: object
              properties:
                status:
                  type: string
                  enum:
                    - ready
                    - needs_attention
                    - not_configured
                issues:
                  type: array
                  items:
                    type: object
                    properties:
                      code:
                        type: string
                      severity:
                        type: string
                        enum:
                          - blocker
                          - warning
                          - info
                      domain:
                        type: string
                        enum:
                          - people_lifecycle
                          - org_structure
                          - webhooks
                      message:
                        type: string
                      action:
                        type: string
                    required:
                      - code
                      - severity
                      - domain
                      - message
                      - action
                    additionalProperties: false
                metrics:
                  type: object
                  properties:
                    active_people:
                      type: integer
                      minimum: 0
                    active_missing_start_date:
                      type: integer
                      minimum: 0
                    archived_without_end_date:
                      type: integer
                      minimum: 0
                    active_with_past_end_date:
                      type: integer
                      minimum: 0
                  required:
                    - active_people
                    - active_missing_start_date
                    - archived_without_end_date
                    - active_with_past_end_date
                  additionalProperties: false
              required:
                - status
                - issues
                - metrics
              additionalProperties: false
            org_structure:
              type: object
              properties:
                status:
                  type: string
                  enum:
                    - ready
                    - needs_attention
                    - not_configured
                issues:
                  type: array
                  items:
                    type: object
                    properties:
                      code:
                        type: string
                      severity:
                        type: string
                        enum:
                          - blocker
                          - warning
                          - info
                      domain:
                        type: string
                        enum:
                          - people_lifecycle
                          - org_structure
                          - webhooks
                      message:
                        type: string
                      action:
                        type: string
                    required:
                      - code
                      - severity
                      - domain
                      - message
                      - action
                    additionalProperties: false
                metrics:
                  type: object
                  properties:
                    active_units:
                      type: object
                      properties:
                        department:
                          type: integer
                          minimum: 0
                        team:
                          type: integer
                          minimum: 0
                        division:
                          type: integer
                          minimum: 0
                        cost_centre:
                          type: integer
                          minimum: 0
                        legal_entity:
                          type: integer
                          minimum: 0
                        location_group:
                          type: integer
                          minimum: 0
                      required:
                        - department
                        - team
                        - division
                        - cost_centre
                        - legal_entity
                        - location_group
                      additionalProperties: false
                    active_people_missing_primary_department:
                      type: integer
                      minimum: 0
                    active_people_missing_primary_cost_centre:
                      type: integer
                      minimum: 0
                  required:
                    - active_units
                    - active_people_missing_primary_department
                    - active_people_missing_primary_cost_centre
                  additionalProperties: false
              required:
                - status
                - issues
                - metrics
              additionalProperties: false
            webhooks:
              type: object
              properties:
                status:
                  type: string
                  enum:
                    - ready
                    - needs_attention
                    - not_configured
                issues:
                  type: array
                  items:
                    type: object
                    properties:
                      code:
                        type: string
                      severity:
                        type: string
                        enum:
                          - blocker
                          - warning
                          - info
                      domain:
                        type: string
                        enum:
                          - people_lifecycle
                          - org_structure
                          - webhooks
                      message:
                        type: string
                      action:
                        type: string
                    required:
                      - code
                      - severity
                      - domain
                      - message
                      - action
                    additionalProperties: false
                metrics:
                  type: object
                  properties:
                    visible:
                      type: boolean
                    active_endpoints:
                      type: integer
                      minimum: 0
                    endpoints_with_person_ended:
                      type: integer
                      minimum: 0
                    endpoints_with_org_unit_events:
                      type: integer
                      minimum: 0
                    failing_endpoints:
                      type: integer
                      minimum: 0
                    endpoints:
                      type: array
                      items:
                        type: object
                        properties:
                          id:
                            type: string
                            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                          name:
                            type: string
                          status:
                            type: string
                            enum:
                              - active
                              - disabled
                          health_status:
                            type: string
                            enum:
                              - healthy
                              - recovering
                              - failing
                              - inactive
                              - unknown
                          subscribed_events:
                            type: array
                            items:
                              type: string
                              enum:
                                - person.created
                                - person.updated
                                - person.ended
                                - person.reactivated
                                - time_off.created
                                - time_off.updated
                                - time_off.cancelled
                                - time_off_category.created
                                - time_off_category.updated
                                - time_off_category.archived
                                - time_off_settings.updated
                                - org_unit.created
                                - org_unit.updated
                                - org_unit.archived
                                - org_unit.reactivated
                                - working_pattern.created
                                - working_pattern.updated
                                - working_pattern.archived
                                - webhook.test
                          action_required:
                            type: boolean
                          recommended_action:
                            type: string
                            enum:
                              - none
                              - inspect_failures
                              - wait_for_retry
                              - enable_endpoint
                          last_success_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                          last_failure_at:
                            type:
                              - string
                              - "null"
                            format: date-time
                        required:
                          - id
                          - name
                          - status
                          - health_status
                          - subscribed_events
                          - action_required
                          - recommended_action
                          - last_success_at
                          - last_failure_at
                        additionalProperties: false
                  required:
                    - visible
                    - active_endpoints
                    - endpoints_with_person_ended
                    - endpoints_with_org_unit_events
                    - failing_endpoints
                    - endpoints
                  additionalProperties: false
              required:
                - status
                - issues
                - metrics
              additionalProperties: false
          required:
            - people_lifecycle
            - org_structure
            - webhooks
          additionalProperties: false
        providers:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                enum:
                  - xero_payroll_uk
                  - xero_expenses
                  - freeagent
                  - dext
                  - pleo
                  - soldo
              name:
                type: string
              category:
                type: string
                enum:
                  - payroll
                  - spend
                  - accounting
              status:
                type: string
                enum:
                  - ready
                  - needs_attention
                  - not_configured
              native_connector:
                type: boolean
                enum:
                  - false
              summary:
                type: string
              required_domains:
                type: array
                items:
                  type: string
                  enum:
                    - people_lifecycle
                    - org_structure
                    - webhooks
              issues:
                type: array
                items:
                  type: object
                  properties:
                    code:
                      type: string
                    severity:
                      type: string
                      enum:
                        - blocker
                        - warning
                        - info
                    domain:
                      type: string
                      enum:
                        - people_lifecycle
                        - org_structure
                        - webhooks
                    message:
                      type: string
                    action:
                      type: string
                  required:
                    - code
                    - severity
                    - domain
                    - message
                    - action
                  additionalProperties: false
              docs_href:
                type: string
            required:
              - id
              - name
              - category
              - status
              - native_connector
              - summary
              - required_domains
              - issues
              - docs_href
            additionalProperties: false
      required:
        - contract_version
        - generated_at
        - status
        - domains
        - providers
      additionalProperties: false
    IntegrationReadinessResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            contract_version:
              type: string
              enum:
                - 2026-07-07.integration-control-plane.v1
            generated_at:
              type: string
              format: date-time
            status:
              type: string
              enum:
                - ready
                - needs_attention
                - not_configured
            domains:
              type: object
              properties:
                people_lifecycle:
                  type: object
                  properties:
                    status:
                      type: string
                      enum:
                        - ready
                        - needs_attention
                        - not_configured
                    issues:
                      type: array
                      items:
                        type: object
                        properties:
                          code:
                            type: string
                          severity:
                            type: string
                            enum:
                              - blocker
                              - warning
                              - info
                          domain:
                            type: string
                            enum:
                              - people_lifecycle
                              - org_structure
                              - webhooks
                          message:
                            type: string
                          action:
                            type: string
                        required:
                          - code
                          - severity
                          - domain
                          - message
                          - action
                        additionalProperties: false
                    metrics:
                      type: object
                      properties:
                        active_people:
                          type: integer
                          minimum: 0
                        active_missing_start_date:
                          type: integer
                          minimum: 0
                        archived_without_end_date:
                          type: integer
                          minimum: 0
                        active_with_past_end_date:
                          type: integer
                          minimum: 0
                      required:
                        - active_people
                        - active_missing_start_date
                        - archived_without_end_date
                        - active_with_past_end_date
                      additionalProperties: false
                  required:
                    - status
                    - issues
                    - metrics
                  additionalProperties: false
                org_structure:
                  type: object
                  properties:
                    status:
                      type: string
                      enum:
                        - ready
                        - needs_attention
                        - not_configured
                    issues:
                      type: array
                      items:
                        type: object
                        properties:
                          code:
                            type: string
                          severity:
                            type: string
                            enum:
                              - blocker
                              - warning
                              - info
                          domain:
                            type: string
                            enum:
                              - people_lifecycle
                              - org_structure
                              - webhooks
                          message:
                            type: string
                          action:
                            type: string
                        required:
                          - code
                          - severity
                          - domain
                          - message
                          - action
                        additionalProperties: false
                    metrics:
                      type: object
                      properties:
                        active_units:
                          type: object
                          properties:
                            department:
                              type: integer
                              minimum: 0
                            team:
                              type: integer
                              minimum: 0
                            division:
                              type: integer
                              minimum: 0
                            cost_centre:
                              type: integer
                              minimum: 0
                            legal_entity:
                              type: integer
                              minimum: 0
                            location_group:
                              type: integer
                              minimum: 0
                          required:
                            - department
                            - team
                            - division
                            - cost_centre
                            - legal_entity
                            - location_group
                          additionalProperties: false
                        active_people_missing_primary_department:
                          type: integer
                          minimum: 0
                        active_people_missing_primary_cost_centre:
                          type: integer
                          minimum: 0
                      required:
                        - active_units
                        - active_people_missing_primary_department
                        - active_people_missing_primary_cost_centre
                      additionalProperties: false
                  required:
                    - status
                    - issues
                    - metrics
                  additionalProperties: false
                webhooks:
                  type: object
                  properties:
                    status:
                      type: string
                      enum:
                        - ready
                        - needs_attention
                        - not_configured
                    issues:
                      type: array
                      items:
                        type: object
                        properties:
                          code:
                            type: string
                          severity:
                            type: string
                            enum:
                              - blocker
                              - warning
                              - info
                          domain:
                            type: string
                            enum:
                              - people_lifecycle
                              - org_structure
                              - webhooks
                          message:
                            type: string
                          action:
                            type: string
                        required:
                          - code
                          - severity
                          - domain
                          - message
                          - action
                        additionalProperties: false
                    metrics:
                      type: object
                      properties:
                        visible:
                          type: boolean
                        active_endpoints:
                          type: integer
                          minimum: 0
                        endpoints_with_person_ended:
                          type: integer
                          minimum: 0
                        endpoints_with_org_unit_events:
                          type: integer
                          minimum: 0
                        failing_endpoints:
                          type: integer
                          minimum: 0
                        endpoints:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: string
                                pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                              name:
                                type: string
                              status:
                                type: string
                                enum:
                                  - active
                                  - disabled
                              health_status:
                                type: string
                                enum:
                                  - healthy
                                  - recovering
                                  - failing
                                  - inactive
                                  - unknown
                              subscribed_events:
                                type: array
                                items:
                                  type: string
                                  enum:
                                    - person.created
                                    - person.updated
                                    - person.ended
                                    - person.reactivated
                                    - time_off.created
                                    - time_off.updated
                                    - time_off.cancelled
                                    - time_off_category.created
                                    - time_off_category.updated
                                    - time_off_category.archived
                                    - time_off_settings.updated
                                    - org_unit.created
                                    - org_unit.updated
                                    - org_unit.archived
                                    - org_unit.reactivated
                                    - working_pattern.created
                                    - working_pattern.updated
                                    - working_pattern.archived
                                    - webhook.test
                              action_required:
                                type: boolean
                              recommended_action:
                                type: string
                                enum:
                                  - none
                                  - inspect_failures
                                  - wait_for_retry
                                  - enable_endpoint
                              last_success_at:
                                type:
                                  - string
                                  - "null"
                                format: date-time
                              last_failure_at:
                                type:
                                  - string
                                  - "null"
                                format: date-time
                            required:
                              - id
                              - name
                              - status
                              - health_status
                              - subscribed_events
                              - action_required
                              - recommended_action
                              - last_success_at
                              - last_failure_at
                            additionalProperties: false
                      required:
                        - visible
                        - active_endpoints
                        - endpoints_with_person_ended
                        - endpoints_with_org_unit_events
                        - failing_endpoints
                        - endpoints
                      additionalProperties: false
                  required:
                    - status
                    - issues
                    - metrics
                  additionalProperties: false
              required:
                - people_lifecycle
                - org_structure
                - webhooks
              additionalProperties: false
            providers:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    enum:
                      - xero_payroll_uk
                      - xero_expenses
                      - freeagent
                      - dext
                      - pleo
                      - soldo
                  name:
                    type: string
                  category:
                    type: string
                    enum:
                      - payroll
                      - spend
                      - accounting
                  status:
                    type: string
                    enum:
                      - ready
                      - needs_attention
                      - not_configured
                  native_connector:
                    type: boolean
                    enum:
                      - false
                  summary:
                    type: string
                  required_domains:
                    type: array
                    items:
                      type: string
                      enum:
                        - people_lifecycle
                        - org_structure
                        - webhooks
                  issues:
                    type: array
                    items:
                      type: object
                      properties:
                        code:
                          type: string
                        severity:
                          type: string
                          enum:
                            - blocker
                            - warning
                            - info
                        domain:
                          type: string
                          enum:
                            - people_lifecycle
                            - org_structure
                            - webhooks
                        message:
                          type: string
                        action:
                          type: string
                      required:
                        - code
                        - severity
                        - domain
                        - message
                        - action
                      additionalProperties: false
                  docs_href:
                    type: string
                required:
                  - id
                  - name
                  - category
                  - status
                  - native_connector
                  - summary
                  - required_domains
                  - issues
                  - docs_href
                additionalProperties: false
          required:
            - contract_version
            - generated_at
            - status
            - domains
            - providers
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    WorkingPattern:
      type: object
      properties:
        id:
          type: string
          pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type: string
        description:
          type:
            - string
            - "null"
        pattern_type:
          type: string
          enum:
            - fixed_weekly
            - flexible
            - zero_hours
        cycle_length_weeks:
          type: integer
          minimum: 1
          maximum: 12
        weekly_working_minutes:
          type: integer
          minimum: 0
          maximum: 10080
        is_default:
          type: boolean
        is_archived:
          type: boolean
        archived_at:
          type:
            - string
            - "null"
          format: date-time
        current_assignment_count:
          type: integer
          minimum: 0
        days:
          type: array
          items:
            type: object
            properties:
              week_index:
                type: integer
                minimum: 1
                maximum: 12
              day_of_week:
                type: integer
                minimum: 1
                maximum: 7
              start_time:
                type:
                  - string
                  - "null"
                pattern: ^\d{2}:\d{2}$
              end_time:
                type:
                  - string
                  - "null"
                pattern: ^\d{2}:\d{2}$
              unpaid_break_minutes:
                type: integer
                minimum: 0
                maximum: 1440
              working_minutes:
                type: integer
                minimum: 0
                maximum: 1440
            required:
              - week_index
              - day_of_week
              - start_time
              - end_time
              - unpaid_break_minutes
              - working_minutes
            additionalProperties: false
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - description
        - pattern_type
        - cycle_length_weeks
        - weekly_working_minutes
        - is_default
        - is_archived
        - archived_at
        - current_assignment_count
        - days
        - created_at
        - updated_at
      additionalProperties: false
    WorkingPatternList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              description:
                type:
                  - string
                  - "null"
              pattern_type:
                type: string
                enum:
                  - fixed_weekly
                  - flexible
                  - zero_hours
              cycle_length_weeks:
                type: integer
                minimum: 1
                maximum: 12
              weekly_working_minutes:
                type: integer
                minimum: 0
                maximum: 10080
              is_default:
                type: boolean
              is_archived:
                type: boolean
              archived_at:
                type:
                  - string
                  - "null"
                format: date-time
              current_assignment_count:
                type: integer
                minimum: 0
              days:
                type: array
                items:
                  type: object
                  properties:
                    week_index:
                      type: integer
                      minimum: 1
                      maximum: 12
                    day_of_week:
                      type: integer
                      minimum: 1
                      maximum: 7
                    start_time:
                      type:
                        - string
                        - "null"
                      pattern: ^\d{2}:\d{2}$
                    end_time:
                      type:
                        - string
                        - "null"
                      pattern: ^\d{2}:\d{2}$
                    unpaid_break_minutes:
                      type: integer
                      minimum: 0
                      maximum: 1440
                    working_minutes:
                      type: integer
                      minimum: 0
                      maximum: 1440
                  required:
                    - week_index
                    - day_of_week
                    - start_time
                    - end_time
                    - unpaid_break_minutes
                    - working_minutes
                  additionalProperties: false
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - name
              - description
              - pattern_type
              - cycle_length_weeks
              - weekly_working_minutes
              - is_default
              - is_archived
              - archived_at
              - current_assignment_count
              - days
              - created_at
              - updated_at
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    WorkingPatternResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            description:
              type:
                - string
                - "null"
            pattern_type:
              type: string
              enum:
                - fixed_weekly
                - flexible
                - zero_hours
            cycle_length_weeks:
              type: integer
              minimum: 1
              maximum: 12
            weekly_working_minutes:
              type: integer
              minimum: 0
              maximum: 10080
            is_default:
              type: boolean
            is_archived:
              type: boolean
            archived_at:
              type:
                - string
                - "null"
              format: date-time
            current_assignment_count:
              type: integer
              minimum: 0
            days:
              type: array
              items:
                type: object
                properties:
                  week_index:
                    type: integer
                    minimum: 1
                    maximum: 12
                  day_of_week:
                    type: integer
                    minimum: 1
                    maximum: 7
                  start_time:
                    type:
                      - string
                      - "null"
                    pattern: ^\d{2}:\d{2}$
                  end_time:
                    type:
                      - string
                      - "null"
                    pattern: ^\d{2}:\d{2}$
                  unpaid_break_minutes:
                    type: integer
                    minimum: 0
                    maximum: 1440
                  working_minutes:
                    type: integer
                    minimum: 0
                    maximum: 1440
                required:
                  - week_index
                  - day_of_week
                  - start_time
                  - end_time
                  - unpaid_break_minutes
                  - working_minutes
                additionalProperties: false
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - description
            - pattern_type
            - cycle_length_weeks
            - weekly_working_minutes
            - is_default
            - is_archived
            - archived_at
            - current_assignment_count
            - days
            - created_at
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    CreateWorkingPatternRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 128
        description:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 2000
        pattern_type:
          type: string
          enum:
            - fixed_weekly
            - flexible
            - zero_hours
          default: fixed_weekly
        cycle_length_weeks:
          type: integer
          minimum: 1
          maximum: 12
          default: 1
        is_default:
          type: boolean
          default: false
        days:
          type: array
          items:
            type: object
            properties:
              week_index:
                type: integer
                minimum: 1
                maximum: 12
                default: 1
              day_of_week:
                type: integer
                minimum: 1
                maximum: 7
              start_time:
                type:
                  - string
                  - "null"
                pattern: ^\d{2}:\d{2}$
              end_time:
                type:
                  - string
                  - "null"
                pattern: ^\d{2}:\d{2}$
              unpaid_break_minutes:
                type: integer
                minimum: 0
                maximum: 1440
              working_minutes:
                type: integer
                minimum: 0
                maximum: 1440
            required:
              - day_of_week
              - start_time
              - end_time
              - unpaid_break_minutes
              - working_minutes
            additionalProperties: false
      required:
        - name
        - days
      additionalProperties: false
    UpdateWorkingPatternRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 128
        description:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 2000
        pattern_type:
          type: string
          enum:
            - fixed_weekly
            - flexible
            - zero_hours
          default: fixed_weekly
        cycle_length_weeks:
          type: integer
          minimum: 1
          maximum: 12
          default: 1
        is_default:
          type: boolean
          default: false
        days:
          type: array
          items:
            type: object
            properties:
              week_index:
                type: integer
                minimum: 1
                maximum: 12
                default: 1
              day_of_week:
                type: integer
                minimum: 1
                maximum: 7
              start_time:
                type:
                  - string
                  - "null"
                pattern: ^\d{2}:\d{2}$
              end_time:
                type:
                  - string
                  - "null"
                pattern: ^\d{2}:\d{2}$
              unpaid_break_minutes:
                type: integer
                minimum: 0
                maximum: 1440
              working_minutes:
                type: integer
                minimum: 0
                maximum: 1440
            required:
              - day_of_week
              - start_time
              - end_time
              - unpaid_break_minutes
              - working_minutes
            additionalProperties: false
      required:
        - name
        - days
      additionalProperties: false
    ArchiveWorkingPatternResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            description:
              type:
                - string
                - "null"
            pattern_type:
              type: string
              enum:
                - fixed_weekly
                - flexible
                - zero_hours
            cycle_length_weeks:
              type: integer
              minimum: 1
              maximum: 12
            weekly_working_minutes:
              type: integer
              minimum: 0
              maximum: 10080
            is_default:
              type: boolean
            is_archived:
              type: boolean
            archived_at:
              type:
                - string
                - "null"
              format: date-time
            current_assignment_count:
              type: integer
              minimum: 0
            days:
              type: array
              items:
                type: object
                properties:
                  week_index:
                    type: integer
                    minimum: 1
                    maximum: 12
                  day_of_week:
                    type: integer
                    minimum: 1
                    maximum: 7
                  start_time:
                    type:
                      - string
                      - "null"
                    pattern: ^\d{2}:\d{2}$
                  end_time:
                    type:
                      - string
                      - "null"
                    pattern: ^\d{2}:\d{2}$
                  unpaid_break_minutes:
                    type: integer
                    minimum: 0
                    maximum: 1440
                  working_minutes:
                    type: integer
                    minimum: 0
                    maximum: 1440
                required:
                  - week_index
                  - day_of_week
                  - start_time
                  - end_time
                  - unpaid_break_minutes
                  - working_minutes
                additionalProperties: false
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - description
            - pattern_type
            - cycle_length_weeks
            - weekly_working_minutes
            - is_default
            - is_archived
            - archived_at
            - current_assignment_count
            - days
            - created_at
            - updated_at
          additionalProperties: false
        archived:
          type: boolean
          enum:
            - true
      required:
        - data
        - archived
      additionalProperties: false
    TimeOff:
      type: object
      properties:
        id:
          type: string
          pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        category_id:
          type: string
          pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        type:
          type: string
        start_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        length:
          type: number
        duration_unit:
          type: string
          enum:
            - days
            - hours
        duration_minutes:
          type: integer
          minimum: 0
        balance_unit:
          type: string
          enum:
            - days
            - hours
        balance_amount:
          type: number
          minimum: 0
        public_holiday_treatment:
          type: string
          enum:
            - exclude
            - include
            - ignore
        is_from_half_day:
          type: boolean
        is_to_half_day:
          type: boolean
        status:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - person_id
        - category_id
        - type
        - start_date
        - end_date
        - length
        - duration_unit
        - duration_minutes
        - balance_unit
        - balance_amount
        - public_holiday_treatment
        - is_from_half_day
        - is_to_half_day
        - status
        - created_at
        - updated_at
      additionalProperties: false
    TimeOffBalance:
      type: object
      properties:
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        leave_year:
          type: object
          properties:
            starts_on:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            ends_on:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            resets_on:
              type: string
          required:
            - starts_on
            - ends_on
            - resets_on
          additionalProperties: false
        annual_leave:
          type: object
          properties:
            base_allowance_days:
              type: number
            allowance_days:
              type: number
            ledger_adjustment_days:
              type: number
            taken_days:
              type: number
            pending_days:
              type: number
            remaining_days:
              type: number
            resets_on:
              type: string
          required:
            - base_allowance_days
            - allowance_days
            - ledger_adjustment_days
            - taken_days
            - pending_days
            - remaining_days
            - resets_on
          additionalProperties: false
        sick_days_taken:
          type: number
        other_leave_taken:
          type: number
        totals:
          type: object
          properties:
            unit:
              type: string
              enum:
                - days
                - hours
            base_allowance:
              type: number
            allowance:
              type: number
            ledger_adjustment:
              type: number
            approved:
              type: number
            pending:
              type: number
            remaining:
              type: number
          required:
            - unit
            - base_allowance
            - allowance
            - ledger_adjustment
            - approved
            - pending
            - remaining
          additionalProperties: false
        categories:
          type: array
          items:
            type: object
            properties:
              category_id:
                type: string
                pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              unit:
                type: string
                enum:
                  - days
                  - hours
              base_allowance:
                type: number
              allowance:
                type: number
              ledger:
                type: object
                properties:
                  allowance_grants:
                    type: number
                  carry_over:
                    type: number
                  manual_adjustments:
                    type: number
                  corrections:
                    type: number
                  pro_rata:
                    type: number
                  expired:
                    type: number
                  net:
                    type: number
                required:
                  - allowance_grants
                  - carry_over
                  - manual_adjustments
                  - corrections
                  - pro_rata
                  - expired
                  - net
                additionalProperties: false
              ledger_entries:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      pattern: ^lbl_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                    entry_type:
                      type: string
                      enum:
                        - allowance_grant
                        - carry_over
                        - manual_adjustment
                        - correction
                        - pro_rata
                        - expiry_lapse
                    unit:
                      type: string
                      enum:
                        - days
                        - hours
                    amount:
                      type: number
                    effective_date:
                      type: string
                      pattern: ^\d{4}-\d{2}-\d{2}$
                    source:
                      type: string
                  required:
                    - id
                    - entry_type
                    - unit
                    - amount
                    - effective_date
                    - source
                  additionalProperties: false
              approved:
                type: number
              pending:
                type: number
              remaining:
                type: number
              consumes_balance:
                type: boolean
              allow_negative_balance:
                type: boolean
            required:
              - category_id
              - name
              - unit
              - base_allowance
              - allowance
              - ledger
              - ledger_entries
              - approved
              - pending
              - remaining
              - consumes_balance
              - allow_negative_balance
            additionalProperties: false
        calculation_basis:
          type: object
          properties:
            leave_year_start:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            leave_year_end:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            source:
              type: string
              enum:
                - stored_duration_metadata_and_balance_ledger
            policy:
              type:
                - object
                - "null"
              properties:
                name:
                  type: string
                assignment_source:
                  type: string
                  enum:
                    - organisation_default
                    - department
                    - person
                effective_from:
                  type:
                    - string
                    - "null"
                  pattern: ^\d{4}-\d{2}-\d{2}$
                effective_to:
                  type:
                    - string
                    - "null"
                  pattern: ^\d{4}-\d{2}-\d{2}$
              required:
                - name
                - assignment_source
                - effective_from
                - effective_to
              additionalProperties: false
          required:
            - leave_year_start
            - leave_year_end
            - source
            - policy
          additionalProperties: false
        calculated_at:
          type: string
          format: date-time
      required:
        - person_id
        - leave_year
        - annual_leave
        - sick_days_taken
        - other_leave_taken
        - totals
        - categories
        - calculation_basis
        - calculated_at
      additionalProperties: false
    TimeOffBalanceResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            leave_year:
              type: object
              properties:
                starts_on:
                  type: string
                  pattern: ^\d{4}-\d{2}-\d{2}$
                ends_on:
                  type: string
                  pattern: ^\d{4}-\d{2}-\d{2}$
                resets_on:
                  type: string
              required:
                - starts_on
                - ends_on
                - resets_on
              additionalProperties: false
            annual_leave:
              type: object
              properties:
                base_allowance_days:
                  type: number
                allowance_days:
                  type: number
                ledger_adjustment_days:
                  type: number
                taken_days:
                  type: number
                pending_days:
                  type: number
                remaining_days:
                  type: number
                resets_on:
                  type: string
              required:
                - base_allowance_days
                - allowance_days
                - ledger_adjustment_days
                - taken_days
                - pending_days
                - remaining_days
                - resets_on
              additionalProperties: false
            sick_days_taken:
              type: number
            other_leave_taken:
              type: number
            totals:
              type: object
              properties:
                unit:
                  type: string
                  enum:
                    - days
                    - hours
                base_allowance:
                  type: number
                allowance:
                  type: number
                ledger_adjustment:
                  type: number
                approved:
                  type: number
                pending:
                  type: number
                remaining:
                  type: number
              required:
                - unit
                - base_allowance
                - allowance
                - ledger_adjustment
                - approved
                - pending
                - remaining
              additionalProperties: false
            categories:
              type: array
              items:
                type: object
                properties:
                  category_id:
                    type: string
                    pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  name:
                    type: string
                  unit:
                    type: string
                    enum:
                      - days
                      - hours
                  base_allowance:
                    type: number
                  allowance:
                    type: number
                  ledger:
                    type: object
                    properties:
                      allowance_grants:
                        type: number
                      carry_over:
                        type: number
                      manual_adjustments:
                        type: number
                      corrections:
                        type: number
                      pro_rata:
                        type: number
                      expired:
                        type: number
                      net:
                        type: number
                    required:
                      - allowance_grants
                      - carry_over
                      - manual_adjustments
                      - corrections
                      - pro_rata
                      - expired
                      - net
                    additionalProperties: false
                  ledger_entries:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          pattern: ^lbl_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                        entry_type:
                          type: string
                          enum:
                            - allowance_grant
                            - carry_over
                            - manual_adjustment
                            - correction
                            - pro_rata
                            - expiry_lapse
                        unit:
                          type: string
                          enum:
                            - days
                            - hours
                        amount:
                          type: number
                        effective_date:
                          type: string
                          pattern: ^\d{4}-\d{2}-\d{2}$
                        source:
                          type: string
                      required:
                        - id
                        - entry_type
                        - unit
                        - amount
                        - effective_date
                        - source
                      additionalProperties: false
                  approved:
                    type: number
                  pending:
                    type: number
                  remaining:
                    type: number
                  consumes_balance:
                    type: boolean
                  allow_negative_balance:
                    type: boolean
                required:
                  - category_id
                  - name
                  - unit
                  - base_allowance
                  - allowance
                  - ledger
                  - ledger_entries
                  - approved
                  - pending
                  - remaining
                  - consumes_balance
                  - allow_negative_balance
                additionalProperties: false
            calculation_basis:
              type: object
              properties:
                leave_year_start:
                  type: string
                  pattern: ^\d{4}-\d{2}-\d{2}$
                leave_year_end:
                  type: string
                  pattern: ^\d{4}-\d{2}-\d{2}$
                source:
                  type: string
                  enum:
                    - stored_duration_metadata_and_balance_ledger
                policy:
                  type:
                    - object
                    - "null"
                  properties:
                    name:
                      type: string
                    assignment_source:
                      type: string
                      enum:
                        - organisation_default
                        - department
                        - person
                    effective_from:
                      type:
                        - string
                        - "null"
                      pattern: ^\d{4}-\d{2}-\d{2}$
                    effective_to:
                      type:
                        - string
                        - "null"
                      pattern: ^\d{4}-\d{2}-\d{2}$
                  required:
                    - name
                    - assignment_source
                    - effective_from
                    - effective_to
                  additionalProperties: false
              required:
                - leave_year_start
                - leave_year_end
                - source
                - policy
              additionalProperties: false
            calculated_at:
              type: string
              format: date-time
          required:
            - person_id
            - leave_year
            - annual_leave
            - sick_days_taken
            - other_leave_taken
            - totals
            - categories
            - calculation_basis
            - calculated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    LeaveBalanceLedgerList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^lbl_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              category_id:
                type: string
                pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              category_name:
                type: string
              entry_type:
                type: string
                enum:
                  - allowance_grant
                  - carry_over
                  - manual_adjustment
                  - correction
                  - pro_rata
                  - expiry_lapse
              unit:
                type: string
                enum:
                  - days
                  - hours
              amount:
                type: number
              effective_date:
                type: string
                pattern: ^\d{4}-\d{2}-\d{2}$
              leave_year:
                type: object
                properties:
                  starts_on:
                    type: string
                    pattern: ^\d{4}-\d{2}-\d{2}$
                  ends_on:
                    type: string
                    pattern: ^\d{4}-\d{2}-\d{2}$
                required:
                  - starts_on
                  - ends_on
                additionalProperties: false
              source:
                type: string
              created_at:
                type: string
                format: date-time
            required:
              - id
              - category_id
              - category_name
              - entry_type
              - unit
              - amount
              - effective_date
              - leave_year
              - source
              - created_at
            additionalProperties: false
        pagination:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
            has_more:
              type: boolean
            limit:
              type: integer
              minimum: 1
              maximum: 100
          required:
            - next_cursor
            - has_more
            - limit
          additionalProperties: false
      required:
        - data
        - pagination
      additionalProperties: false
    LeaveBalanceLedgerQuery:
      type: object
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 25
        cursor:
          type: string
          minLength: 1
        from:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        to:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
      additionalProperties: false
      description: Query parameters for a person's leave-balance ledger history.
    CreateLeaveBalanceAdjustmentRequest:
      type: object
      properties:
        category_id:
          type: string
          pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        entry_type:
          type: string
          enum:
            - manual_adjustment
            - correction
            - pro_rata
            - expiry_lapse
        unit:
          type: string
          enum:
            - days
            - hours
        amount:
          type: number
        effective_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        reason:
          type: string
          minLength: 3
          maxLength: 512
      required:
        - category_id
        - entry_type
        - unit
        - amount
        - effective_date
        - reason
      additionalProperties: false
    LeaveBalanceAdjustmentResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^lbl_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            entry_type:
              type: string
              enum:
                - allowance_grant
                - carry_over
                - manual_adjustment
                - correction
                - pro_rata
                - expiry_lapse
            unit:
              type: string
              enum:
                - days
                - hours
            amount:
              type: number
            effective_date:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            source:
              type: string
          required:
            - id
            - entry_type
            - unit
            - amount
            - effective_date
            - source
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    PublicHoliday:
      type: object
      properties:
        id:
          type: string
          pattern: ^hol_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type: string
        date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        type:
          type: string
        description:
          type:
            - string
            - "null"
      required:
        - id
        - name
        - date
        - type
        - description
      additionalProperties: false
    Document:
      type: object
      properties:
        id:
          type: string
          pattern: ^doc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type: string
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        category:
          type:
            - string
            - "null"
        content_type:
          type:
            - string
            - "null"
        size_bytes:
          type:
            - integer
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - person_id
        - category
        - content_type
        - size_bytes
        - created_at
        - updated_at
      additionalProperties: false
    PayrollReadinessExport:
      type: object
      properties:
        format:
          type: string
          enum:
            - hollyhr-payroll-readiness-export
        version:
          type: number
          enum:
            - 1
        provider:
          type:
            - string
            - "null"
          enum:
            - xero_payroll_uk
            - null
        generated_at:
          type: string
          format: date-time
        files:
          type: array
          items:
            type: object
            properties:
              filename:
                type: string
                minLength: 1
              content:
                type: string
            required:
              - filename
              - content
            additionalProperties: false
          minItems: 1
      required:
        - format
        - version
        - provider
        - generated_at
        - files
      additionalProperties: false
    Country:
      type: object
      properties:
        id:
          type: string
          pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type: string
        country_code:
          type: string
        phone_code:
          type: string
        currency_code:
          type: string
        currency:
          type: string
      required:
        - id
        - name
        - country_code
        - phone_code
        - currency_code
        - currency
      additionalProperties: false
    ProfileStatus:
      type: object
      properties:
        id:
          type: string
          pattern: ^sts_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        label:
          type:
            - string
            - "null"
      required:
        - id
        - label
      additionalProperties: false
    WorkType:
      type: object
      properties:
        id:
          type: string
          pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        label:
          type: string
      required:
        - id
        - label
      additionalProperties: false
    WorkPlace:
      type: object
      properties:
        id:
          type: string
          pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        label:
          type: string
      required:
        - id
        - label
      additionalProperties: false
    TimeOffStatus:
      type: object
      properties:
        id:
          type: string
          pattern: ^tos_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        label:
          type:
            - string
            - "null"
      required:
        - id
        - label
      additionalProperties: false
    TimeOffCategory:
      type: object
      properties:
        id:
          type: string
          pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type: string
        allowance_days:
          type: integer
        is_discretionary:
          type: boolean
        carry_over:
          type:
            - string
            - "null"
        is_active:
          type: boolean
        order:
          type:
            - integer
            - "null"
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - allowance_days
        - is_discretionary
        - carry_over
        - is_active
        - order
        - updated_at
      additionalProperties: false
    TimeOffCategoryResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            allowance_days:
              type: integer
            is_discretionary:
              type: boolean
            carry_over:
              type:
                - string
                - "null"
            is_active:
              type: boolean
            order:
              type:
                - integer
                - "null"
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - allowance_days
            - is_discretionary
            - carry_over
            - is_active
            - order
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    CreateTimeOffCategoryRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 64
        allowance_days:
          type: integer
          minimum: 0
          maximum: 366
          default: 0
        is_discretionary:
          type: boolean
          default: false
        carry_over:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 32
        is_active:
          type: boolean
          default: true
        order:
          type: integer
          minimum: 0
          maximum: 9999
      required:
        - name
      additionalProperties: false
    UpdateTimeOffCategoryRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 64
        allowance_days:
          type: integer
          minimum: 0
          maximum: 366
        is_discretionary:
          type: boolean
        carry_over:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 32
        is_active:
          type: boolean
        order:
          type: integer
          minimum: 0
          maximum: 9999
      additionalProperties: false
      description: At least one supported time-off category field must be provided.
      minProperties: 1
    ArchiveTimeOffCategoryResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            allowance_days:
              type: integer
            is_discretionary:
              type: boolean
            carry_over:
              type:
                - string
                - "null"
            is_active:
              type: boolean
            order:
              type:
                - integer
                - "null"
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - allowance_days
            - is_discretionary
            - carry_over
            - is_active
            - order
            - updated_at
          additionalProperties: false
        archived:
          type: boolean
          enum:
            - true
      required:
        - data
        - archived
      additionalProperties: false
    TimeOffSettings:
      type: object
      properties:
        reset_month:
          type: integer
          minimum: 0
          maximum: 11
        reset_day:
          type: integer
          minimum: 1
          maximum: 31
        show_national_holidays:
          type: boolean
        use_individual_country_holidays:
          type: boolean
        country_id:
          type:
            - string
            - "null"
          pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        updated_at:
          type: string
          format: date-time
      required:
        - reset_month
        - reset_day
        - show_national_holidays
        - use_individual_country_holidays
        - country_id
        - updated_at
      additionalProperties: false
    TimeOffSettingsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            reset_month:
              type: integer
              minimum: 0
              maximum: 11
            reset_day:
              type: integer
              minimum: 1
              maximum: 31
            show_national_holidays:
              type: boolean
            use_individual_country_holidays:
              type: boolean
            country_id:
              type:
                - string
                - "null"
              pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            updated_at:
              type: string
              format: date-time
          required:
            - reset_month
            - reset_day
            - show_national_holidays
            - use_individual_country_holidays
            - country_id
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    UpdateTimeOffSettingsRequest:
      type: object
      properties:
        reset_month:
          type: integer
          minimum: 0
          maximum: 11
        reset_day:
          type: integer
          minimum: 1
          maximum: 31
        show_national_holidays:
          type: boolean
        use_individual_country_holidays:
          type: boolean
        country_id:
          type:
            - string
            - "null"
          pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
      additionalProperties: false
      description: At least one supported safe time-off settings field must be provided.
      minProperties: 1
    DocumentType:
      type: object
      properties:
        id:
          type: string
          pattern: ^dtyp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type: string
        description:
          type:
            - string
            - "null"
      required:
        - id
        - name
        - description
      additionalProperties: false
    PersonPage:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
              work_email:
                type: string
                format: email
              first_name:
                type:
                  - string
                  - "null"
              preferred_name:
                type:
                  - string
                  - "null"
              last_name:
                type:
                  - string
                  - "null"
              display_name:
                type: string
              staff_id:
                type:
                  - string
                  - "null"
              job_title:
                type:
                  - string
                  - "null"
              department:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  name:
                    type: string
                required:
                  - id
                  - name
                additionalProperties: false
              manager:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                  display_name:
                    type: string
                required:
                  - id
                  - display_name
                additionalProperties: false
              work_place:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  label:
                    type: string
                required:
                  - id
                  - label
                additionalProperties: false
              work_type:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  label:
                    type: string
                required:
                  - id
                  - label
                additionalProperties: false
              working_pattern:
                type:
                  - object
                  - "null"
                properties:
                  id:
                    type: string
                    pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                  label:
                    type: string
                required:
                  - id
                  - label
                additionalProperties: false
              contracted_weekly_minutes:
                type:
                  - integer
                  - "null"
                minimum: 0
                maximum: 10080
              full_time_weekly_minutes:
                type:
                  - integer
                  - "null"
                minimum: 1
                maximum: 10080
              fte:
                type:
                  - number
                  - "null"
              status:
                type:
                  - string
                  - "null"
              start_date:
                type:
                  - string
                  - "null"
                pattern: ^\d{4}-\d{2}-\d{2}$
              end_date:
                type:
                  - string
                  - "null"
                pattern: ^\d{4}-\d{2}-\d{2}$
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - work_email
              - first_name
              - preferred_name
              - last_name
              - display_name
              - staff_id
              - job_title
              - department
              - manager
              - work_place
              - work_type
              - working_pattern
              - contracted_weekly_minutes
              - full_time_weekly_minutes
              - fte
              - status
              - start_date
              - end_date
              - created_at
              - updated_at
            additionalProperties: false
        pagination:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
            has_more:
              type: boolean
            limit:
              type: integer
              minimum: 1
              maximum: 100
          required:
            - next_cursor
            - has_more
            - limit
          additionalProperties: false
      required:
        - data
        - pagination
      additionalProperties: false
    PersonResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            work_email:
              type: string
              format: email
            first_name:
              type:
                - string
                - "null"
            preferred_name:
              type:
                - string
                - "null"
            last_name:
              type:
                - string
                - "null"
            display_name:
              type: string
            staff_id:
              type:
                - string
                - "null"
            job_title:
              type:
                - string
                - "null"
            department:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            manager:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
                display_name:
                  type: string
              required:
                - id
                - display_name
              additionalProperties: false
            work_place:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            work_type:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            working_pattern:
              type:
                - object
                - "null"
              properties:
                id:
                  type: string
                  pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
                label:
                  type: string
              required:
                - id
                - label
              additionalProperties: false
            contracted_weekly_minutes:
              type:
                - integer
                - "null"
              minimum: 0
              maximum: 10080
            full_time_weekly_minutes:
              type:
                - integer
                - "null"
              minimum: 1
              maximum: 10080
            fte:
              type:
                - number
                - "null"
            status:
              type:
                - string
                - "null"
            start_date:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            end_date:
              type:
                - string
                - "null"
              pattern: ^\d{4}-\d{2}-\d{2}$
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - work_email
            - first_name
            - preferred_name
            - last_name
            - display_name
            - staff_id
            - job_title
            - department
            - manager
            - work_place
            - work_type
            - working_pattern
            - contracted_weekly_minutes
            - full_time_weekly_minutes
            - fte
            - status
            - start_date
            - end_date
            - created_at
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    TimeOffPage:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              person_id:
                type: string
                pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
              category_id:
                type: string
                pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              type:
                type: string
              start_date:
                type: string
                pattern: ^\d{4}-\d{2}-\d{2}$
              end_date:
                type: string
                pattern: ^\d{4}-\d{2}-\d{2}$
              length:
                type: number
              duration_unit:
                type: string
                enum:
                  - days
                  - hours
              duration_minutes:
                type: integer
                minimum: 0
              balance_unit:
                type: string
                enum:
                  - days
                  - hours
              balance_amount:
                type: number
                minimum: 0
              public_holiday_treatment:
                type: string
                enum:
                  - exclude
                  - include
                  - ignore
              is_from_half_day:
                type: boolean
              is_to_half_day:
                type: boolean
              status:
                type:
                  - string
                  - "null"
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - person_id
              - category_id
              - type
              - start_date
              - end_date
              - length
              - duration_unit
              - duration_minutes
              - balance_unit
              - balance_amount
              - public_holiday_treatment
              - is_from_half_day
              - is_to_half_day
              - status
              - created_at
              - updated_at
            additionalProperties: false
        pagination:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
            has_more:
              type: boolean
            limit:
              type: integer
              minimum: 1
              maximum: 100
          required:
            - next_cursor
            - has_more
            - limit
          additionalProperties: false
      required:
        - data
        - pagination
      additionalProperties: false
    TimeOffResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            category_id:
              type: string
              pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            type:
              type: string
            start_date:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            end_date:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            length:
              type: number
            duration_unit:
              type: string
              enum:
                - days
                - hours
            duration_minutes:
              type: integer
              minimum: 0
            balance_unit:
              type: string
              enum:
                - days
                - hours
            balance_amount:
              type: number
              minimum: 0
            public_holiday_treatment:
              type: string
              enum:
                - exclude
                - include
                - ignore
            is_from_half_day:
              type: boolean
            is_to_half_day:
              type: boolean
            status:
              type:
                - string
                - "null"
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - person_id
            - category_id
            - type
            - start_date
            - end_date
            - length
            - duration_unit
            - duration_minutes
            - balance_unit
            - balance_amount
            - public_holiday_treatment
            - is_from_half_day
            - is_to_half_day
            - status
            - created_at
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    TimeOffWhoIsOutQuery:
      type: object
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 25
        cursor:
          type: string
          minLength: 1
        from:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        to:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
      required:
        - from
        - to
      additionalProperties: false
      description:
        Required date window for approved time-off records overlapping the requested calendar
        range. The window must not exceed 92 days.
    CreateTimeOffRequest:
      type: object
      properties:
        person_id:
          type: string
          pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
        category_id:
          type: string
          pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        start_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        is_from_half_day:
          type: boolean
          default: false
        is_to_half_day:
          type: boolean
          default: false
      required:
        - person_id
        - category_id
        - start_date
        - end_date
      additionalProperties: false
    UpdateTimeOffRequest:
      type: object
      properties:
        category_id:
          type: string
          pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        start_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        end_date:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2}$
        is_from_half_day:
          type: boolean
        is_to_half_day:
          type: boolean
      additionalProperties: false
      description: At least one supported safe time-off field must be provided.
      minProperties: 1
    CancelTimeOffRequest:
      type: object
      properties: {}
      additionalProperties: false
      description: No request fields are accepted for cancellation.
    CancelTimeOffResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            category_id:
              type: string
              pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            type:
              type: string
            start_date:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            end_date:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
            length:
              type: number
            duration_unit:
              type: string
              enum:
                - days
                - hours
            duration_minutes:
              type: integer
              minimum: 0
            balance_unit:
              type: string
              enum:
                - days
                - hours
            balance_amount:
              type: number
              minimum: 0
            public_holiday_treatment:
              type: string
              enum:
                - exclude
                - include
                - ignore
            is_from_half_day:
              type: boolean
            is_to_half_day:
              type: boolean
            status:
              type:
                - string
                - "null"
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - person_id
            - category_id
            - type
            - start_date
            - end_date
            - length
            - duration_unit
            - duration_minutes
            - balance_unit
            - balance_amount
            - public_holiday_treatment
            - is_from_half_day
            - is_to_half_day
            - status
            - created_at
            - updated_at
          additionalProperties: false
        cancelled:
          type: boolean
          enum:
            - true
      required:
        - data
        - cancelled
      additionalProperties: false
    PublicHolidayList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^hol_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              date:
                type: string
                pattern: ^\d{4}-\d{2}-\d{2}$
              type:
                type: string
              description:
                type:
                  - string
                  - "null"
            required:
              - id
              - name
              - date
              - type
              - description
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    DocumentPage:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^doc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              person_id:
                type: string
                pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
              category:
                type:
                  - string
                  - "null"
              content_type:
                type:
                  - string
                  - "null"
              size_bytes:
                type:
                  - integer
                  - "null"
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - name
              - person_id
              - category
              - content_type
              - size_bytes
              - created_at
              - updated_at
            additionalProperties: false
        pagination:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
            has_more:
              type: boolean
            limit:
              type: integer
              minimum: 1
              maximum: 100
          required:
            - next_cursor
            - has_more
            - limit
          additionalProperties: false
      required:
        - data
        - pagination
      additionalProperties: false
    DocumentResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^doc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            person_id:
              type: string
              pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            category:
              type:
                - string
                - "null"
            content_type:
              type:
                - string
                - "null"
            size_bytes:
              type:
                - integer
                - "null"
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - person_id
            - category
            - content_type
            - size_bytes
            - created_at
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    PayrollReadinessExportResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            format:
              type: string
              enum:
                - hollyhr-payroll-readiness-export
            version:
              type: number
              enum:
                - 1
            provider:
              type:
                - string
                - "null"
              enum:
                - xero_payroll_uk
                - null
            generated_at:
              type: string
              format: date-time
            files:
              type: array
              items:
                type: object
                properties:
                  filename:
                    type: string
                    minLength: 1
                  content:
                    type: string
                required:
                  - filename
                  - content
                additionalProperties: false
              minItems: 1
          required:
            - format
            - version
            - provider
            - generated_at
            - files
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    PayrollChangesExportResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            format:
              type: string
              enum:
                - hollyhr-payroll-changes-export
            version:
              type: number
              enum:
                - 1
            period:
              type: object
              properties:
                fromDate:
                  type: string
                  pattern: ^\d{4}-\d{2}-\d{2}$
                toDate:
                  type: string
                  pattern: ^\d{4}-\d{2}-\d{2}$
              required:
                - fromDate
                - toDate
              additionalProperties: false
            generated_at:
              type: string
              format: date-time
            files:
              type: array
              items:
                type: object
                properties:
                  filename:
                    type: string
                    minLength: 1
                  content:
                    type: string
                required:
                  - filename
                  - content
                additionalProperties: false
              minItems: 1
          required:
            - format
            - version
            - period
            - generated_at
            - files
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    CountryList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^cty_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              country_code:
                type: string
              phone_code:
                type: string
              currency_code:
                type: string
              currency:
                type: string
            required:
              - id
              - name
              - country_code
              - phone_code
              - currency_code
              - currency
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    ProfileStatusList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^sts_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              label:
                type:
                  - string
                  - "null"
            required:
              - id
              - label
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    WorkTypeList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^wt_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              label:
                type: string
            required:
              - id
              - label
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    WorkPlaceList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^wp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              label:
                type: string
            required:
              - id
              - label
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    TimeOffStatusList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^tos_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              label:
                type:
                  - string
                  - "null"
            required:
              - id
              - label
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    TimeOffCategoryList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              allowance_days:
                type: integer
              is_discretionary:
                type: boolean
              carry_over:
                type:
                  - string
                  - "null"
              is_active:
                type: boolean
              order:
                type:
                  - integer
                  - "null"
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - name
              - allowance_days
              - is_discretionary
              - carry_over
              - is_active
              - order
              - updated_at
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    DocumentTypeList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^dtyp_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              description:
                type:
                  - string
                  - "null"
            required:
              - id
              - name
              - description
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    Webhook:
      type: object
      properties:
        id:
          type: string
          pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        name:
          type: string
        url:
          type: string
          format: uri
        status:
          type: string
          enum:
            - active
            - disabled
        subscribed_events:
          type: array
          items:
            type: string
            enum:
              - person.created
              - person.updated
              - person.ended
              - person.reactivated
              - time_off.created
              - time_off.updated
              - time_off.cancelled
              - time_off_category.created
              - time_off_category.updated
              - time_off_category.archived
              - time_off_settings.updated
              - org_unit.created
              - org_unit.updated
              - org_unit.archived
              - org_unit.reactivated
              - working_pattern.created
              - working_pattern.updated
              - working_pattern.archived
              - webhook.test
        secret_last_four:
          type: string
        previous_secret_expires_at:
          type:
            - string
            - "null"
          format: date-time
        last_tested_at:
          type:
            - string
            - "null"
          format: date-time
        disabled_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - url
        - status
        - subscribed_events
        - secret_last_four
        - previous_secret_expires_at
        - last_tested_at
        - disabled_at
        - created_at
        - updated_at
      additionalProperties: false
    WebhookList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              name:
                type: string
              url:
                type: string
                format: uri
              status:
                type: string
                enum:
                  - active
                  - disabled
              subscribed_events:
                type: array
                items:
                  type: string
                  enum:
                    - person.created
                    - person.updated
                    - person.ended
                    - person.reactivated
                    - time_off.created
                    - time_off.updated
                    - time_off.cancelled
                    - time_off_category.created
                    - time_off_category.updated
                    - time_off_category.archived
                    - time_off_settings.updated
                    - org_unit.created
                    - org_unit.updated
                    - org_unit.archived
                    - org_unit.reactivated
                    - working_pattern.created
                    - working_pattern.updated
                    - working_pattern.archived
                    - webhook.test
              secret_last_four:
                type: string
              previous_secret_expires_at:
                type:
                  - string
                  - "null"
                format: date-time
              last_tested_at:
                type:
                  - string
                  - "null"
                format: date-time
              disabled_at:
                type:
                  - string
                  - "null"
                format: date-time
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - name
              - url
              - status
              - subscribed_events
              - secret_last_four
              - previous_secret_expires_at
              - last_tested_at
              - disabled_at
              - created_at
              - updated_at
            additionalProperties: false
      required:
        - data
      additionalProperties: false
    WebhookResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            url:
              type: string
              format: uri
            status:
              type: string
              enum:
                - active
                - disabled
            subscribed_events:
              type: array
              items:
                type: string
                enum:
                  - person.created
                  - person.updated
                  - person.ended
                  - person.reactivated
                  - time_off.created
                  - time_off.updated
                  - time_off.cancelled
                  - time_off_category.created
                  - time_off_category.updated
                  - time_off_category.archived
                  - time_off_settings.updated
                  - org_unit.created
                  - org_unit.updated
                  - org_unit.archived
                  - org_unit.reactivated
                  - working_pattern.created
                  - working_pattern.updated
                  - working_pattern.archived
                  - webhook.test
            secret_last_four:
              type: string
            previous_secret_expires_at:
              type:
                - string
                - "null"
              format: date-time
            last_tested_at:
              type:
                - string
                - "null"
              format: date-time
            disabled_at:
              type:
                - string
                - "null"
              format: date-time
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - url
            - status
            - subscribed_events
            - secret_last_four
            - previous_secret_expires_at
            - last_tested_at
            - disabled_at
            - created_at
            - updated_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    WebhookSecretResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            url:
              type: string
              format: uri
            status:
              type: string
              enum:
                - active
                - disabled
            subscribed_events:
              type: array
              items:
                type: string
                enum:
                  - person.created
                  - person.updated
                  - person.ended
                  - person.reactivated
                  - time_off.created
                  - time_off.updated
                  - time_off.cancelled
                  - time_off_category.created
                  - time_off_category.updated
                  - time_off_category.archived
                  - time_off_settings.updated
                  - org_unit.created
                  - org_unit.updated
                  - org_unit.archived
                  - org_unit.reactivated
                  - working_pattern.created
                  - working_pattern.updated
                  - working_pattern.archived
                  - webhook.test
            secret_last_four:
              type: string
            previous_secret_expires_at:
              type:
                - string
                - "null"
              format: date-time
            last_tested_at:
              type:
                - string
                - "null"
              format: date-time
            disabled_at:
              type:
                - string
                - "null"
              format: date-time
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
            signing_secret:
              type:
                - string
                - "null"
          required:
            - id
            - name
            - url
            - status
            - subscribed_events
            - secret_last_four
            - previous_secret_expires_at
            - last_tested_at
            - disabled_at
            - created_at
            - updated_at
            - signing_secret
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    CreateWebhookRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        url:
          type: string
          maxLength: 2048
          format: uri
        subscribed_events:
          type: array
          items:
            type: string
            enum:
              - person.created
              - person.updated
              - person.ended
              - person.reactivated
              - time_off.created
              - time_off.updated
              - time_off.cancelled
              - time_off_category.created
              - time_off_category.updated
              - time_off_category.archived
              - time_off_settings.updated
              - org_unit.created
              - org_unit.updated
              - org_unit.archived
              - org_unit.reactivated
              - working_pattern.created
              - working_pattern.updated
              - working_pattern.archived
              - webhook.test
          minItems: 1
          maxItems: 50
      required:
        - name
        - url
        - subscribed_events
      additionalProperties: false
    UpdateWebhookRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        url:
          type: string
          maxLength: 2048
          format: uri
        subscribed_events:
          type: array
          items:
            type: string
            enum:
              - person.created
              - person.updated
              - person.ended
              - person.reactivated
              - time_off.created
              - time_off.updated
              - time_off.cancelled
              - time_off_category.created
              - time_off_category.updated
              - time_off_category.archived
              - time_off_settings.updated
              - org_unit.created
              - org_unit.updated
              - org_unit.archived
              - org_unit.reactivated
              - working_pattern.created
              - working_pattern.updated
              - working_pattern.archived
              - webhook.test
          minItems: 1
          maxItems: 50
        status:
          type: string
          enum:
            - active
            - disabled
      additionalProperties: false
      description: At least one supported webhook field must be provided.
      minProperties: 1
    DeleteWebhookResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            name:
              type: string
            url:
              type: string
              format: uri
            status:
              type: string
              enum:
                - active
                - disabled
            subscribed_events:
              type: array
              items:
                type: string
                enum:
                  - person.created
                  - person.updated
                  - person.ended
                  - person.reactivated
                  - time_off.created
                  - time_off.updated
                  - time_off.cancelled
                  - time_off_category.created
                  - time_off_category.updated
                  - time_off_category.archived
                  - time_off_settings.updated
                  - org_unit.created
                  - org_unit.updated
                  - org_unit.archived
                  - org_unit.reactivated
                  - working_pattern.created
                  - working_pattern.updated
                  - working_pattern.archived
                  - webhook.test
            secret_last_four:
              type: string
            previous_secret_expires_at:
              type:
                - string
                - "null"
              format: date-time
            last_tested_at:
              type:
                - string
                - "null"
              format: date-time
            disabled_at:
              type:
                - string
                - "null"
              format: date-time
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - name
            - url
            - status
            - subscribed_events
            - secret_last_four
            - previous_secret_expires_at
            - last_tested_at
            - disabled_at
            - created_at
            - updated_at
          additionalProperties: false
        disabled:
          type: boolean
          enum:
            - true
      required:
        - data
        - disabled
      additionalProperties: false
    WebhookDeliveryResource:
      type: object
      properties:
        type:
          type: string
        id:
          type:
            - string
            - "null"
      required:
        - type
        - id
      additionalProperties: false
    WebhookDeliverySummary:
      type: object
      properties:
        id:
          type: string
          pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        webhook_id:
          type: string
          pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        event_id:
          type: string
        event_type:
          type: string
          enum:
            - person.created
            - person.updated
            - person.ended
            - person.reactivated
            - time_off.created
            - time_off.updated
            - time_off.cancelled
            - time_off_category.created
            - time_off_category.updated
            - time_off_category.archived
            - time_off_settings.updated
            - org_unit.created
            - org_unit.updated
            - org_unit.archived
            - org_unit.reactivated
            - working_pattern.created
            - working_pattern.updated
            - working_pattern.archived
            - webhook.test
        api_version:
          type: string
          enum:
            - v1
        resource:
          type:
            - object
            - "null"
          properties:
            type:
              type: string
            id:
              type:
                - string
                - "null"
          required:
            - type
            - id
          additionalProperties: false
        status:
          type: string
          enum:
            - pending
            - delivered
            - retry_scheduled
            - failed
        attempt_count:
          type: integer
          minimum: 0
        last_attempt_at:
          type:
            - string
            - "null"
          format: date-time
        next_attempt_at:
          type:
            - string
            - "null"
          format: date-time
        delivered_at:
          type:
            - string
            - "null"
          format: date-time
        last_status_code:
          type:
            - integer
            - "null"
          minimum: 100
          maximum: 599
        last_error_class:
          type:
            - string
            - "null"
          enum:
            - network
            - http_retryable
            - http_non_retryable
            - null
        last_error_message:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - webhook_id
        - event_id
        - event_type
        - api_version
        - resource
        - status
        - attempt_count
        - last_attempt_at
        - next_attempt_at
        - delivered_at
        - last_status_code
        - last_error_class
        - last_error_message
        - created_at
        - updated_at
      additionalProperties: false
    WebhookDeliveryAttempt:
      type: object
      properties:
        attempt_number:
          type: integer
          minimum: 1
        status:
          type: string
          enum:
            - success
            - retry_scheduled
            - failed
        status_code:
          type:
            - integer
            - "null"
          minimum: 100
          maximum: 599
        error_class:
          type:
            - string
            - "null"
          enum:
            - network
            - http_retryable
            - http_non_retryable
            - null
        error_message:
          type:
            - string
            - "null"
        latency_ms:
          type: integer
          minimum: 0
        attempted_at:
          type: string
          format: date-time
      required:
        - attempt_number
        - status
        - status_code
        - error_class
        - error_message
        - latency_ms
        - attempted_at
      additionalProperties: false
    WebhookDeliveryDetail:
      type: object
      properties:
        id:
          type: string
          pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        webhook_id:
          type: string
          pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        event_id:
          type: string
        event_type:
          type: string
          enum:
            - person.created
            - person.updated
            - person.ended
            - person.reactivated
            - time_off.created
            - time_off.updated
            - time_off.cancelled
            - time_off_category.created
            - time_off_category.updated
            - time_off_category.archived
            - time_off_settings.updated
            - org_unit.created
            - org_unit.updated
            - org_unit.archived
            - org_unit.reactivated
            - working_pattern.created
            - working_pattern.updated
            - working_pattern.archived
            - webhook.test
        api_version:
          type: string
          enum:
            - v1
        resource:
          type:
            - object
            - "null"
          properties:
            type:
              type: string
            id:
              type:
                - string
                - "null"
          required:
            - type
            - id
          additionalProperties: false
        status:
          type: string
          enum:
            - pending
            - delivered
            - retry_scheduled
            - failed
        attempt_count:
          type: integer
          minimum: 0
        last_attempt_at:
          type:
            - string
            - "null"
          format: date-time
        next_attempt_at:
          type:
            - string
            - "null"
          format: date-time
        delivered_at:
          type:
            - string
            - "null"
          format: date-time
        last_status_code:
          type:
            - integer
            - "null"
          minimum: 100
          maximum: 599
        last_error_class:
          type:
            - string
            - "null"
          enum:
            - network
            - http_retryable
            - http_non_retryable
            - null
        last_error_message:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        attempts:
          type: array
          items:
            type: object
            properties:
              attempt_number:
                type: integer
                minimum: 1
              status:
                type: string
                enum:
                  - success
                  - retry_scheduled
                  - failed
              status_code:
                type:
                  - integer
                  - "null"
                minimum: 100
                maximum: 599
              error_class:
                type:
                  - string
                  - "null"
                enum:
                  - network
                  - http_retryable
                  - http_non_retryable
                  - null
              error_message:
                type:
                  - string
                  - "null"
              latency_ms:
                type: integer
                minimum: 0
              attempted_at:
                type: string
                format: date-time
            required:
              - attempt_number
              - status
              - status_code
              - error_class
              - error_message
              - latency_ms
              - attempted_at
            additionalProperties: false
      required:
        - id
        - webhook_id
        - event_id
        - event_type
        - api_version
        - resource
        - status
        - attempt_count
        - last_attempt_at
        - next_attempt_at
        - delivered_at
        - last_status_code
        - last_error_class
        - last_error_message
        - created_at
        - updated_at
        - attempts
      additionalProperties: false
    WebhookHealth:
      type: object
      properties:
        webhook_id:
          type: string
          pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        endpoint_status:
          type: string
          enum:
            - active
            - disabled
        health_status:
          type: string
          enum:
            - healthy
            - recovering
            - failing
            - inactive
            - unknown
        action_required:
          type: boolean
        recommended_action:
          type: string
          enum:
            - none
            - inspect_failures
            - wait_for_retry
            - enable_endpoint
        checked_at:
          type: string
          format: date-time
        delivery_counts:
          type: object
          properties:
            total:
              type: integer
              minimum: 0
            pending:
              type: integer
              minimum: 0
            delivered:
              type: integer
              minimum: 0
            retry_scheduled:
              type: integer
              minimum: 0
            failed:
              type: integer
              minimum: 0
          required:
            - total
            - pending
            - delivered
            - retry_scheduled
            - failed
          additionalProperties: false
        last_delivery_at:
          type:
            - string
            - "null"
          format: date-time
        last_success_at:
          type:
            - string
            - "null"
          format: date-time
        last_failure_at:
          type:
            - string
            - "null"
          format: date-time
        next_retry_at:
          type:
            - string
            - "null"
          format: date-time
        oldest_pending_at:
          type:
            - string
            - "null"
          format: date-time
        latest_failure:
          type:
            - object
            - "null"
          properties:
            status_code:
              type:
                - integer
                - "null"
              minimum: 100
              maximum: 599
            error_class:
              type:
                - string
                - "null"
              enum:
                - network
                - http_retryable
                - http_non_retryable
                - null
            error_message:
              type:
                - string
                - "null"
            failed_at:
              type: string
              format: date-time
          required:
            - status_code
            - error_class
            - error_message
            - failed_at
          additionalProperties: false
      required:
        - webhook_id
        - endpoint_status
        - health_status
        - action_required
        - recommended_action
        - checked_at
        - delivery_counts
        - last_delivery_at
        - last_success_at
        - last_failure_at
        - next_retry_at
        - oldest_pending_at
        - latest_failure
      additionalProperties: false
    WebhookDeliveryList:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              webhook_id:
                type: string
                pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
              event_id:
                type: string
              event_type:
                type: string
                enum:
                  - person.created
                  - person.updated
                  - person.ended
                  - person.reactivated
                  - time_off.created
                  - time_off.updated
                  - time_off.cancelled
                  - time_off_category.created
                  - time_off_category.updated
                  - time_off_category.archived
                  - time_off_settings.updated
                  - org_unit.created
                  - org_unit.updated
                  - org_unit.archived
                  - org_unit.reactivated
                  - working_pattern.created
                  - working_pattern.updated
                  - working_pattern.archived
                  - webhook.test
              api_version:
                type: string
                enum:
                  - v1
              resource:
                type:
                  - object
                  - "null"
                properties:
                  type:
                    type: string
                  id:
                    type:
                      - string
                      - "null"
                required:
                  - type
                  - id
                additionalProperties: false
              status:
                type: string
                enum:
                  - pending
                  - delivered
                  - retry_scheduled
                  - failed
              attempt_count:
                type: integer
                minimum: 0
              last_attempt_at:
                type:
                  - string
                  - "null"
                format: date-time
              next_attempt_at:
                type:
                  - string
                  - "null"
                format: date-time
              delivered_at:
                type:
                  - string
                  - "null"
                format: date-time
              last_status_code:
                type:
                  - integer
                  - "null"
                minimum: 100
                maximum: 599
              last_error_class:
                type:
                  - string
                  - "null"
                enum:
                  - network
                  - http_retryable
                  - http_non_retryable
                  - null
              last_error_message:
                type:
                  - string
                  - "null"
              created_at:
                type: string
                format: date-time
              updated_at:
                type: string
                format: date-time
            required:
              - id
              - webhook_id
              - event_id
              - event_type
              - api_version
              - resource
              - status
              - attempt_count
              - last_attempt_at
              - next_attempt_at
              - delivered_at
              - last_status_code
              - last_error_class
              - last_error_message
              - created_at
              - updated_at
            additionalProperties: false
        pagination:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
            has_more:
              type: boolean
            limit:
              type: integer
              minimum: 1
              maximum: 100
          required:
            - next_cursor
            - has_more
            - limit
          additionalProperties: false
      required:
        - data
        - pagination
      additionalProperties: false
    WebhookDeliveryResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            webhook_id:
              type: string
              pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            event_id:
              type: string
            event_type:
              type: string
              enum:
                - person.created
                - person.updated
                - person.ended
                - person.reactivated
                - time_off.created
                - time_off.updated
                - time_off.cancelled
                - time_off_category.created
                - time_off_category.updated
                - time_off_category.archived
                - time_off_settings.updated
                - org_unit.created
                - org_unit.updated
                - org_unit.archived
                - org_unit.reactivated
                - working_pattern.created
                - working_pattern.updated
                - working_pattern.archived
                - webhook.test
            api_version:
              type: string
              enum:
                - v1
            resource:
              type:
                - object
                - "null"
              properties:
                type:
                  type: string
                id:
                  type:
                    - string
                    - "null"
              required:
                - type
                - id
              additionalProperties: false
            status:
              type: string
              enum:
                - pending
                - delivered
                - retry_scheduled
                - failed
            attempt_count:
              type: integer
              minimum: 0
            last_attempt_at:
              type:
                - string
                - "null"
              format: date-time
            next_attempt_at:
              type:
                - string
                - "null"
              format: date-time
            delivered_at:
              type:
                - string
                - "null"
              format: date-time
            last_status_code:
              type:
                - integer
                - "null"
              minimum: 100
              maximum: 599
            last_error_class:
              type:
                - string
                - "null"
              enum:
                - network
                - http_retryable
                - http_non_retryable
                - null
            last_error_message:
              type:
                - string
                - "null"
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
            attempts:
              type: array
              items:
                type: object
                properties:
                  attempt_number:
                    type: integer
                    minimum: 1
                  status:
                    type: string
                    enum:
                      - success
                      - retry_scheduled
                      - failed
                  status_code:
                    type:
                      - integer
                      - "null"
                    minimum: 100
                    maximum: 599
                  error_class:
                    type:
                      - string
                      - "null"
                    enum:
                      - network
                      - http_retryable
                      - http_non_retryable
                      - null
                  error_message:
                    type:
                      - string
                      - "null"
                  latency_ms:
                    type: integer
                    minimum: 0
                  attempted_at:
                    type: string
                    format: date-time
                required:
                  - attempt_number
                  - status
                  - status_code
                  - error_class
                  - error_message
                  - latency_ms
                  - attempted_at
                additionalProperties: false
          required:
            - id
            - webhook_id
            - event_id
            - event_type
            - api_version
            - resource
            - status
            - attempt_count
            - last_attempt_at
            - next_attempt_at
            - delivered_at
            - last_status_code
            - last_error_class
            - last_error_message
            - created_at
            - updated_at
            - attempts
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    WebhookDeliveryRedeliveryResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            id:
              type: string
              pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            webhook_id:
              type: string
              pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            event_id:
              type: string
            event_type:
              type: string
              enum:
                - person.created
                - person.updated
                - person.ended
                - person.reactivated
                - time_off.created
                - time_off.updated
                - time_off.cancelled
                - time_off_category.created
                - time_off_category.updated
                - time_off_category.archived
                - time_off_settings.updated
                - org_unit.created
                - org_unit.updated
                - org_unit.archived
                - org_unit.reactivated
                - working_pattern.created
                - working_pattern.updated
                - working_pattern.archived
                - webhook.test
            api_version:
              type: string
              enum:
                - v1
            resource:
              type:
                - object
                - "null"
              properties:
                type:
                  type: string
                id:
                  type:
                    - string
                    - "null"
              required:
                - type
                - id
              additionalProperties: false
            status:
              type: string
              enum:
                - pending
                - delivered
                - retry_scheduled
                - failed
            attempt_count:
              type: integer
              minimum: 0
            last_attempt_at:
              type:
                - string
                - "null"
              format: date-time
            next_attempt_at:
              type:
                - string
                - "null"
              format: date-time
            delivered_at:
              type:
                - string
                - "null"
              format: date-time
            last_status_code:
              type:
                - integer
                - "null"
              minimum: 100
              maximum: 599
            last_error_class:
              type:
                - string
                - "null"
              enum:
                - network
                - http_retryable
                - http_non_retryable
                - null
            last_error_message:
              type:
                - string
                - "null"
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
          required:
            - id
            - webhook_id
            - event_id
            - event_type
            - api_version
            - resource
            - status
            - attempt_count
            - last_attempt_at
            - next_attempt_at
            - delivered_at
            - last_status_code
            - last_error_class
            - last_error_message
            - created_at
            - updated_at
          additionalProperties: false
        redelivered:
          type: boolean
          enum:
            - true
      required:
        - data
        - redelivered
      additionalProperties: false
    WebhookHealthResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            webhook_id:
              type: string
              pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            endpoint_status:
              type: string
              enum:
                - active
                - disabled
            health_status:
              type: string
              enum:
                - healthy
                - recovering
                - failing
                - inactive
                - unknown
            action_required:
              type: boolean
            recommended_action:
              type: string
              enum:
                - none
                - inspect_failures
                - wait_for_retry
                - enable_endpoint
            checked_at:
              type: string
              format: date-time
            delivery_counts:
              type: object
              properties:
                total:
                  type: integer
                  minimum: 0
                pending:
                  type: integer
                  minimum: 0
                delivered:
                  type: integer
                  minimum: 0
                retry_scheduled:
                  type: integer
                  minimum: 0
                failed:
                  type: integer
                  minimum: 0
              required:
                - total
                - pending
                - delivered
                - retry_scheduled
                - failed
              additionalProperties: false
            last_delivery_at:
              type:
                - string
                - "null"
              format: date-time
            last_success_at:
              type:
                - string
                - "null"
              format: date-time
            last_failure_at:
              type:
                - string
                - "null"
              format: date-time
            next_retry_at:
              type:
                - string
                - "null"
              format: date-time
            oldest_pending_at:
              type:
                - string
                - "null"
              format: date-time
            latest_failure:
              type:
                - object
                - "null"
              properties:
                status_code:
                  type:
                    - integer
                    - "null"
                  minimum: 100
                  maximum: 599
                error_class:
                  type:
                    - string
                    - "null"
                  enum:
                    - network
                    - http_retryable
                    - http_non_retryable
                    - null
                error_message:
                  type:
                    - string
                    - "null"
                failed_at:
                  type: string
                  format: date-time
              required:
                - status_code
                - error_class
                - error_message
                - failed_at
              additionalProperties: false
          required:
            - webhook_id
            - endpoint_status
            - health_status
            - action_required
            - recommended_action
            - checked_at
            - delivery_counts
            - last_delivery_at
            - last_success_at
            - last_failure_at
            - next_retry_at
            - oldest_pending_at
            - latest_failure
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    WebhookDeliveryQuery:
      type: object
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 25
        cursor:
          type: string
          minLength: 1
        status:
          type: string
          enum:
            - pending
            - delivered
            - retry_scheduled
            - failed
        event_type:
          type: string
          enum:
            - person.created
            - person.updated
            - person.ended
            - person.reactivated
            - time_off.created
            - time_off.updated
            - time_off.cancelled
            - time_off_category.created
            - time_off_category.updated
            - time_off_category.archived
            - time_off_settings.updated
            - org_unit.created
            - org_unit.updated
            - org_unit.archived
            - org_unit.reactivated
            - working_pattern.created
            - working_pattern.updated
            - working_pattern.archived
            - webhook.test
        created_since:
          type: string
          format: date-time
        created_until:
          type: string
          format: date-time
      additionalProperties: false
    RotateWebhookSecretRequest:
      type: object
      properties: {}
      additionalProperties: false
      description: No request fields are accepted for secret rotation.
    WebhookTestDelivery:
      type: object
      properties:
        event_id:
          type: string
        delivery_id:
          type: string
          pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
        event_type:
          type: string
          enum:
            - person.created
            - person.updated
            - person.ended
            - person.reactivated
            - time_off.created
            - time_off.updated
            - time_off.cancelled
            - time_off_category.created
            - time_off_category.updated
            - time_off_category.archived
            - time_off_settings.updated
            - org_unit.created
            - org_unit.updated
            - org_unit.archived
            - org_unit.reactivated
            - working_pattern.created
            - working_pattern.updated
            - working_pattern.archived
            - webhook.test
        headers:
          type: object
          additionalProperties:
            type: string
        payload:
          type: object
          additionalProperties: {}
        queued_at:
          type: string
          format: date-time
      required:
        - event_id
        - delivery_id
        - event_type
        - headers
        - payload
        - queued_at
      additionalProperties: false
    WebhookTestDeliveryResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            event_id:
              type: string
            delivery_id:
              type: string
              pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            event_type:
              type: string
              enum:
                - person.created
                - person.updated
                - person.ended
                - person.reactivated
                - time_off.created
                - time_off.updated
                - time_off.cancelled
                - time_off_category.created
                - time_off_category.updated
                - time_off_category.archived
                - time_off_settings.updated
                - org_unit.created
                - org_unit.updated
                - org_unit.archived
                - org_unit.reactivated
                - working_pattern.created
                - working_pattern.updated
                - working_pattern.archived
                - webhook.test
            headers:
              type: object
              additionalProperties:
                type: string
            payload:
              type: object
              additionalProperties: {}
            queued_at:
              type: string
              format: date-time
          required:
            - event_id
            - delivery_id
            - event_type
            - headers
            - payload
            - queued_at
          additionalProperties: false
      required:
        - data
      additionalProperties: false
    TestWebhookRequest:
      type: object
      properties:
        event_type:
          type: string
          enum:
            - webhook.test
          default: webhook.test
      additionalProperties: false
paths:
  /me:
    get:
      operationId: getApiPrincipal
      tags:
        - Meta
      summary: Inspect the authenticated API key context
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeResponse"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /metadata:
    get:
      operationId: getApiMetadata
      tags:
        - Meta
      summary: Inspect the public API metadata catalogue
      description:
        Returns a generated machine-readable catalogue of routes, required scopes, query/path
        parameters, schema fields, scope field categories, and webhook event scope requirements. The
        catalogue is generated from the same OpenAPI/Zod contract source as the reference docs.
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Public API metadata catalogue.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MetadataResponse"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /organisation:
    get:
      operationId: getOrganisation
      tags:
        - Organisation
      summary: Fetch organisation account details
      description:
        Fetches safe company/account details for the authenticated organisation. The projection
        excludes owner user ids, created/updated user ids, custom-domain provider configuration,
        verification payloads, and other internal tenant infrastructure fields.
      security:
        - ApiKeyAuth: []
      x-required-scope: organisation:read
      responses:
        "200":
          description: Safe organisation account projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganisationResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Organisation not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /organisation/locations:
    get:
      operationId: listOrganisationLocations
      tags:
        - Organisation
      summary: List active organisation locations
      description: Lists active work locations for the authenticated organisation. Locations are
        organisation-owned office/worksite records, not employee home addresses.
      security:
        - ApiKeyAuth: []
      x-required-scope: organisation:read
      responses:
        "200":
          description: Active organisation locations.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrganisationLocationList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people:
    get:
      operationId: listPeople
      tags:
        - People
      summary: List active people
      description:
        Lists active people in the authenticated organisation. The projection intentionally
        includes work_email as an operational account/contact identifier for integrations, but
        excludes personal email, home contact data, date of birth, demographics, compensation, bank,
        tax/government identifiers, and avatar URLs/bytes.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:read
      parameters:
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
        - schema:
            type: string
            minLength: 2
            maxLength: 120
          required: false
          name: search
          in: query
        - schema:
            type: string
            format: date-time
          required: false
          name: updated_since
          in: query
      responses:
        "200":
          description: A cursor page of people.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonPage"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    post:
      operationId: createPerson
      tags:
        - People
      summary: Create a person
      description:
        Creates an active person using the safe setup field set. Requires an Idempotency-Key
        header. The endpoint does not send invite emails, create departments implicitly, or accept
        personal email, home contact data, date of birth, demographics, compensation, bank,
        tax/government identifiers, notes, or avatar bytes.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:write
      parameters:
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePersonRequest"
      responses:
        "201":
          description: Created person projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Related resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description:
            Conflict, such as an existing person in the authenticated organisation or idempotency
            mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}:
    get:
      operationId: getPerson
      tags:
        - People
      summary: Fetch one person
      description:
        Fetches one person by opaque public id. Missing, malformed, or cross-tenant ids return
        404 to avoid resource enumeration.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: A person projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for cache validation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonResponse"
        "304":
          description: The supplied If-None-Match value matches the current person version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updatePerson
      tags:
        - People
      summary: Update a person
      description:
        Updates safe setup fields on one person. Requires Idempotency-Key and If-Match headers.
        The endpoint does not accept work email, staff id, personal email, home contact data, date
        of birth, demographics, compensation, bank, tax/government identifiers, notes, or avatar
        bytes.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePersonRequest"
      responses:
        "200":
          description: Updated person projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person or related resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current resource version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/personal:
    get:
      operationId: getPersonPersonalProfile
      tags:
        - People
      summary: Fetch one person's elevated personal profile
      description: Fetches elevated personal profile fields for one person. Requires the explicit
        people:personal:read scope. The response may include date of birth, personal contact
        details, and home address, but still excludes demographics, compensation, bank details,
        tax/government identifiers, document bytes, notes, and avatar URLs/bytes. Missing,
        malformed, cross-tenant, or wrong-resource ids return 404 to avoid resource enumeration.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:personal:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: An elevated personal profile projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for cache validation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonPersonalProfileResponse"
        "304":
          description: The supplied If-None-Match value matches the current personal profile version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/avatar:
    get:
      operationId: getPersonAvatar
      tags:
        - People
      summary: Fetch one person's profile avatar URL
      description:
        Fetches a fresh one-hour profile-avatar download URL and image metadata for one person.
        Requires the explicit people:avatar:read scope. The response never includes inline avatar
        bytes, content hashes, personal contact data, demographics, compensation, bank details,
        tax/government identifiers, document bytes, or notes. Missing, malformed, cross-tenant,
        wrong-resource, no-avatar, or missing-object cases return 404 to avoid resource enumeration.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:avatar:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
      responses:
        "200":
          description: A short-lived profile-avatar download URL.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonAvatarResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Avatar not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/org-links:
    get:
      operationId: getPersonOrgLinks
      tags:
        - People
      summary: Fetch a person's organisation links
      description:
        Returns org-unit memberships and reporting relationships for one person. Memberships
        can include current and historical rows; archived org units are retained in membership
        history with archived_at so historical placement remains explainable. Requires people:read
        because the response is person placement data.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: A person's org-unit memberships and reporting relationships.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for cache validation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonOrgLinksResponse"
        "304":
          description: The supplied If-None-Match value matches the current org-link version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /custom-fields:
    get:
      operationId: listCustomFieldDefinitions
      tags:
        - Custom Fields
      summary: List API-visible custom-field definitions
      description:
        Lists person custom-field definitions explicitly marked API read or API read/write.
        Definitions hidden from the API are excluded, and the response includes only field metadata
        and configured options.
      security:
        - ApiKeyAuth: []
      x-required-scope: custom_fields:read
      parameters:
        - schema:
            type: string
            enum:
              - person
            default: person
          required: false
          name: target
          in: query
      responses:
        "200":
          description: API-visible custom-field definitions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CustomFieldDefinitionList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/custom-fields:
    get:
      operationId: getPersonCustomFields
      tags:
        - People
        - Custom Fields
      summary: Fetch a person's custom-field values
      description:
        Fetches values for API-visible person custom fields. Missing values are returned as
        null so integrators can distinguish unset fields from missing definitions.
      security:
        - ApiKeyAuth: []
      x-required-scope: custom_fields:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: Person custom-field values.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonCustomFieldValuesResponse"
        "304":
          description: The supplied If-None-Match value matches the current custom-field values.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updatePersonCustomFields
      tags:
        - People
        - Custom Fields
      summary: Update a person's custom-field values
      description:
        Updates values for custom-field definitions explicitly marked API read/write. Requires
        Idempotency-Key and If-Match headers. Send null or an empty string to clear a value.
      security:
        - ApiKeyAuth: []
      x-required-scope: custom_fields:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePersonCustomFieldsRequest"
      responses:
        "200":
          description: Updated person custom-field values.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonCustomFieldValuesResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current resource version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/time-off-balance:
    get:
      operationId: getPersonTimeOffBalance
      tags:
        - People
        - Time Off
      summary: Fetch a person's time-off balance explanation
      description:
        Returns the same time-off balance read model used by the HollyHR app for one person,
        calculated within the organisation's configured leave year. The response includes
        backwards-compatible annual-leave totals plus category-level allowance, approved usage,
        pending reservations, remaining balance, and the stored duration metadata basis used for the
        calculation.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
      responses:
        "200":
          description: Time-off balance explanation projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffBalanceResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/time-off-balance/ledger:
    get:
      operationId: listPersonLeaveBalanceLedger
      tags:
        - People
        - Time Off
      summary: List a person's leave-balance ledger history
      description:
        Returns a bounded, cursor-paginated history of safe leave-balance ledger entries for
        one person. Use this to reconcile allowance grants, carry-over, manual adjustments,
        corrections, pro-rata entries, and expiry lapses against the aggregate balance response. The
        response omits internal actor ids, API key ids, request ids, and free-text reason fields.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: false
          name: from
          in: query
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: false
          name: to
          in: query
      responses:
        "200":
          description: Leave-balance ledger history page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeaveBalanceLedgerList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/time-off-balance/adjustments:
    post:
      operationId: createPersonLeaveBalanceAdjustment
      tags:
        - People
        - Time Off
      summary: Create a leave-balance adjustment
      description:
        Creates an audited balance-ledger adjustment for one person and one consuming time-off
        category. Requires an Idempotency-Key header and the dedicated time_off:balance:write scope.
        The public response returns the safe ledger projection only; internal actor ids, reason
        text, and metadata are not exposed.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:balance:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLeaveBalanceAdjustmentRequest"
      responses:
        "201":
          description: Created leave-balance adjustment projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeaveBalanceAdjustmentResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person or time-off category not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/end:
    post:
      operationId: endPerson
      tags:
        - People
      summary: End a person
      description: Ends/offboards one person by setting an effective end date and inactive status.
        Requires Idempotency-Key and If-Match headers. The endpoint does not accept leaver reason,
        notes, compensation, personal contact data, or any other sensitive offboarding context.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EndPersonRequest"
      responses:
        "200":
          description: Ended person projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the lifecycle action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current resource version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/reactivate:
    post:
      operationId: reactivatePerson
      tags:
        - People
      summary: Reactivate a person
      description: Reactivates one inactive/ended person by clearing the end date and setting active
        status. Requires Idempotency-Key and If-Match headers. Active-person cap and seat checks are
        re-applied before the person becomes active again.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReactivatePersonRequest"
      responses:
        "200":
          description: Reactivated person projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the lifecycle action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current resource version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/employment:
    get:
      operationId: getPersonEmployment
      tags:
        - People
      summary: Get a person's employment details
      description:
        Returns the safe employment projection for one person, including job title, department,
        manager, work place, work type, start date, end date, and updated timestamp. The projection
        intentionally excludes compensation, notice pay, payroll data, reason, notes, personal
        contact data, date of birth, demographics, bank, tax/government identifiers, and avatar
        bytes.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: Employment projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for the employment projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmploymentResponse"
        "304":
          description: The supplied If-None-Match value matches the current employment version.
          headers:
            ETag:
              schema:
                type: string
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updatePersonEmployment
      tags:
        - People
      summary: Update a person's employment details
      description: Updates the safe employment subset for one person. Requires Idempotency-Key and
        If-Match headers. The endpoint does not accept compensation, notice pay, payroll data,
        reason, notes, personal contact data, date of birth, demographics, bank, tax/government
        identifiers, or avatar bytes.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEmploymentRequest"
      responses:
        "200":
          description: Updated employment projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmploymentResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person or related resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current employment version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/employment-history:
    get:
      operationId: listPersonEmploymentHistory
      tags:
        - People
      summary: List a person's employment-history rows
      description:
        Returns a cursor-paginated list of safe employment-history rows for one person. The
        projection excludes compensation, salary-change history, reason, notes, payroll data,
        document bytes, and other sensitive employment context. Closed historical rows return
        working_pattern, contracted_weekly_minutes, full_time_weekly_minutes, and fte as null
        because HollyHR does not store historical working-pattern assignments on employment-history
        rows.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
      responses:
        "200":
          description: A cursor page of employment-history rows.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmploymentHistoryPage"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    post:
      operationId: createPersonEmploymentHistory
      tags:
        - People
      summary: Create a person's employment-history entry
      description: Creates a safe dated employment-history entry for one person. Requires an
        Idempotency-Key header. Omitted employment fields inherit the person's current public
        employment values. The endpoint does not accept compensation, salary-change history, reason,
        notes, payroll data, or other sensitive employment context.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateEmploymentHistoryRequest"
      responses:
        "201":
          description: Created employment-history projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for the created employment-history entry.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmploymentHistoryResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person or related resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /people/{personId}/employment-history/{historyId}:
    get:
      operationId: getPersonEmploymentHistory
      tags:
        - People
      summary: Fetch one employment-history row
      description: Fetches one safe employment-history row for a person by opaque public id. Closed
        historical rows return working_pattern, contracted_weekly_minutes, full_time_weekly_minutes,
        and fte as null because HollyHR does not store historical working-pattern assignments on
        employment-history rows. Missing, malformed, cross-tenant, or mismatched person/history ids
        return 404 to avoid resource enumeration.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:read
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            pattern: ^eh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public employment-history id.
          required: true
          description: Encrypted public employment-history id.
          name: historyId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: Employment-history projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for the employment-history row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmploymentHistoryResponse"
        "304":
          description: The supplied If-None-Match value matches the current employment-history row version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Employment-history row not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updatePersonEmploymentHistory
      tags:
        - People
      summary: Correct an employment-history row
      description:
        Corrects safe fields on a closed employment-history row. Requires Idempotency-Key and
        If-Match headers. The endpoint does not accept compensation, salary-change history, reason,
        notes, payroll data, personal contact data, date of birth, demographics, bank,
        tax/government identifiers, avatar bytes, document bytes, or historical working-pattern
        fields. Open-ended current rows must be corrected through the current employment endpoint so
        the profile snapshot stays consistent.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:write
      parameters:
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
            description: Stable opaque 12-character tenant-person id.
          required: true
          description: Stable opaque 12-character tenant-person id.
          name: personId
          in: path
        - schema:
            type: string
            pattern: ^eh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public employment-history id.
          required: true
          description: Encrypted public employment-history id.
          name: historyId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateEmploymentHistoryRequest"
      responses:
        "200":
          description: Updated employment-history projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the correction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmploymentHistoryResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Employment-history row or related resource not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch or open-ended current row correction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current employment-history row version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /time-off:
    get:
      operationId: listTimeOff
      tags:
        - Time Off
      summary: List time-off records
      description:
        Lists time-off requests/bookings. Free-text notes and health-adjacent details are not
        exposed. The optional person_id filter must be an opaque public person id for the same
        tenant.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:read
      parameters:
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
          required: false
          name: person_id
          in: query
        - schema:
            type: string
            enum:
              - approved
              - declined
              - pending_approval
          required: false
          name: status
          in: query
        - schema:
            type: string
            minLength: 1
            maxLength: 64
          required: false
          name: type
          in: query
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: false
          name: from
          in: query
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: false
          name: to
          in: query
        - schema:
            type: string
            format: date-time
          required: false
          name: updated_since
          in: query
      responses:
        "200":
          description: A cursor page of time-off records.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffPage"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    post:
      operationId: createTimeOff
      tags:
        - Time Off
      summary: Create a time-off record
      description:
        "Creates a time-off record for a person using an active organisation time-off category.
        Requires an Idempotency-Key header. The endpoint follows HollyHR approval policy: records
        are pending when the person has a reporting manager and approved otherwise. It does not
        accept notes, approval decisions, rejection reasons, health details, raw person ids, or
        status overrides."
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:write
      parameters:
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTimeOffRequest"
      responses:
        "201":
          description: Created time-off projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Person or time-off category not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /time-off/who-is-out:
    get:
      operationId: listWhoIsOut
      tags:
        - Time Off
      summary: List who is out
      description:
        Lists approved time-off records overlapping a required date window, capped at 92 days,
        for calendar and absence integrations. The response uses the standard safe time-off
        projection and excludes notes, reasons, approval decisions, and health-adjacent details.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:read
      parameters:
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: true
          name: from
          in: query
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: true
          name: to
          in: query
      responses:
        "200":
          description: A cursor page of approved overlapping time-off records.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffPage"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /time-off/{timeOffId}:
    get:
      operationId: getTimeOff
      tags:
        - Time Off
      summary: Fetch one time-off record
      description:
        Fetches one time-off record by encrypted public id. Free-text notes and health-adjacent
        details are not exposed.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:read
      parameters:
        - schema:
            type: string
            pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public time-off id.
          required: true
          description: Encrypted public time-off id.
          name: timeOffId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: A time-off projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffResponse"
        "304":
          description: The supplied If-None-Match value matches the current time-off version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off record not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updateTimeOff
      tags:
        - Time Off
      summary: Update a time-off record
      description:
        "Updates safe fields on a pending time-off record: category, dates, and half-day flags.
        Requires Idempotency-Key and If-Match headers. Approved, taken, declined, or cancelled
        records cannot be changed through this endpoint. Status transitions, approval decisions,
        notes, rejection reasons, health details, and person changes are not accepted."
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:write
      parameters:
        - schema:
            type: string
            pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public time-off id.
          required: true
          description: Encrypted public time-off id.
          name: timeOffId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTimeOffRequest"
      responses:
        "200":
          description: Updated time-off projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off record or category not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current resource version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /time-off/{timeOffId}/cancel:
    post:
      operationId: cancelTimeOff
      tags:
        - Time Off
      summary: Cancel a time-off record
      description:
        Cancels a pending time-off record by marking it Cancelled. Requires Idempotency-Key and
        If-Match headers. Cancellation updates the record so incremental sync clients can observe it
        through updated_since. The endpoint does not accept cancellation notes or reasons.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:write
      parameters:
        - schema:
            type: string
            pattern: ^time_off_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public time-off id.
          required: true
          description: Encrypted public time-off id.
          name: timeOffId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CancelTimeOffRequest"
      responses:
        "200":
          description: Cancelled time-off projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CancelTimeOffResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off record not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current resource version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /time-off/settings:
    get:
      operationId: getTimeOffSettings
      tags:
        - Time Off
      summary: Fetch time-off settings
      description:
        Fetches the safe organisation time-off settings projection used by calendars and leave
        configuration. No per-person time-off records, notes, reasons, or sensitive policy internals
        are exposed.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:read
      parameters:
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: Safe time-off settings projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffSettingsResponse"
        "304":
          description: The supplied If-None-Match value matches the current settings version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off settings not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updateTimeOffSettings
      tags:
        - Time Off
      summary: Update time-off settings
      description: Updates the safe time-off settings subset. Requires Idempotency-Key and If-Match
        headers. The endpoint accepts only reset date, public-holiday visibility, country reference,
        and individual-country holiday mode.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:config:write
      parameters:
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTimeOffSettingsRequest"
      responses:
        "200":
          description: Updated time-off settings projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffSettingsResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off settings or country not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current settings version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /public-holidays:
    get:
      operationId: listPublicHolidays
      tags:
        - Time Off
      summary: List public holidays for the organisation country
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:read
      parameters:
        - schema:
            type: integer
            minimum: 1970
            maximum: 2999
          required: false
          name: year
          in: query
      responses:
        "200":
          description: Public holidays for the authenticated organisation's country.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicHolidayList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /documents:
    get:
      operationId: listDocuments
      tags:
        - Documents
      summary: List document metadata
      description: Lists document metadata only. Storage keys, signed URLs, file bytes, and document
        contents are not returned. The optional person_id filter must be an opaque public person id
        for the same tenant.
      security:
        - ApiKeyAuth: []
      x-required-scope: documents:read
      parameters:
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
        - schema:
            type: string
            pattern: ^[0123456789abcdefghjkmnpqrstvwxyz]{12}$
          required: false
          name: person_id
          in: query
        - schema:
            type: string
            format: date-time
          required: false
          name: updated_since
          in: query
      responses:
        "200":
          description: A cursor page of document metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentPage"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /documents/{documentId}:
    get:
      operationId: getDocument
      tags:
        - Documents
      summary: Fetch one document metadata record
      description:
        Fetches document metadata only. Storage keys, signed URLs, file bytes, and document
        contents are not returned.
      security:
        - ApiKeyAuth: []
      x-required-scope: documents:read
      parameters:
        - schema:
            type: string
            pattern: ^doc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public document id.
          required: true
          description: Encrypted public document id.
          name: documentId
          in: path
      responses:
        "200":
          description: A document metadata projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Document not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /provider-mappings:
    get:
      operationId: listProviderMappings
      tags:
        - Provider Mappings
      summary: List active provider mappings
      description:
        "Lists active external/provider ID mappings for a bounded local resource lookup. The
        request must include provider_name, resource_type, and resource_id. remote_object_type plus
        external_id can be supplied as an additional narrowing filter. This endpoint returns
        reconciliation metadata only: no raw mapped resource payload, no provider metadata, no
        API-key provenance, and no deleted mapping history. Keys require provider_mappings:read plus
        the relevant resource read scope before any lookup runs."
      security:
        - ApiKeyAuth: []
      x-required-scope: provider_mappings:read
      parameters:
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
        - schema:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-z0-9][a-z0-9._:-]*$
          required: true
          name: provider_name
          in: query
        - schema:
            type: string
            minLength: 1
            maxLength: 128
            pattern: ^[a-zA-Z0-9._:-]+$
            default: default
          required: false
          name: provider_account_id
          in: query
        - schema:
            type: string
            enum:
              - person
              - org_unit
              - person_org_unit_membership
              - person_relationship
              - custom_field_definition
              - time_off
              - working_pattern
          required: true
          name: resource_type
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: resource_id
          in: query
        - schema:
            type: string
            minLength: 1
            maxLength: 64
            pattern: ^[a-z0-9][a-z0-9._:-]*$
          required: false
          name: remote_object_type
          in: query
        - schema:
            type: string
            minLength: 1
            maxLength: 255
          required: false
          name: external_id
          in: query
      responses:
        "200":
          description: A cursor page of active provider mappings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderMappingList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /integration-readiness:
    get:
      operationId: getIntegrationReadiness
      tags:
        - Integrations
      summary: Inspect integration readiness
      description:
        Returns aggregate readiness for using HollyHR as the people, org-structure, lifecycle,
        and webhook source of truth for payroll, spend, and accounting handoffs. The response is a
        readiness/playbook contract, not evidence of native first-party connectors. It never returns
        raw salary, bank, DOB, home-address, receipt, expense, payroll-run, or provider-secret
        values. Keys require people:read; webhook endpoint inventory is included only when the same
        key also has webhooks:manage.
      security:
        - ApiKeyAuth: []
      x-required-scope: people:read
      responses:
        "200":
          description: Integration readiness summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntegrationReadinessResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /payroll-readiness-export:
    get:
      operationId: getPayrollReadinessExport
      tags:
        - Exports
      summary: Download the payroll readiness export
      description:
        Returns the versioned HollyHR payroll readiness export package as JSON file payloads.
        This is a provider-neutral handoff for payroll review and bureau/accountant workflows, not a
        native payroll connector. The package includes safe operational identity, employment,
        organisation, working-time fields, and sensitive-field presence flags only. By default it
        never exports raw DOB, home address, compensation, bank, tax/government identifiers, payroll
        run data, or payslips. When the organisation has an active ADR-0009 payroll-financial export
        grant WITH its allow-API-exports toggle enabled, the granted value tiers (personal_setup
        and/or payroll_financial) are included as additional CSV columns; bank details are never
        included under any grant. The optional provider preset (currently xero_payroll_uk) adds
        provider-shaped upload-template and readiness files for that provider's employee-import
        workflow without widening the exported data.
      security:
        - ApiKeyAuth: []
      x-required-scope: payroll_exports:read
      parameters:
        - schema:
            type: string
            enum:
              - xero_payroll_uk
          required: false
          name: provider
          in: query
      responses:
        "200":
          description: A safe payroll readiness export package.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayrollReadinessExportResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /payroll-changes-export:
    get:
      operationId: getPayrollChangesExport
      tags:
        - Exports
      summary: Download the pay-period payroll changes export
      description:
        "Returns starters, leavers, employment changes, and compensation/bank change flags for
        an inclusive pay-period date range (from/to, YYYY-MM-DD, max 400 days). This is the
        changes-only companion to the payroll readiness export: it tells a payroll operator,
        accountant, or bureau what to update without re-keying the whole employee list. By default
        sensitive payroll values are not included — compensation and bank changes are flags with
        effective dates only. When the organisation has an active ADR-0009 grant with API exports
        enabled, granted value tiers appear as additional columns in the starters/leavers files;
        per-change historical amounts and bank details are never included."
      security:
        - ApiKeyAuth: []
      x-required-scope: payroll_exports:read
      parameters:
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: true
          name: from
          in: query
        - schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          required: true
          name: to
          in: query
      responses:
        "200":
          description: A safe pay-period payroll changes export package.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayrollChangesExportResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /org-units:
    get:
      operationId: listOrgUnits
      tags:
        - Org Units
      summary: List organisation units
      description:
        Lists active organisation units for the authenticated organisation. Department remains
        the friendly product term for department-typed org units, but public integrations should use
        this canonical org-unit surface for departments, teams, divisions, cost centres, legal
        entities, and location groups. Legal entities and cost centres are structural nodes only;
        this endpoint does not expose payroll-grade legal-entity registration or accounting
        allocation data.
      security:
        - ApiKeyAuth: []
      x-required-scope: org_units:read
      responses:
        "200":
          description: Active organisation units.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgUnitList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    post:
      operationId: createOrgUnit
      tags:
        - Org Units
      summary: Create an organisation unit
      description: Creates a tenant-owned organisation unit. Requires an Idempotency-Key header. The
        endpoint creates structural org units only; it does not assign people, create reporting
        lines, or expose payroll-grade legal entity registration.
      security:
        - ApiKeyAuth: []
      x-required-scope: org_units:write
      parameters:
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOrgUnitRequest"
      responses:
        "201":
          description: Created organisation-unit projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgUnitResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description:
            Conflict, such as duplicate active name, provider external-id conflict, or idempotency
            mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /org-units/{orgUnitId}:
    get:
      operationId: getOrgUnit
      tags:
        - Org Units
      summary: Fetch one organisation unit
      description:
        Fetches one organisation unit by encrypted public id, including archived org units.
        Cross-tenant, malformed, or wrong-resource ids return 404. List endpoints return active org
        units only.
      security:
        - ApiKeyAuth: []
      x-required-scope: org_units:read
      parameters:
        - schema:
            type: string
            pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public org-unit id.
          required: true
          description: Encrypted public org-unit id.
          name: orgUnitId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: An organisation-unit projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for cache validation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgUnitResponse"
        "304":
          description: The supplied If-None-Match value matches the current org-unit version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Org unit not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updateOrgUnit
      tags:
        - Org Units
      summary: Update an organisation unit
      description:
        Updates safe organisation-unit fields. Requires Idempotency-Key and If-Match headers.
        Org-unit type is immutable after create; person memberships and reporting lines are not
        changed by this endpoint.
      security:
        - ApiKeyAuth: []
      x-required-scope: org_units:write
      parameters:
        - schema:
            type: string
            pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public org-unit id.
          required: true
          description: Encrypted public org-unit id.
          name: orgUnitId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOrgUnitRequest"
      responses:
        "200":
          description: Updated organisation-unit projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgUnitResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Org unit not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description:
            Conflict, such as archived resource, parent cycle, active-child invariant, provider
            mapping conflict, or idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current org-unit version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    delete:
      operationId: archiveOrgUnit
      tags:
        - Org Units
      summary: Archive an organisation unit
      description:
        Archives one tenant-owned organisation unit. Requires Idempotency-Key and If-Match
        headers. The endpoint is a soft lifecycle action, not a hard delete, and refuses org units
        with active children or current memberships.
      security:
        - ApiKeyAuth: []
      x-required-scope: org_units:write
      parameters:
        - schema:
            type: string
            pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public org-unit id.
          required: true
          description: Encrypted public org-unit id.
          name: orgUnitId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      responses:
        "200":
          description: Archived organisation-unit projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArchiveOrgUnitResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Org unit not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as active children, current memberships, or idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current org-unit version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /org-units/{orgUnitId}/reactivate:
    post:
      operationId: reactivateOrgUnit
      tags:
        - Org Units
      summary: Reactivate an organisation unit
      description: Reactivates one archived organisation unit. Requires Idempotency-Key and If-Match
        headers. Parent hierarchy and provider mapping constraints are rechecked before
        reactivation.
      security:
        - ApiKeyAuth: []
      x-required-scope: org_units:write
      parameters:
        - schema:
            type: string
            pattern: ^org_unit_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public org-unit id.
          required: true
          description: Encrypted public org-unit id.
          name: orgUnitId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      responses:
        "200":
          description: Reactivated organisation-unit projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReactivateOrgUnitResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Org unit not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description:
            Conflict, such as invalid parent hierarchy, provider mapping conflict, or idempotency
            mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current org-unit version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /working-patterns:
    get:
      operationId: listWorkingPatterns
      tags:
        - Working Patterns
      summary: List working patterns
      description:
        Lists reusable working-pattern templates for the authenticated organisation. Active
        templates are returned by default; include_archived=true can be used for reconciliation.
      security:
        - ApiKeyAuth: []
      x-required-scope: working_patterns:read
      parameters:
        - schema:
            type: boolean
            default: false
          required: false
          name: include_archived
          in: query
      responses:
        "200":
          description: Working-pattern templates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkingPatternList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    post:
      operationId: createWorkingPattern
      tags:
        - Working Patterns
      summary: Create a working pattern
      description:
        Creates a tenant-owned reusable working-pattern template. Requires an Idempotency-Key
        header. The endpoint accepts working-time template fields only; it does not assign the
        pattern to people.
      security:
        - ApiKeyAuth: []
      x-required-scope: working_patterns:write
      parameters:
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWorkingPatternRequest"
      responses:
        "201":
          description: Created working-pattern projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkingPatternResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as duplicate working-pattern name or idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /working-patterns/{workingPatternId}:
    get:
      operationId: getWorkingPattern
      tags:
        - Working Patterns
      summary: Fetch one working pattern
      description:
        Fetches one reusable working-pattern template by encrypted public id. Cross-tenant,
        malformed, or wrong-resource ids return 404.
      security:
        - ApiKeyAuth: []
      x-required-scope: working_patterns:read
      parameters:
        - schema:
            type: string
            pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public working-pattern id.
          required: true
          description: Encrypted public working-pattern id.
          name: workingPatternId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: A working-pattern projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkingPatternResponse"
        "304":
          description: The supplied If-None-Match value matches the current working-pattern version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Working pattern not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updateWorkingPattern
      tags:
        - Working Patterns
      summary: Update a working pattern
      description:
        Updates one tenant-owned reusable working-pattern template. Requires Idempotency-Key
        and If-Match headers. Archived working patterns cannot be edited.
      security:
        - ApiKeyAuth: []
      x-required-scope: working_patterns:write
      parameters:
        - schema:
            type: string
            pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public working-pattern id.
          required: true
          description: Encrypted public working-pattern id.
          name: workingPatternId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWorkingPatternRequest"
      responses:
        "200":
          description: Updated working-pattern projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkingPatternResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Working pattern not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as duplicate name, archived template, or idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current working-pattern version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    delete:
      operationId: archiveWorkingPattern
      tags:
        - Working Patterns
      summary: Archive a working pattern
      description:
        Archives one tenant-owned reusable working-pattern template. Requires Idempotency-Key
        and If-Match headers. Working patterns assigned to current profiles return conflict instead
        of being archived.
      security:
        - ApiKeyAuth: []
      x-required-scope: working_patterns:write
      parameters:
        - schema:
            type: string
            pattern: ^working_pattern_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public working-pattern id.
          required: true
          description: Encrypted public working-pattern id.
          name: workingPatternId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      responses:
        "200":
          description: Archived working-pattern projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArchiveWorkingPatternResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Working pattern not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an assigned working pattern or idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current working-pattern version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/countries:
    get:
      operationId: listReferenceCountries
      tags:
        - Reference Data
      summary: List country reference data
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      responses:
        "200":
          description: Reference data for the authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CountryList"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/statuses:
    get:
      operationId: listReferenceProfileStatuses
      tags:
        - Reference Data
      summary: List profile status reference data
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      responses:
        "200":
          description: Reference data for the authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProfileStatusList"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/work-types:
    get:
      operationId: listReferenceWorkTypes
      tags:
        - Reference Data
      summary: List work type reference data
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      responses:
        "200":
          description: Reference data for the authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkTypeList"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/work-places:
    get:
      operationId: listReferenceWorkPlaces
      tags:
        - Reference Data
      summary: List work place reference data
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      responses:
        "200":
          description: Reference data for the authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkPlaceList"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/time-off-statuses:
    get:
      operationId: listReferenceTimeOffStatuses
      tags:
        - Reference Data
      summary: List time-off status reference data
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      responses:
        "200":
          description: Reference data for the authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffStatusList"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/time-off-categories:
    get:
      operationId: listReferenceTimeOffCategories
      tags:
        - Reference Data
      summary: List organisation time-off category reference data
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      responses:
        "200":
          description: Reference data for the authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffCategoryList"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    post:
      operationId: createTimeOffCategory
      tags:
        - Reference Data
      summary: Create a time-off category
      description:
        Creates one tenant-owned time-off category for future time-off records. Requires an
        Idempotency-Key header. Duplicate names are rejected case-insensitively.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:config:write
      parameters:
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTimeOffCategoryRequest"
      responses:
        "201":
          description: Created time-off category projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffCategoryResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as duplicate category name or idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/time-off-categories/{categoryId}:
    get:
      operationId: getTimeOffCategory
      tags:
        - Reference Data
      summary: Fetch one time-off category
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      parameters:
        - schema:
            type: string
            pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public time-off category id.
          required: true
          description: Encrypted public time-off category id.
          name: categoryId
          in: path
        - schema:
            type: string
            description: Optional cache validator. Returns 304 when it matches the current ETag.
          required: false
          description: Optional cache validator. Returns 304 when it matches the current ETag.
          name: If-None-Match
          in: header
      responses:
        "200":
          description: A time-off category projection.
          headers:
            ETag:
              schema:
                type: string
              description: Strong version tag for conditional updates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffCategoryResponse"
        "304":
          description: The supplied If-None-Match value matches the current category version.
          headers:
            ETag:
              schema:
                type: string
              description: Current strong version tag.
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off category not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updateTimeOffCategory
      tags:
        - Reference Data
      summary: Update a time-off category
      description:
        Updates safe fields on one tenant-owned time-off category. Requires Idempotency-Key and
        If-Match headers. Updating a category name affects future time-off records; historical
        records keep their stored category label.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:config:write
      parameters:
        - schema:
            type: string
            pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public time-off category id.
          required: true
          description: Encrypted public time-off category id.
          name: categoryId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateTimeOffCategoryRequest"
      responses:
        "200":
          description: Updated time-off category projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the update.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TimeOffCategoryResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off category not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as duplicate category name or idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current category version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    delete:
      operationId: archiveTimeOffCategory
      tags:
        - Reference Data
      summary: Archive a time-off category
      description:
        Archives one tenant-owned time-off category by setting is_active to false. Requires
        Idempotency-Key and If-Match headers. The API does not hard-delete categories so historical
        time-off records remain interpretable.
      security:
        - ApiKeyAuth: []
      x-required-scope: time_off:config:write
      parameters:
        - schema:
            type: string
            pattern: ^toc_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public time-off category id.
          required: true
          description: Encrypted public time-off category id.
          name: categoryId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
        - schema:
            type: string
            description: Required for conditional updates. Use the exact strong ETag returned by the
              corresponding resource GET endpoint.
          required: true
          description: Required for conditional updates. Use the exact strong ETag returned by the
            corresponding resource GET endpoint.
          name: If-Match
          in: header
      responses:
        "200":
          description: Archived time-off category projection.
          headers:
            ETag:
              schema:
                type: string
              description: New strong version tag after the archive.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArchiveTimeOffCategoryResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Time-off category not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Conflict, such as an idempotency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "412":
          description: If-Match does not match the current category version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "428":
          description: If-Match header is required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /reference/document-types:
    get:
      operationId: listReferenceDocumentTypes
      tags:
        - Reference Data
      summary: List document type reference data
      security:
        - ApiKeyAuth: []
      x-required-scope: reference:read
      responses:
        "200":
          description: Reference data for the authenticated API key context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentTypeList"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks:
    get:
      operationId: listWebhooks
      tags:
        - Webhooks
      summary: List webhooks
      description:
        Lists outbound webhooks for the authenticated organisation. Signing secrets are never
        returned after create or rotation.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      responses:
        "200":
          description: Webhook summaries.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    post:
      operationId: createWebhook
      tags:
        - Webhooks
      summary: Create a webhook
      description: Creates an HTTPS webhook destination and encrypted signing secret. Requires an
        Idempotency-Key header. The plaintext signing secret is returned once only in this response.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookRequest"
      responses:
        "201":
          description: Created webhook with one-time signing secret.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSecretResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Webhook name or idempotency conflict.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks/{webhookId}:
    get:
      operationId: getWebhook
      tags:
        - Webhooks
      summary: Fetch one webhook
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
      responses:
        "200":
          description: Webhook summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    patch:
      operationId: updateWebhook
      tags:
        - Webhooks
      summary: Update a webhook
      description:
        Updates webhook name, URL, subscribed events, or active/disabled status. Requires an
        Idempotency-Key header. Signing secrets are not returned.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWebhookRequest"
      responses:
        "200":
          description: Updated webhook summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Webhook name or idempotency conflict.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
    delete:
      operationId: disableWebhook
      tags:
        - Webhooks
      summary: Disable a webhook
      description:
        Disables one webhook. Requires an Idempotency-Key header. The webhook is retained for
        audit and delivery history.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      responses:
        "200":
          description: Disabled webhook summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeleteWebhookResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Idempotency conflict.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks/{webhookId}/rotate-secret:
    post:
      operationId: rotateWebhookSecret
      tags:
        - Webhooks
      summary: Rotate a webhook signing secret
      description: Rotates the webhook signing secret. Requires an Idempotency-Key header. The new
        plaintext secret is returned once only; the previous secret remains valid for verification
        overlap.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RotateWebhookSecretRequest"
      responses:
        "200":
          description: Webhook with one-time new signing secret.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSecretResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Idempotency conflict.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks/{webhookId}/test:
    post:
      operationId: testWebhook
      tags:
        - Webhooks
      summary: Queue a webhook test delivery
      description:
        Queues a synthetic webhook.test event, creates a delivery record, and returns the exact
        signed headers and payload shape. Requires an Idempotency-Key header.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestWebhookRequest"
      responses:
        "202":
          description: Queued signed test delivery.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookTestDeliveryResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Webhook subscription or idempotency conflict.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks/{webhookId}/deliveries:
    get:
      operationId: listWebhookDeliveries
      tags:
        - Webhooks
      summary: List webhook deliveries
      description:
        Lists delivery-log summaries for one webhook endpoint. The response includes delivery
        state, retry timing, status/error metadata, and scoped resource metadata when the API key
        also has the event resource read scope. Payload bodies, request bodies, response bodies,
        signatures, and signing secrets are never returned.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            minLength: 1
          required: false
          name: cursor
          in: query
        - schema:
            type: string
            enum:
              - pending
              - delivered
              - retry_scheduled
              - failed
          required: false
          name: status
          in: query
        - schema:
            type: string
            enum:
              - person.created
              - person.updated
              - person.ended
              - person.reactivated
              - time_off.created
              - time_off.updated
              - time_off.cancelled
              - time_off_category.created
              - time_off_category.updated
              - time_off_category.archived
              - time_off_settings.updated
              - org_unit.created
              - org_unit.updated
              - org_unit.archived
              - org_unit.reactivated
              - working_pattern.created
              - working_pattern.updated
              - working_pattern.archived
              - webhook.test
          required: false
          name: event_type
          in: query
        - schema:
            type: string
            format: date-time
          required: false
          name: created_since
          in: query
        - schema:
            type: string
            format: date-time
          required: false
          name: created_until
          in: query
      responses:
        "200":
          description: A cursor page of webhook delivery summaries.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryList"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks/{webhookId}/health:
    get:
      operationId: getWebhookHealth
      tags:
        - Webhooks
      summary: Fetch webhook health
      description: Returns endpoint-level delivery health derived from retained delivery rows. The
        response includes delivery counts by status, recent success/failure timestamps, retry
        timing, and the latest sanitized failure summary. Payload bodies, response bodies,
        signatures, and signing secrets are never returned.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
      responses:
        "200":
          description: Webhook endpoint health summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookHealthResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks/{webhookId}/deliveries/{deliveryId}:
    get:
      operationId: getWebhookDelivery
      tags:
        - Webhooks
      summary: Fetch one webhook delivery
      description:
        Fetches one delivery-log record and its attempt history. The response exposes delivery
        status, HTTP status code, failure class, retry timing, latency, and timestamps only; payload
        bodies, request bodies, response bodies, signatures, and signing secrets are never returned.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
        - schema:
            type: string
            pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook delivery id.
          required: true
          description: Encrypted public webhook delivery id.
          name: deliveryId
          in: path
      responses:
        "200":
          description: Webhook delivery with attempt history.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryResponse"
        "400":
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook or delivery not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
  /webhooks/{webhookId}/deliveries/{deliveryId}/redeliver:
    post:
      operationId: redeliverWebhookDelivery
      tags:
        - Webhooks
      summary: Redeliver a webhook delivery
      description:
        Resets a failed or retry-scheduled webhook delivery so the delivery worker retries it
        on the next due run. Requires an Idempotency-Key header. Prior attempt history is preserved
        and no duplicate event or delivery row is created.
      security:
        - ApiKeyAuth: []
      x-required-scope: webhooks:manage
      parameters:
        - schema:
            type: string
            pattern: ^wh_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook id.
          required: true
          description: Encrypted public webhook id.
          name: webhookId
          in: path
        - schema:
            type: string
            pattern: ^whd_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$
            description: Encrypted public webhook delivery id.
          required: true
          description: Encrypted public webhook delivery id.
          name: deliveryId
          in: path
        - schema:
            type: string
            minLength: 8
            maxLength: 255
            pattern: ^[\x20-\x7E]+$
            description:
              Required for mutation requests. Reuse the same key only when retrying the exact same
              request.
          required: true
          description:
            Required for mutation requests. Reuse the same key only when retrying the exact same
            request.
          name: Idempotency-Key
          in: header
      responses:
        "202":
          description: Webhook delivery queued for redelivery.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryRedeliveryResponse"
        "400":
          description: Webhook delivery is not eligible for redelivery.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "401":
          description: Missing, malformed, unknown, revoked, or expired API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "403":
          description: Missing scope or plan entitlement.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "404":
          description: Webhook or delivery not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "409":
          description: Idempotency or delivery-state conflict.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
        "429":
          description: Rate limited. Responses include Retry-After and rate-limit metadata where available.
          headers:
            Retry-After:
              schema:
                type: string
              description: Seconds to wait before retrying the request.
            RateLimit-Limit:
              schema:
                type: string
              description: Current route bucket limit.
            RateLimit-Remaining:
              schema:
                type: string
              description: Requests remaining in the current route bucket window.
            RateLimit-Reset:
              schema:
                type: string
              description: Seconds until the current route bucket resets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorEnvelope"
