openapi: 3.0.3
info:
  title: Quardlyn CRM API
  version: v1
  description: >
    Public CRM API contract for third-party property CRM integrations.
    Version v1 is STABLE (API Contract Frozen) as of 2026-07-03. Developers
    can build against v1, new backward-compatible features may be added, and
    no public API contract changes may be made to v1 unless they are backward
    compatible or approved as a breaking v2 change.
servers:
  - url: https://{host}/api/v1/crm
    variables:
      host:
        default: example.quardlyn.test
security:
  - CrmKey: []
    CrmTimestamp: []
    CrmNonce: []
    CrmSignature: []
  - OAuthBearer: []
tags:
  - name: Metadata
  - name: Properties
  - name: Sandbox
paths:
  /health:
    get:
      tags: [Metadata]
      summary: Check CRM API availability
      security: []
      responses:
        '200':
          description: API health
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
  /capabilities:
    get:
      tags: [Metadata]
      summary: Return public CRM API capabilities
      security: []
      responses:
        '200':
          description: Public capabilities
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CapabilitiesResponse'
  /version:
    get:
      tags: [Metadata]
      summary: Return public CRM API version metadata
      security: []
      responses:
        '200':
          description: Version metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VersionResponse'
  /properties:
    get:
      tags: [Properties]
      summary: List properties for the authenticated CRM connection
      description: >
        Live Production GET endpoint. Returns a lightweight summary payload by
        default. Use expand=full to request complete canonical property objects.
        Results are scoped to the authenticated CRM connection; properties outside
        that scope must not be returned.
      operationId: listProperties
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
        - $ref: '#/components/parameters/StatusFilter'
        - $ref: '#/components/parameters/MarketingStatusFilter'
        - $ref: '#/components/parameters/DepartmentFilter'
        - $ref: '#/components/parameters/PublishedFilter'
        - $ref: '#/components/parameters/IncludeArchived'
        - $ref: '#/components/parameters/UpdatedSince'
        - $ref: '#/components/parameters/CreatedSince'
        - $ref: '#/components/parameters/SyncToken'
        - $ref: '#/components/parameters/Fields'
        - $ref: '#/components/parameters/Expand'
        - $ref: '#/components/parameters/Sort'
        - $ref: '#/components/parameters/Order'
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Property page for the authenticated CRM connection
          headers:
            X-API-Version:
              schema:
                type: string
            X-Request-Id:
              schema:
                type: string
            ETag:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PropertyListResponse'
        '304':
          description: Not modified. Returned when If-None-Match matches the current collection representation.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
    post:
      tags: [Properties]
      summary: Create or upsert a property
      operationId: createProperty
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/RequestId'
        - $ref: '#/components/parameters/ProviderRequestId'
        - $ref: '#/components/parameters/EventId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PropertyRequest'
      responses:
        '201':
          description: Property accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /properties/{external_ref}:
    get:
      tags: [Properties]
      summary: Retrieve a full canonical property by external reference
      description: >
        Live Production GET endpoint. Returns the full canonical property payload
        by default. Access is scoped to the authenticated CRM connection. If the
        property exists but does not belong to the connection, return 404 to avoid
        leaking existence.
      operationId: getPropertyByExternalRef
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/Fields'
        - $ref: '#/components/parameters/Expand'
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Full canonical property payload
          headers:
            X-API-Version:
              schema:
                type: string
            X-Request-Id:
              schema:
                type: string
            ETag:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PropertyGetResponse'
        '304':
          description: Not modified. Returned when If-None-Match matches the current property representation.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
    put:
      tags: [Properties]
      summary: Replace or upsert a property by external reference
      operationId: updateProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/RequestId'
        - $ref: '#/components/parameters/ProviderRequestId'
        - $ref: '#/components/parameters/EventId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PropertyRequest'
      responses:
        '200':
          description: Property updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
    patch:
      tags: [Properties]
      summary: Partially update or upsert a property by external reference
      operationId: patchProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/RequestId'
        - $ref: '#/components/parameters/ProviderRequestId'
        - $ref: '#/components/parameters/EventId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PropertyRequest'
      responses:
        '200':
          description: Property updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
    delete:
      tags: [Properties]
      summary: Delete or inactivate a property
      operationId: deleteProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/RequestId'
        - $ref: '#/components/parameters/ProviderRequestId'
        - $ref: '#/components/parameters/EventId'
      responses:
        '200':
          description: Property deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /properties/{external_ref}/withdraw:
    post:
      tags: [Properties]
      summary: Withdraw a property
      operationId: withdrawProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /properties/{external_ref}/archive:
    post:
      tags: [Properties]
      summary: Archive a property
      operationId: archiveProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /properties/{external_ref}/restore:
    post:
      tags: [Properties]
      summary: Restore an archived property
      operationId: restoreProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /properties/{external_ref}/reactivate:
    post:
      tags: [Properties]
      summary: Reactivate a property
      operationId: reactivateProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /properties/lookup:
    get:
      tags: [Properties]
      summary: Retrieve a property by explicit identifier type
      description: >
        Live Production GET endpoint. Use this endpoint when looking up by immutable
        property_uid, or when external references may have changed. The endpoint
        does not guess identifier type; id_type and value are required.
      operationId: lookupProperty
      parameters:
        - name: id_type
          in: query
          required: true
          schema:
            type: string
            enum: [external_ref, property_uid]
        - name: value
          in: query
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/Fields'
        - $ref: '#/components/parameters/Expand'
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Full canonical property payload
          headers:
            X-API-Version:
              schema:
                type: string
            X-Request-Id:
              schema:
                type: string
            ETag:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PropertyGetResponse'
        '304':
          description: Not modified.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /properties/count:
    get:
      tags: [Properties]
      summary: 'Reserved v1 contract: count properties matching filters'
      description: Reserved for future efficient CRM synchronization and dashboards.
      operationId: countProperties
      parameters:
        - $ref: '#/components/parameters/StatusFilter'
        - $ref: '#/components/parameters/MarketingStatusFilter'
        - $ref: '#/components/parameters/DepartmentFilter'
        - $ref: '#/components/parameters/PublishedFilter'
        - $ref: '#/components/parameters/IncludeArchived'
        - $ref: '#/components/parameters/UpdatedSince'
        - $ref: '#/components/parameters/CreatedSince'
        - $ref: '#/components/parameters/SyncToken'
      responses:
        '200':
          description: Count result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PropertyCountResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
  /properties/changes:
    get:
      tags: [Properties]
      summary: 'Reserved v1 contract: incremental property changes'
      description: Reserved for future sync_token based incremental synchronization.
      operationId: listPropertyChanges
      parameters:
        - $ref: '#/components/parameters/SyncToken'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
        - $ref: '#/components/parameters/Fields'
        - $ref: '#/components/parameters/Expand'
      responses:
        '200':
          description: Incremental property changes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PropertyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
  /properties/deleted:
    get:
      tags: [Properties]
      summary: 'Reserved v1 contract: deleted or withdrawn property references'
      description: Reserved for future downstream CRM deletion reconciliation.
      operationId: listDeletedProperties
      parameters:
        - $ref: '#/components/parameters/SyncToken'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: Deleted property references
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeletedPropertyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
  /properties/schema:
    get:
      tags: [Properties]
      summary: 'Reserved v1 contract: canonical property schema metadata'
      description: >
        Reserved for future clients that need runtime discovery
        of supported fields, enums, limits, media capabilities, ownership metadata and
        canonical schema version.
      operationId: getPropertySchema
      responses:
        '200':
          description: Canonical schema capability metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PropertySchemaResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
  /sandbox/crm/properties:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    get:
      tags: [Sandbox]
      summary: List sandbox properties for certification and sync testing
      operationId: sandboxListProperties
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
      responses:
        '200':
          description: Sandbox property page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxPropertyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
    post:
      tags: [Sandbox]
      summary: Create or upsert a sandbox property using the canonical schema
      operationId: sandboxCreateProperty
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PropertyRequest'
      responses:
        '201':
          description: Sandbox property created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxPropertyResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/ValidationFailed'
  /sandbox/crm/properties/{external_ref}:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    put:
      tags: [Sandbox]
      summary: Replace or upsert a sandbox property
      operationId: sandboxUpdateProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PropertyRequest'
      responses:
        '200':
          description: Sandbox property updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxPropertyResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationFailed'
    patch:
      tags: [Sandbox]
      summary: Partially update a sandbox property
      operationId: sandboxPatchProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PropertyRequest'
      responses:
        '200':
          description: Sandbox property updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxPropertyResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationFailed'
    delete:
      tags: [Sandbox]
      summary: Delete a sandbox property
      operationId: sandboxDeleteProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /sandbox/crm/properties/{external_ref}/withdraw:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    post:
      tags: [Sandbox]
      summary: Withdraw a sandbox property
      operationId: sandboxWithdrawProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /sandbox/crm/properties/{external_ref}/archive:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    post:
      tags: [Sandbox]
      summary: Archive a sandbox property
      operationId: sandboxArchiveProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /sandbox/crm/properties/{external_ref}/restore:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    post:
      tags: [Sandbox]
      summary: Restore a sandbox property
      operationId: sandboxRestoreProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /sandbox/crm/properties/{external_ref}/reactivate:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    post:
      tags: [Sandbox]
      summary: Reactivate a sandbox property
      operationId: sandboxReactivateProperty
      parameters:
        - $ref: '#/components/parameters/ExternalRef'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /sandbox/crm/sync:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    get:
      tags: [Sandbox]
      summary: Run sandbox initial or delta sync checks
      operationId: sandboxSync
      parameters:
        - name: cursor
          in: query
          required: false
          schema:
            type: string
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
      responses:
        '200':
          description: Sandbox sync page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxPropertyListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
  /sandbox/crm/webhooks:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    get:
      tags: [Sandbox]
      summary: List sandbox webhook simulations generated during certification
      operationId: sandboxListWebhooks
      responses:
        '200':
          description: Sandbox webhook simulation list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxWebhookListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
  /sandbox/crm/webhooks/{webhook_id}/replay:
    servers:
      - url: https://{host}/api/v1
        variables:
          host:
            default: example.quardlyn.test
    post:
      tags: [Sandbox]
      summary: Replay a sandbox webhook simulation
      operationId: sandboxReplayWebhook
      parameters:
        - name: webhook_id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          $ref: '#/components/responses/Success'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
