openapi: 3.1.0
info:
  title: Nimbus Document Purchase API
  description: |
    API service for purchasing Nimbus-provided documents, including land registry (HMLR) documents and other property-related documents.

    This API enables customers to:
    - Check document availability and pricing for a title number
    - View pending applications on the title
    - Purchase documents with token-based billing
    - Receive documents via webhook delivery

    ## Authentication
    This API uses OAuth 2.0 for authentication. Users must authenticate and authorize access to their account.

    OAuth 2.0 Flow:
    1. Direct users to the authorization endpoint to log in
    2. User grants permission to your application
    3. Receive an authorization code
    4. Exchange the code for an access token
    5. Include the access token in the `Authorization: Bearer <token>` header for all API requests

    Access tokens expire after 1 hour. Use the refresh token to obtain a new access token without requiring the user to log in again.

    ## Rate Limits
    - 60 requests per minute per user
    - 1,000 requests per hour per user

    ## Workflow
    1. Call `/check-availability` to see available documents and costs
    2. Call `/purchase` with the documents you want to buy
    3. Documents are delivered to your webhook URL when ready
    4. Optionally poll `/orders/{order_id}` to check status

  version: 1.0.0
  contact:
    name: Nimbus Support
    email: support@nimbusproperty.co.uk
  license:
    name: Proprietary
servers:
  - url: https://api.nimbusmaps.co.uk/docs/v1
    description: Production server
  - url: https://api-preprod.nimbusmaps.xyz/docs/v1
    description: Test server

tags:
  - name: Documents
    description: Document availability and purchase operations
  - name: Orders
    description: Order status and management
  - name: Health
    description: Service health checks

security:
  - OAuth2: []