components:
  securitySchemes:
    CrmKey:
      type: apiKey
      in: header
      name: X-CRM-Key
    CrmTimestamp:
      type: apiKey
      in: header
      name: X-CRM-Timestamp
    CrmNonce:
      type: apiKey
      in: header
      name: X-CRM-Nonce
    CrmSignature:
      type: apiKey
      in: header
      name: X-CRM-Signature
    OAuthBearer:
      type: http
      scheme: bearer
      bearerFormat: opaque
  parameters:
    ExternalRef:
      name: external_ref
      in: path
      required: true
      description: URL-encoded, case-sensitive external property reference unique per authenticated connection.
      schema:
        type: string
        maxLength: 191
        pattern: '^[^\x00-\x1F\x7F\/?#]+$'
    Limit:
      name: limit
      in: query
      required: false
      description: Page size. Default 50, maximum 100.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    Offset:
      name: offset
      in: query
      required: false
      description: Zero-based page offset.
      schema:
        type: integer
        minimum: 0
        default: 0
    StatusFilter:
      name: status
      in: query
      required: false
      schema:
        type: string
    MarketingStatusFilter:
      name: marketing_status
      in: query
      required: false
      schema:
        type: string
        enum: [draft, available, under_offer, sold_stc, sold, let_agreed, let, withdrawn, archived]
    DepartmentFilter:
      name: department
      in: query
      required: false
      schema:
        type: string
        enum: [sale, let, rent, auction, commercial]
    PublishedFilter:
      name: published
      in: query
      required: false
      schema:
        type: boolean
    IncludeArchived:
      name: include_archived
      in: query
      required: false
      description: Archived/deleted records are hidden by default.
      schema:
        type: boolean
        default: false
    UpdatedSince:
      name: updated_since
      in: query
      required: false
      schema:
        type: string
        format: date-time
    CreatedSince:
      name: created_since
      in: query
      required: false
      schema:
        type: string
        format: date-time
    SyncToken:
      name: sync_token
      in: query
      required: false
      description: Opaque token returned by previous list/change responses for incremental sync.
      schema:
        type: string
    Fields:
      name: fields
      in: query
      required: false
      description: Comma-separated canonical fields to include. Unknown fields return validation_failed.
      schema:
        type: string
        example: property_uid,external_ref,title,price,status
    Expand:
      name: expand
      in: query
      required: false
      description: Comma-separated expansion set. Allowed values are media, documents, ownership, rooms, virtual_tour and full. Collection defaults to summary; expand=full returns full canonical objects.
      schema:
        type: string
        example: media,documents,ownership
    Sort:
      name: sort
      in: query
      required: false
      schema:
        type: string
        enum: [updated_at, created_at, price, title]
        default: updated_at
    Order:
      name: order
      in: query
      required: false
      schema:
        type: string
        enum: [asc, desc]
        default: desc
    IfNoneMatch:
      name: If-None-Match
      in: header
      required: false
      description: ETag value from a previous response. Matching resources return 304 Not Modified.
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      schema:
        type: string
    ProviderRequestId:
      name: X-Provider-Request-Id
      in: header
      required: false
      schema:
        type: string
    EventId:
      name: X-Event-Id
      in: header
      required: false
      schema:
        type: string
  responses:
    Success:
      description: Successful operation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SuccessResponse'
    Unauthorized:
      description: Missing or invalid CRM authentication.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: Authenticated but not permitted.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: Resource not found for the authenticated connection.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Conflict:
      description: Duplicate request, already processing request, or property conflict.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    ValidationFailed:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalError:
      description: Internal processing error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PayloadTooLarge:
      description: Request body exceeds the sandbox or production payload limit.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    PropertyGetResponse:
      type: object
      required: [success, api_version, environment, external_ref, property, request_id]
      properties:
        success:
          type: boolean
          example: true
        api_version:
          type: string
          example: v1
        environment:
          type: string
          example: production
        external_ref:
          type: string
          example: CRM-SALE-001
        property:
          $ref: '#/components/schemas/Property'
        meta:
          type: object
          properties:
            retrieved_at:
              type: string
              format: date-time
            property_uid:
              type: string
            managed_mode:
              type: string
              enum: [CRM_MASTER, HYBRID, PORTAL_MASTER]
            ownership_source:
              type: string
            last_sync_at:
              type: string
              format: date-time
        request_id:
          type: string
          example: req_abc123
    PropertySummary:
      type: object
      description: Lightweight collection item returned by GET /properties unless expand=full is requested.
      properties:
        property_uid:
          type: string
        external_ref:
          type: string
        title:
          type: string
        display_address:
          type: string
        department:
          type: string
        status:
          type: string
        marketing_status:
          type: string
        price:
          type: number
        price_display:
          type: string
        rent_price:
          type: number
        rent_frequency:
          type: string
        updated_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        published:
          type: boolean
        thumbnail:
          type: string
          format: uri
        has_virtual_tour:
          type: boolean
        has_floorplan:
          type: boolean
        has_brochure:
          type: boolean
        has_epc:
          type: boolean
        managed_mode:
          type: string
          enum: [CRM_MASTER, HYBRID, PORTAL_MASTER]
        ownership_source:
          type: string
    PropertyListResponse:
      type: object
      required: [success, api_version, environment, data, pagination, request_id]
      properties:
        success:
          type: boolean
          example: true
        api_version:
          type: string
          example: v1
        environment:
          type: string
          example: production
        data:
          type: array
          description: PropertySummary by default; full Property objects when expand=full.
          items:
            oneOf:
              - $ref: '#/components/schemas/PropertySummary'
              - $ref: '#/components/schemas/Property'
        pagination:
          type: object
          properties:
            limit:
              type: integer
              example: 50
            offset:
              type: integer
              example: 0
            count:
              type: integer
              example: 50
            has_more:
              type: boolean
              example: true
            next_offset:
              type: integer
              example: 50
        filters:
          type: object
          additionalProperties: true
        sync:
          type: object
          properties:
            next_sync_token:
              type: string
            watermark:
              type: string
              format: date-time
        request_id:
          type: string
          example: req_abc123
    PropertyCountResponse:
      type: object
      required: [success, api_version, environment, count, request_id]
      properties:
        success:
          type: boolean
          example: true
        api_version:
          type: string
          example: v1
        environment:
          type: string
          example: production
        count:
          type: integer
          example: 123
        filters:
          type: object
          additionalProperties: true
        request_id:
          type: string
          example: req_abc123
    DeletedProperty:
      type: object
      properties:
        property_uid:
          type: string
        external_ref:
          type: string
        deleted_at:
          type: string
          format: date-time
        status:
          type: string
          example: deleted
    DeletedPropertyListResponse:
      type: object
      required: [success, api_version, environment, data, pagination, request_id]
      properties:
        success:
          type: boolean
          example: true
        api_version:
          type: string
          example: v1
        environment:
          type: string
          example: production
        data:
          type: array
          items:
            $ref: '#/components/schemas/DeletedProperty'
        pagination:
          type: object
          additionalProperties: true
        sync:
          type: object
          properties:
            next_sync_token:
              type: string
            watermark:
              type: string
              format: date-time
        request_id:
          type: string
          example: req_abc123
    PropertySchemaResponse:
      type: object
      required: [success, version, canonical_fields, capabilities]
      properties:
        success:
          type: boolean
          example: true
        version:
          type: string
          example: v1
        canonical_fields:
          type: integer
          example: 371
        capabilities:
          type: object
          properties:
            supports_virtual_tours:
              type: boolean
            supports_documents:
              type: boolean
            supports_rooms:
              type: boolean
            supports_ownership:
              type: boolean
            supports_media:
              type: boolean
            supports_field_selection:
              type: boolean
            supports_etags:
              type: boolean
            supports_sync_token:
              type: boolean
        fields:
          type: array
          items:
            type: string
        enums:
          type: object
          additionalProperties: true
        limits:
          type: object
          additionalProperties: true
    PropertyRequest:
      type: object
      required: [property]
      properties:
        request_id:
          type: string
        provider_request_id:
          type: string
        event_id:
          type: string
        idempotency_key:
          type: string
        property:
          $ref: '#/components/schemas/Property'
    Property:
      type: object
      description: >
        Quardlyn canonical property import model. This is the same flat canonical
        property object used by the Feed Engine for BLM, CSV and XML imports.
        Nested CRM aliases may be accepted internally for legacy clients, but
        they are not the public schema.
      required: [external_ref, department, display_address]
      additionalProperties: false
      properties:
        external_ref:
          type: string
          maxLength: 191
          description: Required. Provider property reference unique within the authenticated connection.
          example: CRM-SALE-001
        status_id:
          type: integer
          description: Optional. Import status code. 1 available, 2 under offer, 3 sold STC, 4 sold, 5 inactive.
          enum: [0, 1, 2, 3, 4, 5]
          example: 1
        trans_type_id:
          type: integer
          description: Optional. Transaction type. 1 sale, 2 let, 3 auction, 4 commercial.
          enum: [1, 2, 3, 4]
          example: 1
        category:
          type: string
          description: Optional. Provider category such as residential, commercial, land, new_homes, student or shared_ownership.
          example: residential
        department:
          type: string
          description: Required. Canonical department imported to uk_pfor.
          enum: [sale, let, rent, auction, commercial]
          example: sale
        marketing_status:
          type: string
          description: Optional. Public listing state used with published to choose LIVE or DRAFT import state.
          enum: [draft, available, under_offer, sold_stc, sold, let_agreed, let, withdrawn, archived]
          example: available
        status:
          type: string
          description: Optional. Provider status label accepted alongside marketing_status/status_id.
          example: For Sale
        published:
          type: boolean
          description: Optional. true imports as LIVE when validation passes; false or omitted imports as DRAFT.
          example: true
        title:
          type: string
          description: Optional. Public listing title. display_address remains required for ingestion.
          example: Three-bedroom house near the station
        display_address:
          type: string
          description: Required. Public display title/address.
          example: 10 High Street, London
        address_1:
          type: string
          description: Optional. Address line 1.
          example: 10 High Street
        address_2:
          type: string
          description: Optional. Address line 2.
          example: Westminster
        address_3:
          type: string
          description: Optional. Address line 3/locality.
          example: St James
        address_4:
          type: string
          description: Optional. Address line 4.
          example: Greater London
        town:
          type: string
          description: Optional. Postal town/city.
          example: London
        county:
          type: string
          description: Optional. County/area.
          example: Greater London
        country:
          type: string
          description: Optional. Country name. Defaults internally to United Kingdom when omitted.
          example: United Kingdom
        postcode:
          type: string
          description: Required for live publishing. Full postcode; may be generated from postcode1 and postcode2.
          example: SW1A 1AA
        postcode1:
          type: string
          description: Optional. Outward postcode component for feeds that split postcode.
          example: SW1A
        postcode2:
          type: string
          description: Optional. Inward postcode component for feeds that split postcode.
          example: 1AA
        latitude:
          type: number
          format: double
          description: Optional. Decimal latitude. Alias lat is also accepted internally.
          example: 51.501
        longitude:
          type: number
          format: double
          description: Optional. Decimal longitude. Alias lng is also accepted internally.
          example: -0.141
        lat:
          type: number
          format: double
          description: Optional. Feed-engine coordinate field equivalent to latitude.
          example: 51.501
        lng:
          type: number
          format: double
          description: Optional. Feed-engine coordinate field equivalent to longitude.
          example: -0.141
        property_type:
          oneOf:
            - type: string
            - type: integer
          description: Optional. Internal property main type id or canonical type label.
          example: Residential Property
        property_subtype:
          oneOf:
            - type: string
            - type: integer
          description: Optional. Internal subtype id or provider subtype value resolved by import mapping.
          example: 101
        property_sub_type:
          oneOf:
            - type: string
            - type: integer
          description: Optional. Alternate canonical subtype spelling accepted by the API.
          example: terraced
        beds:
          type: integer
          minimum: 0
          description: Optional. Bedroom count.
          example: 3
        bedrooms:
          type: integer
          minimum: 0
          description: Optional. Bedroom count alias accepted by the API.
          example: 3
        baths:
          type: integer
          minimum: 0
          description: Optional. Bathroom count.
          example: 2
        bathrooms:
          type: integer
          minimum: 0
          description: Optional. Bathroom count alias accepted by the API.
          example: 2
        receptions:
          type: integer
          minimum: 0
          description: Optional. Reception/living room count.
          example: 1
        price:
          type: number
          description: Required for live sale, let and auction unless price_display is supplied. Sale price, rent amount or auction guide price.
          example: 525000
        sale_price:
          type: number
          description: Optional. Sale-specific price when a feed separates sale, rent and auction prices.
          example: 525000
        rent_price:
          type: number
          description: Optional. Lettings-specific rent amount when supplied separately from price.
          example: 1650
        auction_price:
          type: number
          description: Optional. Auction guide/reserve amount when supplied separately from price.
          example: 300000
        currency:
          type: string
          description: Optional. ISO-style currency code or provider currency label.
          example: GBP
        price_display:
          type: string
          description: Optional. Display price text such as POA or Offers over 525000.
          example: Offers over GBP 525,000
        price_qualifier:
          type: string
          description: Optional. Provider price qualifier.
          example: offers_over
        rent_frequency:
          type: string
          description: Optional for lettings. Rent period.
          enum: [PCM, PW, pcm, pw, '1', '2']
          example: PCM
        deposit:
          type: number
          description: Optional. Lettings deposit/bond amount.
          example: 1673
        available_from:
          type: string
          format: date
          description: Optional. Lettings available date.
          example: 2026-08-01
        tenure:
          type: string
          description: Optional. Sale/auction tenure label.
          enum: [Freehold, Leasehold, Share of Freehold, Commonhold, Unknown, freehold, leasehold, share_of_freehold, commonhold, unknown]
          example: Freehold
        tenure_type_id:
          oneOf:
            - type: integer
            - type: string
          description: Optional. Tenure code. 1 Freehold, 2 Leasehold, or tenure label.
          example: 1
        new_home_flag:
          oneOf:
            - type: boolean
            - type: integer
            - type: string
          description: Optional. New build/new home flag.
          example: false
        new_build:
          oneOf:
            - type: boolean
            - type: integer
            - type: string
          description: Optional. New build flag alias accepted by the API.
          example: false
        furnished:
          oneOf:
            - type: boolean
            - type: integer
            - type: string
          description: Optional. Furnishing state for lettings.
          example: Furnished
        leasehold_years:
          type: number
          description: Optional. Remaining lease length in years.
          example: 998
        service_charge:
          type: number
          description: Optional. Leasehold or building service charge.
          example: 1200
        ground_rent:
          type: number
          description: Optional. Leasehold ground rent.
          example: 250
        council_tax_band:
          type: string
          description: Optional. Council tax band.
          example: D
        epc_rating:
          type: string
          description: Optional. EPC rating label.
          example: C
        epc_current_rating:
          type: string
          description: Optional. EPC current rating.
          example: C
        epc_potential_rating:
          type: string
          description: Optional. EPC potential rating.
          example: B
        parking:
          type: string
          description: Optional. Parking description or availability flag.
          example: Driveway
        garage:
          type: string
          description: Optional. Garage description or availability flag.
          example: Single garage
        garden:
          type: string
          description: Optional. Garden description or availability flag.
          example: Rear garden
        balcony:
          type: string
          description: Optional. Balcony description or availability flag.
          example: Private balcony
        lift:
          type: string
          description: Optional. Lift access description or availability flag.
          example: Lift access
        accessibility:
          type: string
          description: Optional. Accessibility notes.
          example: Step-free entrance
        let_min_term:
          type: string
          description: Optional. Lettings minimum term.
          example: 12+ months
        let_type_id:
          oneOf:
            - type: integer
            - type: string
          description: Optional. 1 Long term, 2 Short term, 3 Student, or matching label.
          example: 1
        let_furn_id:
          oneOf:
            - type: integer
            - type: string
          description: Optional. 1 Unfurnished, 2 Furnished, 3 Part Furnished, or matching label.
          example: 2
        size_min:
          type: number
          description: Optional. Minimum floor/land area for commercial or land.
          example: 1800
        size_max:
          type: number
          description: Optional. Maximum floor/land area for commercial or land.
          example: 1800
        area_size_unit_id:
          oneOf:
            - type: integer
            - type: string
          description: Optional. Area unit. 1/sqft/sq ft/square feet or 2/sqm/sq m/square metres.
          example: sqft
        price_per_unit:
          type: number
          description: Optional. Commercial or land price per area unit.
          example: 18
        floor_area:
          type: number
          description: Optional. Floor area value.
          example: 1200
        floor_area_unit:
          type: string
          description: Optional. Floor area unit.
          example: sqft
        plot_area:
          type: number
          description: Optional. Plot or land area value.
          example: 0.25
        plot_area_unit:
          type: string
          description: Optional. Plot area unit.
          example: acres
        summary:
          type: string
          description: Optional. Short description.
          example: Three-bedroom house near local amenities.
        description:
          type: string
          description: Optional. Full property description.
          example: A well-presented three-bedroom property with garden and off-street parking.
        features:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
          description: Optional. Pipe-delimited, double-pipe-delimited or array of feature strings.
          example: [Garden, Driveway, Close to station]
        image_0:
          type: string
          format: uri
          description: Optional. Legacy canonical image slot 0. Prefer media_image_00.
          example: https://cdn.example.com/properties/CRM-SALE-001/front.jpg
        image_1:
          type: string
          format: uri
          description: Optional. Legacy canonical image slot 1. image_0 through image_10 are supported.
        image_caption_0:
          type: string
          description: Optional. Caption for image_0. image_caption_0 through image_caption_10 are supported.
          example: Front elevation
        media_image_00:
          type: string
          format: uri
          description: Optional. Primary media image. media_image_00 through media_image_69 are supported.
          example: https://cdn.example.com/properties/CRM-SALE-001/front.jpg
        media_image_text_00:
          type: string
          description: Optional. Caption/label for media_image_00. media_image_text_00 through media_image_text_69 are supported.
          example: Front elevation
        media_floorplan_00:
          type: string
          format: uri
          description: Optional. Floorplan media. media_floorplan_00 through media_floorplan_19 are supported.
          example: https://cdn.example.com/properties/CRM-SALE-001/floorplan.jpg
        floorplan:
          type: string
          format: uri
          description: Optional. Legacy canonical floorplan field. Prefer media_floorplan_00.
          example: https://cdn.example.com/properties/CRM-SALE-001/floorplan.jpg
        epc_image:
          type: string
          format: uri
          description: Optional. EPC image URL or filename.
          example: https://cdn.example.com/properties/CRM-SALE-001/epc.jpg
        epc_pdf:
          type: string
          format: uri
          description: Optional. EPC PDF document URL or filename.
          example: https://cdn.example.com/properties/CRM-SALE-001/epc.pdf
        brochure_pdf:
          type: string
          format: uri
          description: Optional. Brochure PDF document URL or filename.
          example: https://cdn.example.com/properties/CRM-SALE-001/brochure.pdf
        media_document_00:
          type: string
          format: uri
          description: Optional. Generic document slot. media_document_00 through media_document_19 are supported.
          example: https://cdn.example.com/properties/CRM-SALE-001/brochure.pdf
        media_document_text_00:
          type: string
          description: Optional. Document label for media_document_00. media_document_text_00 through media_document_text_19 are supported.
          example: Brochure
        virtual_tour:
          type: string
          format: uri
          description: Optional legacy virtual tour alias. Prefer virtual_tour_url.
          example: https://tours.example.com/CRM-SALE-001
        virtual_tour_url:
          type: string
          format: uri
          description: Optional. HTTPS virtual tour URL. Matterport, EyeSpy360, YouTube, Vimeo, Kuula, configured Generic HTTPS domains and securely streamed uploaded MP4 tours are supported.
          example: https://my.matterport.com/show/?m=abc123
        virtual_tour_provider:
          type: string
          description: Optional. Detected automatically from virtual_tour_url.
          enum: [Matterport, EyeSpy360, YouTube, Vimeo, Kuula, Generic, Uploaded Video]
          example: Matterport
        virtual_tour_type:
          type: string
          enum: [iframe, uploaded_video]
          readOnly: true
          description: Runtime rendering type for the public modal.
        virtual_tour_enabled:
          type: boolean
          description: Whether the virtual tour should be displayed publicly.
          example: true
        video:
          type: string
          format: uri
          description: Optional. Video URL.
          example: https://video.example.com/CRM-SALE-001
        source_type:
          type: string
          readOnly: true
          description: Optional ownership metadata. Normally supplied by the authenticated connection, not clients.
          example: GENERIC_CRM
        feed_connection_id:
          type: integer
          readOnly: true
          description: Optional ownership metadata. Normally derived from authentication.
          example: 123
        external_property_id:
          type: string
          readOnly: true
          description: Optional ownership metadata mirroring external_ref when stored internally.
          example: CRM-SALE-001
        managed_mode:
          type: string
          readOnly: true
          description: Optional ownership mode metadata.
          enum: [CRM_MASTER, HYBRID, PORTAL_MASTER]
          example: CRM_MASTER
        last_sync_at:
          type: string
          format: date-time
          readOnly: true
          description: Optional sync metadata timestamp.
        sync_status:
          type: string
          readOnly: true
          description: Optional sync metadata status.
          example: OK
        sync_error:
          type: string
          nullable: true
          readOnly: true
          description: Optional sync metadata error.
        seen_in_run_id:
          type: integer
          nullable: true
          readOnly: true
          description: Optional feed run visibility marker.
        owner_id:
          type: string
          readOnly: true
          description: Optional ownership metadata.
        owner_type:
          type: string
          readOnly: true
          description: Optional ownership metadata.
        owner_name:
          type: string
          readOnly: true
          description: Optional ownership metadata.
        field_owner:
          type: string
          readOnly: true
          description: Optional field ownership metadata.
        field_owner_map:
          oneOf:
            - type: object
            - type: string
          readOnly: true
          description: Optional per-field ownership metadata map.
        ownership_source:
          type: string
          readOnly: true
          description: Optional field ownership source.
        last_seen_at:
          type: string
          format: date-time
          readOnly: true
          description: Optional last seen timestamp for sync visibility.
        source_updated_at:
          type: string
          format: date-time
          readOnly: true
          description: Optional source updated timestamp supplied by the provider.
        uprn:
          type: string
          description: Optional Unique Property Reference Number.
        what3words:
          type: string
          description: Optional What3Words address.
        portal_url:
          type: string
          format: uri
          description: Optional source portal URL.
        seo_title:
          type: string
          description: Optional SEO title.
        seo_description:
          type: string
          description: Optional SEO description.
        floor_level:
          type: string
          description: Optional floor level.
        rooms:
          oneOf:
            - type: array
              items:
                type: object
            - type: object
            - type: string
          description: Optional room data supplied by a CRM/feed.
        flood_risk:
          type: string
          description: Optional flood-risk note.
        material_information:
          type: string
          description: Optional material information summary.
      x-canonical-ranges:
        image: image_0 through image_10
        image_caption: image_caption_0 through image_caption_10
        media_image: media_image_00 through media_image_69
        media_image_text: media_image_text_00 through media_image_text_69
        media_floorplan: media_floorplan_00 through media_floorplan_19
        media_floorplan_text: media_floorplan_text_00 through media_floorplan_text_19
        media_document: media_document_00 through media_document_19
        media_document_text: media_document_text_00 through media_document_text_19
    SuccessResponse:
      type: object
      required: [success, request_id, external_ref, status, message]
      properties:
        success:
          type: boolean
          enum: [true]
        request_id:
          type: string
        external_ref:
          type: string
        status:
          type: string
          enum: [created, updated, withdrawn, deleted, archived, restored, reactivated, completed]
        message:
          type: string
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/ErrorDetail'
    SandboxPropertyResponse:
      allOf:
        - $ref: '#/components/schemas/SuccessResponse'
        - type: object
          properties:
            property:
              $ref: '#/components/schemas/Property'
    SandboxPropertyListResponse:
      type: object
      required: [success, data, pagination]
      properties:
        success:
          type: boolean
          enum: [true]
        data:
          type: array
          items:
            $ref: '#/components/schemas/Property'
        pagination:
          type: object
          properties:
            limit:
              type: integer
            offset:
              type: integer
            total:
              type: integer
            next_offset:
              type: integer
              nullable: true
    SandboxWebhookListResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          enum: [true]
        data:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
              event_type:
                type: string
                enum: [property.created, property.updated, property.withdrawn, property.archived, property.restored, property.reactivated, property.deleted]
              external_ref:
                type: string
              delivery_status:
                type: string
              attempts:
                type: integer
              payload_json:
                type: string
              created_at:
                type: string
                format: date-time
    ErrorResponse:
      type: object
      required: [success, error]
      properties:
        success:
          type: boolean
          enum: [false]
        error:
          $ref: '#/components/schemas/Error'
    Error:
      type: object
      required: [code, message, details, request_id, retryable]
      properties:
        code:
          type: string
          enum: [unauthorized, forbidden, not_found, method_not_allowed, validation_failed, duplicate_request, already_processing, rate_limited, payload_too_large, conflict, internal_error]
        message:
          type: string
        details:
          type: array
          items:
            $ref: '#/components/schemas/ErrorDetail'
        request_id:
          type: string
        retryable:
          type: boolean
    ErrorDetail:
      type: object
      properties:
        field:
          type: string
        message:
          type: string
    HealthResponse:
      type: object
      properties:
        success:
          type: boolean
        api_version:
          type: string
        service_status:
          type: string
        authentication_enabled:
          type: boolean
        current_time:
          type: string
          format: date-time
        supported_versions:
          type: array
          items:
            type: string
    CapabilitiesResponse:
      type: object
      properties:
        success:
          type: boolean
        api_version:
          type: string
        capabilities:
          type: object
          additionalProperties:
            type: boolean
        enums:
          type: object
        external_ref:
          type: object
    VersionResponse:
      type: object
      properties:
        success:
          type: boolean
        api_version:
          type: string
        build_version:
          type: string
        supported_features:
          type: array
          items:
            type: string
        deprecation_policy:
          type: object