paths:
  /check-availability:
    get:
      tags:
        - Documents
      summary: Check document availability and costs
      description: |
        Check which HMLR documents are available for a given title and the token cost for each.
        Also returns information about any pending applications on the title.

        You can query by either:
        - **title_number**: UK Land Registry title number (e.g., AB123456)
        - **title_id**: Internal UUID identifier for the title

        Exactly one of these parameters must be provided.

        This endpoint:
        - Queries HMLR for available documents
        - Queries HMLR for pending applications
        - Calculates token costs based on document types
        - Returns current account balance

        Results are cached for 5 minutes in Azure API Management.
      operationId: checkAvailability
      security:
        - OAuth2: [docs.read]
      parameters:
        - name: title_number
          in: query
          required: false
          description: UK Land Registry title number (e.g., AB123456). Either title_number or title_id must be provided.
          schema:
            type: string
            pattern: '^[A-Z]{1,3}[0-9]{1,7}$'
            minLength: 2
            maxLength: 10
          example: "AB123456"
        - name: title_id
          in: query
          required: false
          description: Internal UUID identifier for the title. Either title_number or title_id must be provided.
          schema:
            type: string
            format: uuid
          example: "0000a942-3cee-431d-bd71-d07cb27197ec"
      responses:
        '200':
          description: Document availability information retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AvailabilityCheckResponse'
              examples:
                immediateAvailable:
                  summary: Documents available for immediate download
                  value:
                    data:
                      title_status: "Title number is valid."
                      title_status_code: "VALID"
                      title_number: "AB123456"
                      pending_applications: []
                      referred_to_documents: []
                      register:
                        type: "Register"
                        type_code: "REGISTER"
                        availability: "available for immediate download"
                        availability_code: "IMMEDIATE"
                        backdated: false
                        token_cost: 7
                        previously_purchased: null
                      title_plan:
                        type: "Plan"
                        type_code: "TITLEPLAN"
                        availability: "available for immediate download"
                        availability_code: "IMMEDIATE"
                        backdated: false
                        token_cost: 7
                        previously_purchased: null
                    total_token_cost_estimate: 14
                    current_balance: 150
                withPendingApplications:
                  summary: Title with pending applications
                  value:
                    data:
                      title_status: "There is at least one pending application for registration against this title number. You may want to view the day list."
                      title_status_code: "PENDING_APPLICATIONS"
                      title_number: "CD789012"
                      pending_applications:
                        - application_type: "50"
                          application_type_description: "Dispositionary First Lease (Old Title)"
                          expedited: "false"
                          application_reference: "FA8480F"
                          priority_date: "2025-12-29"
                          priority_time: "06:08:45"
                          application_progress: "Received: Priority Protected"
                          customer_reference: "VH/ID57587"
                          application_received_by: "20"
                          lodged_by: "SPENCER WEST LLP\nLONGBOW HOUSE\n20 CHISWELL STREET\nLONDON\nEC1Y 4TW"
                        - application_type: "30"
                          application_type_description: "Dealing"
                          expedited: "false"
                          application_reference: "N629ZCV"
                          priority_date: "2025-11-24"
                          priority_time: "12:26:21"
                          application_progress: "Received: Priority Protected - Awaiting Processing"
                          customer_reference: "ZEL/4477"
                          application_received_by: "20"
                          lodged_by: "JOWETT KENNEDY FIDLER LLP\n2 THE EMBANKMENT\nSOVEREIGN STREET\nLEEDS\nLS1 4BA"
                        - application_type: "30"
                          application_type_description: "Dealing"
                          expedited: "true"
                          application_reference: "S531VST"
                          priority_date: "2025-02-06"
                          priority_time: "11:04:07"
                          application_progress: "Delayed - Prior Pending Application(s) or Official Search(es)"
                          customer_reference: "199706.00023/JOJA/HNDR"
                          application_received_by: "20"
                          lodged_by: "CMS CAMERON MCKENNA NABARRO OLSW\n1-3 CHARTER SQUARE\nSHEFFIELD\nS1 4HS"
                      referred_to_documents: []
                      register:
                        type: "Register"
                        type_code: "REGISTER"
                        availability: "available for immediate download"
                        availability_code: "IMMEDIATE"
                        backdated: true
                        token_cost: 7
                        previously_purchased: "2025-12-15T10:30:00Z"
                      title_plan:
                        type: "Plan"
                        type_code: "TITLEPLAN"
                        availability: "available for immediate download"
                        availability_code: "IMMEDIATE"
                        backdated: true
                        token_cost: 7
                        previously_purchased: null
                    total_token_cost_estimate: 14
                    current_balance: 150
                withReferredDocuments:
                  summary: Title with referred documents (e.g., deeds, charges)
                  value:
                    data:
                      title_status: "Title number is valid."
                      title_status_code: "VALID"
                      title_number: "EF345678"
                      pending_applications: []
                      referred_to_documents:
                        - type: "Charge"
                          type_code: "CHARGE"
                          entry_numbers: ["C1"]
                          date: "2020-12-01"
                          filed_under: "EF345678"
                          availability: "available for immediate download"
                          availability_code: "IMMEDIATE"
                          plan_only: false
                          token_cost: 7
                          previously_purchased: "2025-11-20T14:22:00Z"
                        - type: "Abstract"
                          type_code: "ABSTRACT"
                          entry_numbers: ["A1"]
                          date: "2019-03-15"
                          filed_under: null
                          availability: "requires further investigation"
                          availability_code: "MANUAL"
                          plan_only: true
                          token_cost: 7
                          previously_purchased: null
                      register:
                        type: "Register"
                        type_code: "REGISTER"
                        availability: "available for immediate download"
                        availability_code: "IMMEDIATE"
                        backdated: false
                        token_cost: 7
                        previously_purchased: null
                      title_plan:
                        type: "Plan"
                        type_code: "TITLEPLAN"
                        availability: "available for immediate download"
                        availability_code: "IMMEDIATE"
                        backdated: false
                        token_cost: 7
                        previously_purchased: null
                    total_token_cost_estimate: 28
                    current_balance: 150
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Title not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                titleNumberNotFound:
                  summary: Title number not found in HMLR registry
                  value:
                    error:
                      code: "NOT_FOUND"
                      message: "Title number not found"
                titleIdNotFound:
                  summary: Title ID not found in database
                  value:
                    error:
                      code: "TITLE_ID_NOT_FOUND"
                      message: "Title ID not found"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '502':
          $ref: '#/components/responses/BadGateway'

  /purchase:
    post:
      tags:
        - Documents
      summary: Purchase documents
      description: |
        Purchase one or more documents for a title.

        This endpoint:
        - Validates document availability with the document provider
        - Checks account balance
        - Deducts tokens from your account via the Ledger API
        - Queues a background job to order and deliver documents
        - Returns immediately with an order ID

        Documents are delivered asynchronously to your webhook URL when ready.
        Use the webhook_secret to verify webhook authenticity via HMAC signature.
      operationId: purchaseDocuments
      security:
        - OAuth2: [docs.purchase]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PurchaseRequest'
            examples:
              standardPurchase:
                summary: Purchase register and title plan
                value:
                  title_number: "AB123456"
                  documents:
                    - "register"
                    - "title_plan"
                  webhook_url: "https://customer.example.com/webhooks/documents"
                  webhook_secret: "whsec_abc123def456"
                  customer_reference: "CUST-ORDER-12345"
      responses:
        '201':
          description: Order created successfully and processing started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PurchaseResponse'
              examples:
                success:
                  summary: Successful purchase
                  value:
                    order_id: "770e8400-e29b-41d4-a716-446655440002"
                    status: "PROCESSING"
                    title_number: "AB123456"
                    documents_ordered:
                      - "register"
                      - "title_plan"
                    total_tokens_charged: 14
                    new_balance: 136
                    estimated_delivery: "2026-01-15T14:35:00Z"
                    webhook_url: "https://customer.example.com/webhooks/documents"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '409':
          description: |-
            Duplicate order in progress.

            This occurs when ANY of the requested documents are already being processed in another order
            (status: PENDING_PAYMENT or PROCESSING) for the same title.

            The response will indicate which documents are in progress and in which order(s).
            You should either:
            - Wait for the existing order(s) to complete, then order again
            - Modify your request to exclude the documents that are in progress

            **Note:** Documents that were previously purchased and delivered CAN be purchased again,
            as they may be out of date. The 'previously_purchased' field in the availability check
            is informational only to help you decide, but does not prevent repurchasing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                allDocumentsInProgress:
                  summary: All requested documents in progress
                  value:
                    error:
                      code: "DOCUMENTS_IN_PROGRESS"
                      message: "One or more requested documents are already being processed"
                      details:
                        documents_in_progress:
                          - document_id: "register"
                            order_id: "770e8400-e29b-41d4-a716-446655440002"
                            order_status: "PROCESSING"
                          - document_id: "title_plan"
                            order_id: "770e8400-e29b-41d4-a716-446655440002"
                            order_status: "PROCESSING"
                someDocumentsInProgress:
                  summary: Partial conflict - some documents in progress
                  value:
                    error:
                      code: "DOCUMENTS_IN_PROGRESS"
                      message: "One or more requested documents are already being processed"
                      details:
                        documents_in_progress:
                          - document_id: "register"
                            order_id: "770e8400-e29b-41d4-a716-446655440002"
                            order_status: "PROCESSING"
                        documents_available:
                          - "title_plan"
                          - "ref_doc_001"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '502':
          $ref: '#/components/responses/BadGateway'

  /orders/{order_id}:
    get:
      tags:
        - Orders
      summary: Get order status
      description: |
        Retrieve the current status of a document order.

        Order statuses:
        - `PENDING_PAYMENT`: Payment is being processed
        - `PROCESSING`: Documents are being ordered from the provider
        - `COMPLETED`: Documents have been delivered to webhook
        - `FAILED`: Order failed (see error_message)
        - `WEBHOOK_FAILED`: Documents ready but webhook delivery failed
      operationId: getOrderStatus
      security:
        - OAuth2: [docs.read]
      parameters:
        - name: order_id
          in: path
          required: true
          description: The unique identifier of the order
          schema:
            type: string
            format: uuid
          example: "770e8400-e29b-41d4-a716-446655440002"
      responses:
        '200':
          description: Order status retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderStatusResponse'
              examples:
                completed:
                  summary: Completed order
                  value:
                    order_id: "770e8400-e29b-41d4-a716-446655440002"
                    status: "COMPLETED"
                    title_number: "AB123456"
                    customer_reference: "CUST-ORDER-12345"
                    documents_ordered:
                      - "register"
                      - "title_plan"
                    documents_delivered:
                      - document_type_id: "register"
                        document_id: "990e8400-e29b-41d4-a716-446655440003"
                        status: "DELIVERED"
                      - document_type_id: "title_plan"
                        document_id: "991e8400-e29b-41d4-a716-446655440004"
                        status: "DELIVERED"
                    total_tokens_charged: 14
                    webhook_delivery_status: "SUCCESS"
                    webhook_delivery_attempts: 1
                    created_at: "2026-01-15T14:30:00Z"
                    completed_at: "2026-01-15T14:32:15Z"
                processing:
                  summary: Order still processing
                  value:
                    order_id: "770e8400-e29b-41d4-a716-446655440002"
                    status: "PROCESSING"
                    title_number: "AB123456"
                    customer_reference: "CUST-ORDER-12345"
                    documents_ordered:
                      - "register"
                      - "title_plan"
                    documents_delivered:
                      - document_type_id: "register"
                        status: "PENDING"
                      - document_type_id: "title_plan"
                        status: "PENDING"
                    total_tokens_charged: 14
                    webhook_delivery_status: null
                    webhook_delivery_attempts: 0
                    created_at: "2026-01-15T14:30:00Z"
                    completed_at: null
                webhookFailed:
                  summary: Documents ready but webhook failed
                  value:
                    order_id: "770e8400-e29b-41d4-a716-446655440002"
                    status: "WEBHOOK_FAILED"
                    title_number: "AB123456"
                    customer_reference: "CUST-ORDER-12345"
                    documents_ordered:
                      - "register"
                      - "title_plan"
                    documents_delivered:
                      - document_type_id: "register"
                        document_id: "990e8400-e29b-41d4-a716-446655440003"
                        status: "DOWNLOADED"
                      - document_type_id: "title_plan"
                        document_id: "991e8400-e29b-41d4-a716-446655440004"
                        status: "DOWNLOADED"
                    total_tokens_charged: 14
                    webhook_delivery_status: "FAILED"
                    webhook_delivery_attempts: 4
                    error_message: "Webhook delivery failed after 4 attempts: Connection timeout"
                    created_at: "2026-01-15T14:30:00Z"
                    completed_at: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Order not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: "ORDER_NOT_FOUND"
                  message: "The specified order was not found"
                  details:
                    order_id: "770e8400-e29b-41d4-a716-446655440002"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /download/{document_id}:
    get:
      tags:
        - Documents
      summary: Download a purchased document
      description: |
        Download a document from a completed order using its unique document ID.

        This endpoint allows you to:
        - Re-download documents that were previously delivered via webhook
        - Download documents when webhook delivery failed
        - Access documents from past orders

        **No additional charge** - You've already paid for the document.

        Documents are stored indefinitely so you can always re-download your previously purchased documents.

        **Getting the document_id:** The unique document ID is provided in the order status response
        after the document has been downloaded. Use the `document_id` field (UUID) from the DocumentDeliveryStatus.
      operationId: downloadDocument
      security:
        - OAuth2: [docs.read]
      parameters:
        - name: document_id
          in: path
          required: true
          description: The unique document ID for the document (UUID from order status response)
          schema:
            type: string
            format: uuid
          example: "990e8400-e29b-41d4-a716-446655440003"
      responses:
        '200':
          description: Document retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentDownloadResponse'
              examples:
                pdfDocument:
                  summary: PDF document download
                  value:
                    document_id: "990e8400-e29b-41d4-a716-446655440003"
                    order_id: "770e8400-e29b-41d4-a716-446655440002"
                    document_type_code: "REGISTER"
                    document_type: "Register"
                    title_number: "AB123456"
                    format: "pdf"
                    content: "JVBERi0xLjQKJeLjz9MK..."
                    metadata:
                      issue_date: "2026-01-15"
                      pages: 4
                    downloaded_at: "2026-01-15T14:32:15Z"
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Order or document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                documentNotFound:
                  summary: Document not found
                  value:
                    error:
                      code: "DOCUMENT_NOT_FOUND"
                      message: "The specified document was not found or you don't have access to it"
                      details:
                        document_id: "990e8400-e29b-41d4-a716-446655440003"
        '422':
          description: Document not yet ready for download
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: "DOCUMENT_NOT_READY"
                  message: "The document is still being processed and is not yet available for download"
                  details:
                    document_id: "990e8400-e29b-41d4-a716-446655440003"
                    status: "ORDERED"
                    estimated_ready: "2026-01-15T14:35:00Z"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /ownership:
    post:
      tags:
        - Documents
      summary: Get registered owner of a title
      description: |
        Retrieve the registered owner(s) of a property title using HMLR's Online Owner Verification (OOV) service.

        This endpoint:
        - Queries HMLR for the registered owner(s) of a title by title number
        - Returns complete ownership information for all registered proprietors
        - Useful for due diligence, pre-transaction checks, and KYC processes

      operationId: getOwnership
      security:
        - OAuth2: [docs.purchase]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OwnershipQueryRequest'
            examples:
              standard:
                summary: Query ownership by title number
                value:
                  title_number: "AB123456"
                  customer_reference: "OWNER-001"
      responses:
        '200':
          description: Ownership information retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OwnershipQueryResponse'
              examples:
                singleOwner:
                  summary: Single individual owner
                  value:
                    query_id: "OWN-123456-2026"
                    title_number: "AB123456"
                    registered_owners:
                      - first_forename: "John"
                        middle_name: null
                        surname: "Smith"
                        proprietor_type: "Private Individual"
                    customer_reference: "OWNER-001"
                    retrieved_at: "2026-01-18T14:30:00Z"
                singleOwnerWithMiddleName:
                  summary: Single owner with middle name
                  value:
                    query_id: "OWN-123457-2026"
                    title_number: "CD789012"
                    registered_owners:
                      - first_forename: "Jane"
                        middle_name: "Marie"
                        surname: "Doe"
                        proprietor_type: "Private Individual"
                    customer_reference: "OWNER-002"
                    retrieved_at: "2026-01-18T14:31:00Z"
                multipleOwners:
                  summary: Property with multiple registered owners
                  value:
                    query_id: "OWN-123458-2026"
                    title_number: "EF345678"
                    registered_owners:
                      - first_forename: "John"
                        middle_name: null
                        surname: "Smith"
                        proprietor_type: "Private Individual"
                      - first_forename: "Sarah"
                        middle_name: "Anne"
                        surname: "Smith"
                        proprietor_type: "Private Individual"
                    customer_reference: "OWNER-003"
                    retrieved_at: "2026-01-18T14:32:00Z"
                companyOwner:
                  summary: Company as registered owner
                  value:
                    query_id: "OWN-123459-2026"
                    title_number: "GH123456"
                    registered_owners:
                      - company_name: "Example Property Holdings Ltd"
                        proprietor_type: "Limited Company"
                        company_number: "12345678"
                    customer_reference: "OWNER-004"
                    retrieved_at: "2026-01-18T14:33:00Z"
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Title not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: "TITLE_NOT_FOUND"
                  message: "The specified title number was not found"
                  details:
                    title_number: "ZZ999999"
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '502':
          $ref: '#/components/responses/BadGateway'

  /health:
    get:
      tags:
        - Health
      summary: Health check endpoint
      description: |
        Check the health status of the API service and its dependencies.

        Returns 200 if all systems are operational.
        Returns 503 if any critical dependency is unavailable.
      operationId: healthCheck
      security: []  # No authentication required
      responses:
        '200':
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: "healthy"
                timestamp: "2026-01-15T14:30:00Z"
                checks:
                  database: "ok"
                  redis: "ok"
                  document_provider: "ok"
                  ledger_api: "ok"
        '503':
          description: Service is unhealthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: "unhealthy"
                timestamp: "2026-01-15T14:30:00Z"
                checks:
                  database: "ok"
                  redis: "ok"
                  document_provider: "error"
                  ledger_api: "ok"

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      description: |
        OAuth 2.0 authentication for individual user access.

        Users must authenticate with their credentials and grant authorization to your application.
        The access token must be included in the Authorization header as a Bearer token.
      flows:
        authorizationCode:
          authorizationUrl: https://auth.nimbusmaps.co.uk/oauth2/v2.0/authorize
          tokenUrl: https://auth.nimbusmaps.co.uk/oauth2/v2.0/token
          refreshUrl: https://auth.nimbusmaps.co.uk/oauth2/v2.0/token
          scopes:
            docs.read: Read document availability and order information
            docs.purchase: Purchase documents and check ownership

  schemas:
    AvailabilityCheckRequest:
      type: object
      properties:
        title_number:
          type: string
          description: UK Land Registry title number. Either title_number or title_id must be provided.
          pattern: '^[A-Z]{1,3}[0-9]{1,7}$'
          examples:
            - "AB123456"
          minLength: 2
          maxLength: 10
        title_id:
          type: string
          format: uuid
          description: Internal UUID identifier for the title. Either title_number or title_id must be provided.
          examples:
            - "0000a942-3cee-431d-bd71-d07cb27197ec"
      oneOf:
        - required: [title_number]
        - required: [title_id]

    AvailabilityCheckResponse:
      type: object
      required:
        - data
        - total_token_cost_estimate
        - current_balance
      properties:
        data:
          $ref: '#/components/schemas/AvailabilityCheckData'
        total_token_cost_estimate:
          type: integer
          description: Total cost in tokens if all documents are purchased
          examples:
            - 21
          minimum: 0
        current_balance:
          type: integer
          description: Current token balance for this account
          examples:
            - 150
          minimum: 0

    AvailabilityCheckData:
      type: object
      required:
        - title_status
        - title_status_code
        - title_number
        - pending_applications
        - referred_to_documents
        - register
        - title_plan
      properties:
        title_status:
          type: string
          description: Human-readable status message from HMLR
          examples:
            - "Title number is valid."
        title_status_code:
          type: string
          enum:
            - VALID
            - PENDING_APPLICATIONS
            - CLOSED
            - INVALID
            - CLOSED_AND_CONTINUED
            - NOT_COMPUTERISED
            - PENDING_DEALING
            - PENDING_FIRST
            - SCHEME
          description: Status code from HMLR
          examples:
            - "VALID"
        title_number:
          type: string
          description: The title number that was checked
          examples:
            - "AB123456"
        pending_applications:
          type: array
          description: List of pending applications from HMLR Application Enquiry API
          items:
            $ref: '#/components/schemas/PendingApplication'
        referred_to_documents:
          type: array
          description: List of referred documents (deeds, charges, leases, etc.)
          items:
            $ref: '#/components/schemas/ReferredDocument'
        register:
          $ref: '#/components/schemas/StandardDocument'
        title_plan:
          $ref: '#/components/schemas/StandardDocument'

    StandardDocument:
      type: object
      required:
        - type
        - type_code
        - availability
        - availability_code
        - backdated
        - token_cost
        - previously_purchased
      properties:
        type:
          type: string
          enum: [Register, Plan]
          description: Document type name
          examples:
            - "Register"
            - "Plan"
        type_code:
          type: string
          description: Document type code
          examples:
            - "REGISTER"
            - "TITLEPLAN"
        availability:
          type: string
          description: Availability status message from HMLR
          examples:
            - "available for immediate download"
        availability_code:
          type: string
          enum: [IMMEDIATE, MANUAL, UNAVAILABLE]
          description: |
            Availability status code:
            - IMMEDIATE: Available for immediate download
            - MANUAL: Requires manual processing/further investigation
            - UNAVAILABLE: Not available
          examples:
            - "IMMEDIATE"
        backdated:
          type: boolean
          description: Whether the document is backdated (due to pending applications)
          examples:
            - false
        token_cost:
          type: integer
          description: Cost in tokens for this document
          examples:
            - 7
          minimum: 0
        previously_purchased:
          type: string
          format: date-time
          nullable: true
          description: |-
            ISO 8601 timestamp when this customer previously purchased this document, or null if never purchased.
            This is informational only - you can still purchase the document again as it may be out of date.
          examples:
            - null

    ReferredDocument:
      type: object
      required:
        - type
        - type_code
        - entry_numbers
        - availability
        - availability_code
        - plan_only
        - token_cost
        - previously_purchased
      properties:
        type:
          type: string
          description: Human-readable document type (e.g., "Charge", "Deed", "Lease")
          examples:
            - "Charge"
        type_code:
          type: string
          description: Document type code
          examples:
            - "CHARGE"
        entry_numbers:
          type: array
          description: Register entry numbers where this document is referred to
          items:
            type: string
          examples:
            - ["C1"]
        date:
          type: string
          format: date
          nullable: true
          description: Date of the referred document
          examples:
            - "2020-12-01"
        filed_under:
          type: string
          nullable: true
          description: Title number under which the document is filed
          pattern: '^[A-Z0-9]{1,9}$'
          examples:
            - "AB123456"
        availability:
          type: string
          description: Availability status message from HMLR
          examples:
            - "available for immediate download"
        availability_code:
          type: string
          enum: [IMMEDIATE, MANUAL, UNAVAILABLE]
          description: |
            Availability status code:
            - IMMEDIATE: Available for immediate download
            - MANUAL: Requires manual processing/further investigation
            - UNAVAILABLE: Not available
          examples:
            - "IMMEDIATE"
        plan_only:
          type: boolean
          description: Whether the document contains only a plan, not the complete document
          examples:
            - false
        token_cost:
          type: integer
          description: Cost in tokens for this document
          examples:
            - 7
          minimum: 0
        previously_purchased:
          type: string
          format: date-time
          nullable: true
          description: |-
            ISO 8601 timestamp when this customer previously purchased this document, or null if never purchased.
            This is informational only - you can still purchase the document again as it may be out of date.
          examples:
            - "2025-11-20T14:22:00Z"

    PendingApplication:
      type: object
      required:
        - application_type
        - application_type_description
        - expedited
        - application_reference
        - priority_date
        - priority_time
        - application_progress
        - customer_reference
        - application_received_by
        - lodged_by
      properties:
        application_type:
          type: string
          description: HMLR application type code (e.g., "30", "50")
          examples:
            - "50"
        application_type_description:
          type: string
          description: Human-readable description of the application type
          examples:
            - "Dispositionary First Lease (Old Title)"
        expedited:
          type: string
          description: Whether the application is expedited ("true" or "false" as string)
          enum: ["true", "false"]
          examples:
            - "false"
        application_reference:
          type: string
          description: HMLR application reference number
          examples:
            - "FA8480F"
        priority_date:
          type: string
          format: date
          description: Priority date of the application (YYYY-MM-DD)
          examples:
            - "2025-12-29"
        priority_time:
          type: string
          description: Priority time of the application (HH:MM:SS)
          pattern: '^\d{2}:\d{2}:\d{2}$'
          examples:
            - "06:08:45"
        application_progress:
          type: string
          description: Current progress status of the application
          examples:
            - "Received: Priority Protected"
        customer_reference:
          type: string
          description: Customer's reference for the application
          examples:
            - "VH/ID57587"
        application_received_by:
          type: string
          description: How the application was received (e.g., "10" for postal, "20" for electronic, "30" for fax)
          examples:
            - "20"
        lodged_by:
          type: string
          description: Name and address of the party who lodged the application (may contain newlines)
          examples:
            - "SPENCER WEST LLP\nLONGBOW HOUSE\n20 CHISWELL STREET\nLONDON\nEC1Y 4TW"

    PurchaseRequest:
      type: object
      required:
        - title_number
        - documents
        - webhook_url
      properties:
        title_number:
          type: string
          description: UK Land Registry title number
          pattern: '^[A-Z]{1,3}[0-9]{1,7}$'
          examples:
            - "AB123456"
        documents:
          type: array
          description: List of document IDs to purchase
          items:
            type: string
          minItems: 1
          examples:
            - ["register", "title_plan"]
        webhook_url:
          type: string
          format: uri
          description: HTTPS URL to receive documents when ready
          pattern: '^https://'
          examples:
            - "https://customer.example.com/webhooks/documents"
        webhook_secret:
          type: string
          description: Secret for HMAC signature verification (recommended)
          examples:
            - "whsec_abc123def456"
          minLength: 16
          maxLength: 255
        customer_reference:
          type: string
          description: Your internal reference for this order
          examples:
            - "CUST-ORDER-12345"
          maxLength: 255

    PurchaseResponse:
      type: object
      required:
        - order_id
        - status
        - title_number
        - documents_ordered
        - total_tokens_charged
        - new_balance
        - webhook_url
      properties:
        order_id:
          type: string
          format: uuid
          description: Unique identifier for this order
          examples:
            - "770e8400-e29b-41d4-a716-446655440002"
        status:
          type: string
          enum: [PENDING_PAYMENT, PROCESSING]
          description: Current order status
          examples:
            - "PROCESSING"
        title_number:
          type: string
          description: The title number for this order
          examples:
            - "AB123456"
        documents_ordered:
          type: array
          description: List of document IDs that will be ordered and delivered
          items:
            type: string
          examples:
            - ["register", "title_plan"]
        total_tokens_charged:
          type: integer
          description: Total tokens deducted from account (only for documents_ordered, not excluded ones)
          examples:
            - 14
          minimum: 0
        new_balance:
          type: integer
          description: New account balance after charge
          examples:
            - 144
          minimum: 0
        estimated_delivery:
          type: string
          format: date-time
          description: Estimated time when documents will be delivered
          examples:
            - "2026-01-15T14:35:00Z"
        webhook_url:
          type: string
          format: uri
          description: URL where documents will be delivered
          examples:
            - "https://customer.example.com/webhooks/documents"

    OrderStatusResponse:
      type: object
      required:
        - order_id
        - status
        - title_number
        - documents_ordered
        - documents_delivered
        - total_tokens_charged
        - webhook_delivery_attempts
        - created_at
      properties:
        order_id:
          type: string
          format: uuid
          description: Unique identifier for this order
          examples:
            - "770e8400-e29b-41d4-a716-446655440002"
        status:
          type: string
          enum: [PENDING_PAYMENT, PROCESSING, COMPLETED, FAILED, WEBHOOK_FAILED]
          description: Current order status
          examples:
            - "COMPLETED"
        title_number:
          type: string
          description: The title number for this order
          examples:
            - "AB123456"
        customer_reference:
          type: string
          description: Customer's reference for this order
          examples:
            - "CUST-ORDER-12345"
        documents_ordered:
          type: array
          description: List of document IDs in this order
          items:
            type: string
          examples:
            - ["register", "title_plan"]
        documents_delivered:
          type: array
          description: Delivery status of each document
          items:
            $ref: '#/components/schemas/DocumentDeliveryStatus'
        total_tokens_charged:
          type: integer
          description: Total tokens charged for this order
          examples:
            - 6
          minimum: 0
        webhook_delivery_status:
          type: string
          enum: [SUCCESS, FAILED, PENDING]
          nullable: true
          description: Status of webhook delivery
          examples:
            - "SUCCESS"
        webhook_delivery_attempts:
          type: integer
          description: Number of webhook delivery attempts made
          examples:
            - 1
          minimum: 0
        error_message:
          type: string
          description: Error message if order failed
          examples:
            - "Document provider service unavailable"
        created_at:
          type: string
          format: date-time
          description: When the order was created
          examples:
            - "2026-01-15T14:30:00Z"
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: When the order was completed
          examples:
            - "2026-01-15T14:32:15Z"

    DocumentDeliveryStatus:
      type: object
      required:
        - document_type_id
        - status
      properties:
        document_type_id:
          type: string
          description: Document type identifier (e.g., "register", "title_plan", "ref_doc_001")
          examples:
            - "register"
        document_id:
          type: string
          format: uuid
          nullable: true
          description: Unique document ID for this document. Available once status is DOWNLOADED or DELIVERED. Use this ID with the download endpoint.
          examples:
            - "990e8400-e29b-41d4-a716-446655440003"
        status:
          type: string
          enum: [PENDING, ORDERED, DOWNLOADED, DELIVERED, FAILED]
          description: Current status of this document
          examples:
            - "DELIVERED"

    DocumentDownloadResponse:
      type: object
      required:
        - document_id
        - order_id
        - document_type_code
        - document_type
        - title_number
        - format
        - content
        - downloaded_at
      properties:
        document_id:
          type: string
          format: uuid
          description: Unique document identifier for this document
          examples:
            - "990e8400-e29b-41d4-a716-446655440003"
        order_id:
          type: string
          format: uuid
          description: The order ID this document belongs to
          examples:
            - "770e8400-e29b-41d4-a716-446655440002"
        document_type_code:
          type: string
          description: Document type code (REGISTER, TITLEPLAN, CHARGE, etc.)
          examples:
            - "REGISTER"
        document_type:
          type: string
          description: Human-readable document type
          examples:
            - "Register"
        title_number:
          type: string
          description: The title number this document relates to
          examples:
            - "AB123456"
        format:
          type: string
          enum: [pdf, xml]
          description: Document format
          examples:
            - "pdf"
        content:
          type: string
          format: byte
          description: Base64-encoded document content
          examples:
            - "JVBERi0xLjQKJeLjz9MK..."
        metadata:
          type: object
          description: Additional document metadata
          properties:
            issue_date:
              type: string
              format: date
              description: Document issue date from the document provider
              examples:
                - "2026-01-15"
            pages:
              type: integer
              description: Number of pages (for PDF documents)
              examples:
                - 4
            scale:
              type: string
              description: Map scale (for title plans)
              examples:
                - "1:1250"
            file_size_bytes:
              type: integer
              description: Size of the document in bytes
              examples:
                - 245680
        downloaded_at:
          type: string
          format: date-time
          description: When the document was originally downloaded from the document provider
          examples:
            - "2026-01-15T14:32:15Z"

    OwnershipQueryRequest:
      type: object
      required:
        - title_number
      properties:
        title_number:
          type: string
          description: UK Land Registry title number
          pattern: '^[A-Za-yY]{0,3}\d{1,6}|Z\d{1,6}Z$'
          examples:
            - "AB123456"
          minLength: 1
          maxLength: 9
        customer_reference:
          type: string
          description: Your reference for tracking this ownership query
          pattern: '^[A-Za-z0-9\s~!"@#$%''()*+,\-./:;=>?\[\\\]_\{\}\^£&]*$'
          maxLength: 25
          examples:
            - "OWNER-001"

    OwnershipQueryResponse:
      type: object
      required:
        - query_id
        - title_number
        - registered_owners
        - retrieved_at
      properties:
        query_id:
          type: string
          description: Unique identifier for this ownership query
          examples:
            - "OWN-123456-2026"
        title_number:
          type: string
          description: The title number that was queried
          examples:
            - "AB123456"
        registered_owners:
          type: array
          description: List of registered proprietors (owners) for this title
          items:
            type: object
            properties:
              first_forename:
                type: string
                nullable: true
                description: First forename (null for companies)
                examples:
                  - "John"
              middle_name:
                type: string
                nullable: true
                description: Middle name(s)
                examples:
                  - "Paul"
              surname:
                type: string
                nullable: true
                description: Surname (null for companies)
                examples:
                  - "Smith"
              company_name:
                type: string
                nullable: true
                description: Company name if proprietor is a company
                examples:
                  - "Example Property Holdings Ltd"
              proprietor_type:
                type: string
                enum: [Private Individual, Limited Company, Overseas Company, Other]
                description: Type of proprietor
                examples:
                  - "Private Individual"
              company_number:
                type: string
                nullable: true
                description: Companies House registration number (if applicable)
                examples:
                  - "12345678"
        customer_reference:
          type: string
          nullable: true
          description: Your reference that was provided in the request
          examples:
            - "OWNER-001"
        retrieved_at:
          type: string
          format: date-time
          description: When the ownership information was retrieved
          examples:
            - "2026-01-18T14:30:00Z"

    HealthResponse:
      type: object
      required:
        - status
        - timestamp
        - checks
      properties:
        status:
          type: string
          enum: [healthy, unhealthy]
          description: Overall health status
          examples:
            - "healthy"
        timestamp:
          type: string
          format: date-time
          description: Timestamp of the health check
          examples:
            - "2026-01-15T14:30:00Z"
        checks:
          type: object
          description: Health status of individual dependencies
          properties:
            database:
              type: string
              enum: [ok, error]
              examples:
                - "ok"
            redis:
              type: string
              enum: [ok, error]
              examples:
                - "ok"
            document_provider:
              type: string
              enum: [ok, error]
              examples:
                - "ok"
            ledger_api:
              type: string
              enum: [ok, error]
              examples:
                - "ok"

    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code
              examples:
                - "INSUFFICIENT_BALANCE"
            message:
              type: string
              description: Human-readable error message
              examples:
                - "Insufficient balance to complete purchase"
            details:
              type: object
              description: Additional error details
              additionalProperties: true
              examples:
                - required: 10
                  available: 5
            request_id:
              type: string
              format: uuid
              description: Request ID for tracking
              examples:
                - "550e8400-e29b-41d4-a716-446655440000"

    WebhookPayload:
      type: object
      required:
        - order_id
        - title_number
        - status
        - documents
        - delivered_at
      description: |
        Payload sent to customer webhook URL when documents are ready.

        The X-Webhook-Signature header contains an HMAC-SHA256 signature:
        `sha256=<hex_digest>`

        Verify by computing HMAC-SHA256 of the request body using your webhook_secret.
      properties:
        order_id:
          type: string
          format: uuid
          description: Order identifier
          examples:
            - "770e8400-e29b-41d4-a716-446655440002"
        title_number:
          type: string
          description: Title number
          examples:
            - "AB123456"
        customer_reference:
          type: string
          description: Your reference from the purchase request
          examples:
            - "CUST-ORDER-12345"
        status:
          type: string
          enum: [COMPLETED, PARTIALLY_COMPLETED, FAILED]
          description: Overall delivery status
          examples:
            - "COMPLETED"
        documents:
          type: array
          description: Delivered documents
          items:
            $ref: '#/components/schemas/WebhookDocument'
        delivered_at:
          type: string
          format: date-time
          description: Timestamp of delivery
          examples:
            - "2026-01-15T14:32:15Z"

    WebhookDocument:
      type: object
      required:
        - document_id
        - document_type
        - format
        - content
      properties:
        document_id:
          type: string
          description: Document identifier
          examples:
            - "register"
        document_type:
          type: string
          description: Document type
          examples:
            - "Register"
        format:
          type: string
          enum: [pdf, xml]
          description: Document format
          examples:
            - "pdf"
        content:
          type: string
          format: byte
          description: Base64-encoded document content
          examples:
            - "JVBERi0xLjQKJeLjz9MK..."
        metadata:
          type: object
          description: Additional document metadata
          properties:
            issue_date:
              type: string
              format: date
              description: Document issue date
              examples:
                - "2026-01-15"
            pages:
              type: integer
              description: Number of pages (for PDF)
              examples:
                - 4
            scale:
              type: string
              description: Map scale (for title plans)
              examples:
                - "1:1250"

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "INVALID_REQUEST"
              message: "Invalid title number format"
              details:
                field: "title_number"
                value: "INVALID"
                pattern: "^[A-Z]{1,3}[0-9]{1,7}$"

    Unauthorized:
      description: Authentication failed - invalid, expired, or missing access token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missingToken:
              summary: Missing access token
              value:
                error:
                  code: "UNAUTHORIZED"
                  message: "Missing or invalid Authorization header. Include 'Authorization: Bearer <access_token>' in your request."
            expiredToken:
              summary: Expired access token
              value:
                error:
                  code: "TOKEN_EXPIRED"
                  message: "Access token has expired. Please refresh your token or re-authenticate."
            invalidToken:
              summary: Invalid access token
              value:
                error:
                  code: "INVALID_TOKEN"
                  message: "Access token is invalid or malformed."
            insufficientScopes:
              summary: Insufficient permissions
              value:
                error:
                  code: "FORBIDDEN"
                  message: "Access token does not have the required scopes for this operation."
                  details:
                    required_scopes: ["docs.purchase"]
                    granted_scopes: ["docs.read"]

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: "Not found"
            message: "Title number not found"

    PaymentRequired:
      description: Insufficient token balance
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "INSUFFICIENT_BALANCE"
              message: "Insufficient balance to complete purchase"
              details:
                required: 10
                available: 5

    TooManyRequests:
      description: Rate limit exceeded
      headers:
        X-RateLimit-Limit:
          description: The rate limit ceiling for this endpoint
          schema:
            type: integer
            examples:
              - 60
        X-RateLimit-Remaining:
          description: The number of requests left for the time window
          schema:
            type: integer
            examples:
              - 0
        X-RateLimit-Reset:
          description: The time at which the rate limit window resets (Unix timestamp)
          schema:
            type: integer
            examples:
              - 1705330200
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "RATE_LIMIT_EXCEEDED"
              message: "Rate limit exceeded. Please try again later."
              details:
                limit: 60
                window: "per minute"
                reset_at: "2026-01-15T14:30:00Z"

    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "INTERNAL_ERROR"
              message: "An unexpected error occurred"
              request_id: "550e8400-e29b-41d4-a716-446655440000"

    BadGateway:
      description: External service error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: "EXTERNAL_SERVICE_ERROR"
              message: "HMLR service is currently unavailable"
              details:
                service: "HMLR Document Availability API"
                retry_after: 300
