openapi: 3.0.3
info:
  title: Nimbus Property Search API
  description: >
    The Nimbus Property Search API provides access to comprehensive UK property
    data from the Nimbus golden record index.

    Search, filter, and retrieve property titles using a flexible query syntax —
    filter by location, property characteristics, geographic bounds, ownership,
    and more.

    For field reference and examples, see the Field Reference documentation.
  version: 1.0.0
  contact:
    name: Nimbus API Support
    email: support@nimbusproperty.co.uk
servers:
  - url: https://api.nimbusmaps.co.uk/search/v1
    description: Production server
  - url: https://api-preprod.nimbusmaps.xyz/search/v1
    description: Test server
security:
  - OAuth2: []
tags:
  - name: Address search
    description: Search for addresses and retrieve associated property identifiers
  - name: Titles
    description: Property title search and data retrieval
  - name: Comps
    description: Comparable deal search and data retrieval
  - name: Utilities
    description: Utility and diagnostic endpoints
paths:
  /titles:
    get:
      operationId: findTitle
      tags:
        - Titles
      summary: Find a single title by ID or number
      description: >
        Retrieve a property title by UUID or title number. Supports lookups by
        ID (direct), or by number with optional country code (defaults to ENG).
        If both parameters provided, ID takes precedence.
      security:
        - OAuth2:
            - search.titles
      parameters:
        - name: id
          in: query
          required: false
          description: Title UUID for direct lookup
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
        - name: number
          in: query
          required: false
          description: Title number (case-insensitive, e.g., "LA135828")
          schema:
            type: string
            pattern: ^[A-Z]{2,3}[0-9]+$
          example: LA135828
        - name: country
          in: query
          required: false
          description: Country ISO code (only used with 'number' parameter)
          schema:
            type: string
            enum:
              - ENG
              - SCT
            default: ENG
          example: ENG
      responses:
        '200':
          description: Title found successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TitleDocument'
              examples:
                byId:
                  summary: Lookup by ID
                  value:
                    id: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                    number: LA135828
                    countryIsoCode: ENG
                    tenure: Leasehold
                    propertyCategory: Residential
                    propertyType:
                      - Terraced
                    areaInAcres: 0.023483
                    mainAddress:
                      fullAddress: 30, Penarth Road, Bolton, BL3 5RJ
                      buildingNumber: '30'
                      street: PENARTH ROAD
                      town: BOLTON
                      postcode:
                        value: BL3 5RJ
                        outcode: BL3
                    owners: []
                byNumber:
                  summary: Lookup by title number (English)
                  value:
                    id: 76a19144-1878-4973-817c-3703199a152c
                    number: CB153191
                    countryIsoCode: ENG
                    tenure: Freehold
                    propertyCategory: Residential
                    propertyType:
                      - Terraced
                    areaInAcres: 0.036295
                    mainAddress:
                      fullAddress: 9, Blackthorn Close, Cambridge, CB4 1FZ
                      buildingNumber: '9'
                      street: BLACKTHORN CLOSE
                      town: CAMBRIDGE
                      postcode:
                        value: CB4 1FZ
                        outcode: CB4
                byNumberScottish:
                  summary: Lookup by title number (Scottish)
                  value:
                    id: 3ca49d43-b5cd-440a-9c46-180a6d3ef3af
                    number: ROS8936
                    countryIsoCode: SCT
                    tenure: Freehold
                    propertyCategory: Residential
        '400':
          description: Missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingParams:
                  summary: Missing required parameters
                  value:
                    error:
                      code: MISSING_PARAMETERS
                      message: Either 'id' or 'number' parameter is required
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
                invalidFormat:
                  summary: Invalid parameter format
                  value:
                    error:
                      code: INVALID_FORMAT
                      message: Parameter 'id' must be a valid UUID
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Title not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                notFoundById:
                  summary: Not found by ID
                  value:
                    error:
                      code: NOT_FOUND
                      message: >-
                        Title with ID '4665bc0b-69d6-4e04-8117-48bb25dfba2c' not
                        found
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
                notFoundByNumber:
                  summary: Not found by number
                  value:
                    error:
                      code: NOT_FOUND
                      message: 'Title with number ''LA135828'' (country: ENG) not found'
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: searchTitles
      tags:
        - Titles
      summary: Search titles
      description: >
        Search property titles using a flexible query syntax. Limits: max 25
        results, 5s timeout, from+size≤10000. Supports match, term, bool, range,
        and geo queries. See the Field Reference for available fields.
      security:
        - OAuth2:
            - search.titles
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ElasticsearchQuery'
            examples:
              simpleMatch:
                summary: Simple text match
                value:
                  query:
                    match:
                      mainAddress.fullAddress: Bolton
                  size: 10
              boolQuery:
                summary: Boolean query with filters
                value:
                  query:
                    bool:
                      must:
                        - match:
                            mainAddress.town: Cambridge
                      filter:
                        - range:
                            areaInAcres:
                              gte: 0.01
                              lte: 1
                  size: 10
              titleNumberLookup:
                summary: Exact title number lookup
                value:
                  query:
                    match:
                      number: LA135828
                  size: 1
              multiMatch:
                summary: Multi-field search
                value:
                  query:
                    multi_match:
                      query: 30 Penarth Road Bolton
                      fields:
                        - mainAddress.fullAddress^3
                        - mainAddress.street^2
                        - mainAddress.town^2
                        - number^5
                      fuzziness: AUTO
                  size: 10
              postcodePrefix:
                summary: Postcode area search
                value:
                  query:
                    match:
                      mainAddress.postcode.outcode: BL3
                  size: 10
              geoBoundingBox:
                summary: Geographic bounding box search
                value:
                  query:
                    bool:
                      must:
                        - match:
                            propertyCategory: Commercial
                      filter:
                        - geo_bounding_box:
                            geometry.centroid:
                              top_left:
                                lat: 53.6
                                lon: -2.5
                              bottom_right:
                                lat: 53.5
                                lon: -2.4
                  size: 25
              aggregationsExample:
                summary: Property distribution analysis
                value:
                  query:
                    match:
                      mainAddress.town: Cambridge
                  size: 0
                  aggs:
                    by_tenure:
                      terms:
                        field: tenure
                    avg_area:
                      avg:
                        field: areaInAcres
                    price_ranges:
                      histogram:
                        field: sales.price
                        interval: 50000
              complexFiltering:
                summary: Multi-criteria property search with filters
                value:
                  query:
                    bool:
                      must:
                        - match:
                            mainAddress.town: London
                      filter:
                        - range:
                            areaInAcres:
                              gte: 0.01
                              lte: 0.5
                  size: 10
              nestedQueryLeases:
                summary: Search properties by lease characteristics
                value:
                  query:
                    nested:
                      path: leases
                      query:
                        range:
                          leases.rentAnnually:
                            gte: 5000
                  size: 10
              constraintAnalysis:
                summary: Find properties with planning constraints
                value:
                  query:
                    bool:
                      must:
                        - match:
                            mainAddress.town: Cambridge
                      filter:
                        - range:
                            areaInAcres:
                              gte: 0.5
                  size: 10
              planningHistory:
                summary: Properties with recent planning approvals
                value:
                  query:
                    nested:
                      path: planning
                      query:
                        bool:
                          must:
                            - match:
                                planning.decision: Approved
                            - range:
                                planning.dateSubmitted:
                                  gte: '1999-01-01'
                  size: 10
              companyOwnership:
                summary: Find properties with company owners
                value:
                  query:
                    nested:
                      path: owners
                      query:
                        exists:
                          field: owners.companyName
                  size: 10
              valueAnalysis:
                summary: Properties by price per sq ft range
                value:
                  query:
                    nested:
                      path: sales
                      query:
                        range:
                          sales.pricePerSquareFoot:
                            gte: 400
                            lte: 600
                  sort:
                    - sales.dateAcquired:
                        order: desc
                        nested:
                          path: sales
                  size: 10
              epcRatingSearch:
                summary: Properties with EPC data available
                value:
                  query:
                    match:
                      mainAddress.town: London
                  size: 10
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ElasticsearchResponse'
              examples:
                success:
                  summary: Successful search response
                  value:
                    took: 42
                    timed_out: false
                    _shards:
                      total: 3
                      successful: 3
                      failed: 0
                    hits:
                      total:
                        value: 123
                        relation: eq
                      max_score: 1.5
                      hits:
                        - _index: properties
                          _id: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                          _score: 1.5
                          _source:
                            id: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                            number: LA135828
                            countryIsoCode: ENG
                            tenure: Leasehold
                            propertyCategory: Residential
                            propertyType:
                              - Terraced
                            areaInAcres: 0.023483
                            mainAddress:
                              fullAddress: 30, Penarth Road, Bolton, BL3 5RJ
                              buildingNumber: '30'
                              street: PENARTH ROAD
                              town: BOLTON
                              postcode:
                                value: BL3 5RJ
                                outcode: BL3
                            owners: []
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
  /comps:
    get:
      operationId: findComparableDeal
      tags:
        - Comps
      summary: Find a single comparable deal by ID
      description: |
        Retrieve one public comparable deal document by UUID.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: id
          in: query
          required: true
          description: Comparable deal UUID for direct lookup
          schema:
            type: string
            format: uuid
          example: f1a0087f-8647-420f-9c72-fbcc9320ecb0
      responses:
        '200':
          description: Comparable deal found successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ComparableDeal'
              example:
                id: f1a0087f-8647-420f-9c72-fbcc9320ecb0
                titleId: cb534ec8-6059-49ad-b40d-3e836a91c36e
                comparableType: Residential
                comparableStatus: RegisteredSale
                dealType: Residential
                ownershipType: Freehold
                propertyType:
                  - Detached
                siteSizeInAcres: 0.337
                mixedUse: false
                validationStatus: Validated
                dealData:
                  dealId: f1a0087f-8647-420f-9c72-fbcc9320ecb0
                  dealDate: '2005-08-23T23:00:00Z'
                  dealPrice: 250000
                  indexedPrice: 418651.69
                  dealPricePerSquareFoot: 134.25
                  dealPricePerAcre: 741939.33
                  dealAddress: >-
                    Land Adjacent The Garage, Great North Road, South Muskham,
                    NG23 6EA
                  transactionType: Sale
                  comparableType: Residential
                  dealType: Sale
                  propertyType: Detached
                  propertyTypeArray:
                    - Detached
                  propertyCondition: Secondhand
                  totalFloorAreaInSquareFeet: 1862.15
                  siteAreaInSquareFeet: 14677.75
                  siteAreaInSquareMeters: 1363.61
                  useClass:
                    - C3
                  useClasses: C3 - Single-family homes
                  countryIsoCode: ENG
                  lastModified: '2026-04-29T08:46:29Z'
                  dealGeometry:
                    entryId: cb534ec8-6059-49ad-b40d-3e836a91c36e
                    centroid:
                      lat: 53.107
                      lon: -0.822
                    area: 3778.96
        '400':
          description: Missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Comparable deal not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      operationId: searchComparableDeals
      tags:
        - Comps
      summary: Search comparable deals
      description: |
        Search comparable deal records using a flexible query syntax.
        Limits: max 25 results, 5s timeout, from+size≤10000.
      security:
        - OAuth2:
            - search.comps
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ElasticsearchQuery'
            examples:
              commercialSales:
                summary: Commercial sales over one million pounds
                value:
                  query:
                    bool:
                      filter:
                        - term:
                            comparableType: Commercial
                        - term:
                            dealData.transactionType: Sale
                        - range:
                            dealData.dealPrice:
                              gte: 1000000
                  sort:
                    - dealData.dealDate:
                        order: desc
                  size: 10
              titleLinkedDeals:
                summary: Deals linked to a title
                value:
                  query:
                    term:
                      titleId: cb534ec8-6059-49ad-b40d-3e836a91c36e
                  size: 25
              yieldAggregation:
                summary: Investment yield aggregation
                value:
                  query:
                    bool:
                      filter:
                        - term:
                            comparableType: Commercial
                        - term:
                            dealType: Investment
                        - exists:
                            field: dealData.netYield
                  size: 0
                  aggs:
                    by_property_type:
                      terms:
                        field: dealData.propertyTypeArray
                      aggs:
                        avg_yield:
                          avg:
                            field: dealData.netYield
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompsElasticsearchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
  /comps/{dealId}/sales:
    get:
      operationId: getCompsSales
      tags:
        - Comps
      summary: Get sale detail records for a comparable deal
      description: >
        Retrieve up to 10 sale detail records for a comparable deal.

        The response includes ownership type, corporate ownership where
        available, linked investment-sale leases, tenants, and agents.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: dealId
          in: path
          required: true
          description: Comparable deal UUID
          schema:
            type: string
            format: uuid
          example: f1a0087f-8647-420f-9c72-fbcc9320ecb0
      responses:
        '200':
          description: Sale detail records for the comparable deal
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CompsSaleRecord'
              example:
                - id: 98aa543d-0c40-4b97-9093-e6f15edc6770
                  dealId: f1a0087f-8647-420f-9c72-fbcc9320ecb0
                  titleId: cb534ec8-6059-49ad-b40d-3e836a91c36e
                  dateAcquired: '2022-02-25T00:00:00Z'
                  price: 1000000
                  dataSourceType: PublicRecords
                  ownerships:
                    - id: 6e2e7310-b525-4a7c-b34f-af9f0caa69eb
                      ownerType: Company
                      corporateOwners:
                        - id: 9fba1bc2-50f9-4282-8e66-b606e9121f1a
                          proprietorName: HEYLO HOUSING LIMITED
                          companyId: 240acddd-0b25-401e-bc2e-cc6f527222d3
                  linkedLeases: []
                  acquisitionAgents: []
                  disposingAgents: []
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: Detail service temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /comps/{dealId}/leases:
    get:
      operationId: getCompsLeases
      tags:
        - Comps
      summary: Get lease detail records for a comparable deal
      description: |
        Retrieve up to 10 lease detail records for a comparable deal.
        The response includes break clauses, covenants, tenants, and agents.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: dealId
          in: path
          required: true
          description: Comparable deal UUID
          schema:
            type: string
            format: uuid
          example: f1a0087f-8647-420f-9c72-fbcc9320ecb0
      responses:
        '200':
          description: Lease detail records for the comparable deal
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CompsLeaseRecord'
              example:
                - id: 8a314d48-e7ae-420d-a5f6-7ea5d45a8b32
                  dealId: f1a0087f-8647-420f-9c72-fbcc9320ecb0
                  startDate: '2020-01-01T00:00:00Z'
                  endDate: '2030-01-01T00:00:00Z'
                  leaseType: BusinessLease
                  rentAnnually: 45000
                  breakClause: null
                  covenants: []
                  tenants: []
                  acquisitionAgents: []
                  disposingAgents: []
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: Detail service temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /comps/{dealId}/listings:
    get:
      operationId: getCompsListings
      tags:
        - Comps
      summary: Get listing detail records for a comparable deal
      description: >
        Retrieve up to 10 listing detail records for a comparable deal.

        The response includes price history, marketing resources, images, and
        agents.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: dealId
          in: path
          required: true
          description: Comparable deal UUID
          schema:
            type: string
            format: uuid
          example: f1a0087f-8647-420f-9c72-fbcc9320ecb0
      responses:
        '200':
          description: Listing detail records for the comparable deal
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CompsListingRecord'
              example:
                - id: 003a77d9-8749-45c7-b9a4-b97da19ea4e1
                  dealId: f1a0087f-8647-420f-9c72-fbcc9320ecb0
                  firstListingDate: '2025-01-15T00:00:00Z'
                  listingStatus: Active
                  salesNoticeType: ForSale
                  history: []
                  resources: []
                  agents: []
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: Detail service temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /comps/{dealId}/brochures:
    get:
      operationId: getCompsBrochures
      tags:
        - Comps
      summary: List available brochures for a comparable deal
      description: >
        Returns metadata about brochure PDFs archived to blob storage for a
        comparable deal's listings.

        Use the index value with the /brochures/download endpoint to fetch a
        specific PDF.

        Returns empty array if no brochures are archived.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: dealId
          in: path
          required: true
          description: Comparable deal UUID
          schema:
            type: string
            format: uuid
          example: dace1cb1-6cbf-4529-be74-316c236083d6
      responses:
        '200':
          description: Brochure metadata (empty array if none archived)
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                      description: Listing resource UUID
                    listingId:
                      type: string
                      format: uuid
                      description: Parent listing UUID
                    agentName:
                      type: string
                      nullable: true
                      description: Name of the listing agent (if available)
                    brochureDate:
                      type: string
                      format: date-time
                      description: Date the brochure was archived
                    index:
                      type: integer
                      description: Zero-based index for use with the download endpoint
                  required:
                    - id
                    - listingId
                    - brochureDate
                    - index
              example:
                - id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                  listingId: b2c3d4e5-f6a7-8901-bcde-f12345678901
                  agentName: Savills
                  brochureDate: '2025-11-04T10:30:00Z'
                  index: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: Detail service temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /comps/{dealId}/brochures/download:
    get:
      operationId: getCompsBrochuresDownload
      tags:
        - Comps
      summary: Download a brochure PDF for a comparable deal
      description: >
        Downloads the actual PDF file for a brochure that has been archived to
        blob storage.

        Use the /comps/{dealId}/brochures endpoint first to list available
        brochures,

        then use the index parameter to select which one to download.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: dealId
          in: path
          required: true
          description: Comparable deal UUID
          schema:
            type: string
            format: uuid
          example: dace1cb1-6cbf-4529-be74-316c236083d6
        - name: index
          in: query
          required: false
          description: Zero-based index of the brochure to download (default 0)
          schema:
            type: integer
            default: 0
            minimum: 0
          example: 0
      responses:
        '200':
          description: Brochure PDF file
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: Detail service temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /titles/{titleId}/brochures:
    get:
      operationId: getTitleBrochures
      tags:
        - Titles
      summary: List available brochures for a title/property
      description: >
        Returns metadata about brochure PDFs archived to blob storage across all
        comparable deals for a title.

        Use the index value with the /brochures/download endpoint to fetch a
        specific PDF.

        Returns empty array if no brochures are archived.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: cb534ec8-6059-49ad-b40d-3e836a91c36e
      responses:
        '200':
          description: Brochure metadata across all deals for the title
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                      description: Listing resource UUID
                    listingId:
                      type: string
                      format: uuid
                      description: Parent listing UUID
                    dealId:
                      type: string
                      format: uuid
                      description: Comparable deal UUID this brochure belongs to
                    agentName:
                      type: string
                      nullable: true
                      description: Name of the listing agent (if available)
                    brochureDate:
                      type: string
                      format: date-time
                      description: Date the brochure was archived
                    index:
                      type: integer
                      description: Zero-based index for use with the download endpoint
                  required:
                    - id
                    - listingId
                    - dealId
                    - brochureDate
                    - index
              example:
                - id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
                  listingId: b2c3d4e5-f6a7-8901-bcde-f12345678901
                  dealId: dace1cb1-6cbf-4529-be74-316c236083d6
                  agentName: Savills
                  brochureDate: '2025-11-04T10:30:00Z'
                  index: 0
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/brochures/download:
    get:
      operationId: getTitleBrochuresDownload
      tags:
        - Titles
      summary: Download a brochure PDF for a title/property
      description: >
        Downloads the actual PDF file for a brochure archived for a title's
        comparable deals.

        Use the /titles/{titleId}/brochures endpoint first to list available
        brochures,

        then use the index parameter to select which one to download.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: cb534ec8-6059-49ad-b40d-3e836a91c36e
        - name: index
          in: query
          required: false
          description: Zero-based index of the brochure to download (default 0)
          schema:
            type: integer
            default: 0
            minimum: 0
          example: 0
      responses:
        '200':
          description: Brochure PDF file
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/footfall:
    get:
      operationId: getTitleFootfall
      tags:
        - Titles
      summary: Get footfall estimates for a title
      description: >
        Returns hourly pedestrian footfall estimates for the grid cell that best
        overlaps the title polygon.

        Selects the cell with the highest daily average when multiple cells
        overlap.

        Includes both overall averages and monthly time-series data.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Footfall averages and monthly breakdown
          content:
            application/json:
              schema:
                type: object
                properties:
                  titleId:
                    type: string
                    format: uuid
                  indexId:
                    type: string
                    description: Grid cell identifier
                  averages:
                    type: object
                    properties:
                      daily:
                        type: number
                      fridayNightout:
                        type: number
                      weekendDaytime:
                        type: number
                      rushHours:
                        type: number
                      officeHours:
                        type: number
                      nightHours:
                        type: number
                      sleepingHours:
                        type: number
                  monthly:
                    type: array
                    items:
                      type: object
                      properties:
                        year:
                          type: integer
                        month:
                          type: integer
                        daily:
                          type: number
                        fridayNightout:
                          type: number
                        weekendDaytime:
                          type: number
                        rushHours:
                          type: number
                        officeHours:
                          type: number
                        nightHours:
                          type: number
                        sleepingHours:
                          type: number
                required:
                  - titleId
                  - indexId
                  - averages
                  - monthly
              example:
                titleId: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                indexId: '630949366352100863'
                averages:
                  daily: 380
                  fridayNightout: 350
                  weekendDaytime: 330
                  rushHours: 440
                  officeHours: 350
                  nightHours: 480
                  sleepingHours: 400
                monthly:
                  - year: 2025
                    month: 9
                    daily: 200
                    fridayNightout: 170
                    weekendDaytime: 70
                    rushHours: 250
                    officeHours: 150
                    nightHours: 240
                    sleepingHours: 220
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /footfall/{indexId}:
    get:
      operationId: getFootfall
      tags:
        - Utilities
      summary: Get footfall estimates by grid cell index
      description: >
        Returns hourly pedestrian footfall estimates for a specific grid cell by
        its index ID.

        Includes both overall averages and monthly time-series data.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: indexId
          in: path
          required: true
          description: Footfall grid cell index ID
          schema:
            type: string
          example: '630949366352100863'
      responses:
        '200':
          description: Footfall averages and monthly breakdown
          content:
            application/json:
              schema:
                type: object
                properties:
                  indexId:
                    type: string
                    description: Grid cell identifier
                  averages:
                    type: object
                    properties:
                      daily:
                        type: number
                      fridayNightout:
                        type: number
                      weekendDaytime:
                        type: number
                      rushHours:
                        type: number
                      officeHours:
                        type: number
                      nightHours:
                        type: number
                      sleepingHours:
                        type: number
                  monthly:
                    type: array
                    items:
                      type: object
                      properties:
                        year:
                          type: integer
                        month:
                          type: integer
                        daily:
                          type: number
                        fridayNightout:
                          type: number
                        weekendDaytime:
                          type: number
                        rushHours:
                          type: number
                        officeHours:
                          type: number
                        nightHours:
                          type: number
                        sleepingHours:
                          type: number
                required:
                  - indexId
                  - averages
                  - monthly
              example:
                indexId: '630949366352100863'
                averages:
                  daily: 380
                  fridayNightout: 350
                  weekendDaytime: 330
                  rushHours: 440
                  officeHours: 350
                  nightHours: 480
                  sleepingHours: 400
                monthly:
                  - year: 2025
                    month: 9
                    daily: 200
                    fridayNightout: 170
                    weekendDaytime: 70
                    rushHours: 250
                    officeHours: 150
                    nightHours: 240
                    sleepingHours: 220
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /comps/nl-query:
    post:
      operationId: searchComparableDealsNaturalLanguage
      tags:
        - Comps
      summary: Search comparable deals using natural language
      description: >
        Search comparable deal records using natural language queries,
        automatically interpreted and executed.
      security:
        - OAuth2:
            - search.comps
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NaturalLanguageQueryRequest'
            examples:
              commercialSales:
                summary: Commercial office sales
                value:
                  query: commercial office sales in Birmingham over 500k since 2022
                  size: 10
              investmentYield:
                summary: Investment deals by yield
                value:
                  query: >-
                    investment deals with net yield above 6 percent in
                    Manchester
                  size: 10
      responses:
        '200':
          description: Natural language query successfully converted and executed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NaturalLanguageCompsQueryResponse'
        '400':
          description: Invalid request or AI failed to generate valid query
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '502':
          description: AI service unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
  /titles/nl-query:
    post:
      operationId: searchTitlesNaturalLanguage
      tags:
        - Titles
      summary: Search titles using natural language query
      description: >
        Search UK land title records using natural language queries,
        automatically interpreted and executed.


        Accepts plain English like "houses in Bolton over 3 acres". The response
        includes results and a description of the executed query, enabling
        conversational refinement.


        How it works:

        1. Submit a plain English query describing the properties you want

        2. The query is automatically interpreted and executed against the
        property index

        3. Results are returned with a natural language description of what was
        searched


        Use this endpoint when:

        - You prefer natural language over structured query syntax

        - You want to explore data without writing queries manually

        - You need conversational query refinement


        Limits: Same as POST /titles - max 25 results, 5s timeout,
        from+size≤10000
      security:
        - OAuth2:
            - search.titles
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NaturalLanguageQueryRequest'
            examples:
              simpleLocation:
                summary: Simple location search
                value:
                  query: houses in Bolton
                  size: 10
              locationWithSize:
                summary: Location with area filter
                value:
                  query: commercial properties in Cambridge over 2 acres
                  size: 15
              specificCriteria:
                summary: Multiple specific criteria
                value:
                  query: >-
                    freehold residential properties in London between 0.5 and 1
                    acre
                  size: 20
              constraintSearch:
                summary: Search with planning constraints
                value:
                  query: properties with listed building constraints in Oxford
                  size: 10
              ownershipSearch:
                summary: Search by ownership type
                value:
                  query: properties owned by housing associations in Manchester
                  size: 10
              geographicBounds:
                summary: Geographic bounding box
                value:
                  query: >-
                    commercial properties in the area bounded by latitude 53.5
                    to 53.6 and longitude -2.5 to -2.4
                  size: 25
      responses:
        '200':
          description: Natural language query successfully converted and executed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NaturalLanguageQueryResponse'
              examples:
                success:
                  summary: Successful natural language search
                  value:
                    queryDescription: >-
                      Searching for residential terraced properties in Bolton
                      with an area between 0.02 and 0.05 acres
                    elasticsearchQuery:
                      query:
                        bool:
                          must:
                            - match:
                                mainAddress.town: Bolton
                            - term:
                                propertyCategory: Residential
                            - term:
                                propertyType.keyword: Terraced
                          filter:
                            - range:
                                areaInAcres:
                                  gte: 0.02
                                  lte: 0.05
                      size: 10
                    results:
                      took: 42
                      timed_out: false
                      _shards:
                        total: 3
                        successful: 3
                        failed: 0
                      hits:
                        total:
                          value: 123
                          relation: eq
                        max_score: 1.5
                        hits:
                          - _index: properties
                            _id: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                            _score: 1.5
                            _source:
                              id: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                              number: LA135828
                              countryIsoCode: ENG
                              tenure: Leasehold
                              propertyCategory: Residential
                              propertyType:
                                - Terraced
                              areaInAcres: 0.023483
                              mainAddress:
                                fullAddress: 30, Penarth Road, Bolton, BL3 5RJ
                                buildingNumber: '30'
                                street: PENARTH ROAD
                                town: BOLTON
                                postcode:
                                  value: BL3 5RJ
                                  outcode: BL3
                              owners: []
        '400':
          description: Invalid request or AI failed to generate valid query
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidQuery:
                  summary: Query could not be interpreted
                  value:
                    error:
                      code: INVALID_NL_QUERY
                      message: >-
                        Could not interpret your natural language request. Try
                        being more specific or using simpler terms. Example:
                        'houses in Bolton over 3 acres'
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
                emptyQuery:
                  summary: Empty or missing query string
                  value:
                    error:
                      code: MISSING_QUERY
                      message: The 'query' field is required and cannot be empty
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '502':
          description: AI service unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: AI_SERVICE_ERROR
                  message: >-
                    The natural language query service is temporarily
                    unavailable. Please try again or use POST /titles with a
                    structured query.
                  correlationId: 550e8400-e29b-41d4-a716-446655440000
        '504':
          $ref: '#/components/responses/GatewayTimeout'
  /titles/{titleId}/comps:
    get:
      operationId: getTitleComps
      tags:
        - Titles
      summary: Get comparable deal summaries for a title
      description: >
        Retrieve public comparable deal summaries associated with a specific
        title.
      security:
        - OAuth2:
            - search.comps
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: cb534ec8-6059-49ad-b40d-3e836a91c36e
      responses:
        '200':
          description: Comparable deal summaries for the title
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ComparableDealSummary'
              example:
                - id: f1a0087f-8647-420f-9c72-fbcc9320ecb0
                  comparableType: Residential
                  comparableStatus: RegisteredSale
                  dealType: Residential
                  ownershipType: Freehold
                  propertyType:
                    - Detached
                  mixedUse: false
                  validationStatus: Validated
                  dealDate: '2005-08-23T23:00:00Z'
                  dealPrice: 250000
                  indexedPrice: 418651.69
                  dealPricePerSquareFoot: 134.25
                  transactionType: Sale
                  dealAddress: >-
                    Land Adjacent The Garage, Great North Road, South Muskham,
                    NG23 6EA
                  totalFloorAreaInSquareFeet: 1862.15
                  siteAreaInSquareFeet: 14677.75
                  useClass:
                    - C3
                  countryIsoCode: ENG
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
  /titles/{titleId}/planning:
    get:
      operationId: getTitlePlanning
      tags:
        - Titles
      summary: Get planning applications for a title
      description: >
        Retrieve all planning applications associated with a specific property
        title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Planning applications retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    titleId:
                      type: string
                      format: uuid
                    reference:
                      type: string
                      description: Planning application reference
                      example: C/99/1017
                    authority:
                      type: string
                      description: Planning authority
                      example: Cambridge
                    description:
                      type: string
                      description: Application description
                    dateSubmitted:
                      type: string
                      format: date-time
                    decisionDate:
                      type: string
                      format: date-time
                      nullable: true
                      description: Date the planning decision was issued
                    decision:
                      type: string
                      nullable: true
                      description: Planning decision text
                    decisionStatus:
                      type: string
                      nullable: true
                      description: >-
                        Normalised decision status. Use this field for filtering
                        rather than the raw decision text.
                    status:
                      type: string
                      nullable: true
                      description: >-
                        Planning application status (e.g. "DECIDED",
                        "WITHDRAWN")
                    applicationUrl:
                      type: string
                      format: uri
                      nullable: true
                    agentName:
                      type: string
                      nullable: true
                    agentCompany:
                      type: string
                      nullable: true
                    agentAddress:
                      type: string
                      nullable: true
                    applicantName:
                      type: string
                      nullable: true
                    applicantCompany:
                      type: string
                      nullable: true
                    applicantAddress:
                      type: string
                      nullable: true
                    units:
                      type: integer
                    year:
                      type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/constraints:
    get:
      operationId: getTitleConstraints
      tags:
        - Titles
      summary: Get planning and environmental constraints for a title
      description: >
        Retrieve all planning and environmental constraints affecting a specific
        property title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Constraints retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    titleId:
                      type: string
                      format: uuid
                    name:
                      type: string
                      description: Constraint type name
                      example: SiteOfSpecialScientificInterest
                    categoryName:
                      type: string
                      nullable: true
                      description: Constraint category or severity
                    isOverlapping:
                      type: boolean
                    overlapPercentage:
                      type: number
                      format: double
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/sales:
    get:
      operationId: getTitleSales
      tags:
        - Titles
      summary: Get sales history for a title
      description: |
        Retrieve all property sale transactions for a specific title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Sales history retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    titleId:
                      type: string
                      format: uuid
                    dateAcquired:
                      type: string
                      format: date-time
                      example: '2024-05-02T23:00:00Z'
                    price:
                      type: number
                      format: double
                      description: Sale price in GBP
                    pricePerSquareFoot:
                      type: number
                      format: double
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/leases:
    get:
      operationId: getTitleLeases
      tags:
        - Titles
      summary: Get lease information for a title
      description: |
        Retrieve all lease information associated with a specific title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Lease information retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    titleId:
                      type: string
                      format: uuid
                    leaseStartDate:
                      type: string
                      format: date-time
                    leaseEndDate:
                      type: string
                      format: date-time
                    leaseLength:
                      type: integer
                      description: Lease length in years
                    leaseType:
                      type: string
                      enum:
                        - Business
                        - Occupational
                        - VirtualFreehold
                    tenantInAdministration:
                      type: boolean
                    tenantLateFiling:
                      type: boolean
                    rent:
                      type: number
                      format: double
                      nullable: true
                      description: Rent amount as recorded in the lease
                    rentAnnually:
                      type: number
                      format: double
                      nullable: true
                      description: Annual rent amount
                    rentPerSquareFoot:
                      type: number
                      format: double
                      nullable: true
                    rentReviewDateText:
                      type: string
                      nullable: true
                    breakClauseDescription:
                      type: string
                      nullable: true
                    breakClauseTermInMonths:
                      type: integer
                      nullable: true
                    breakClauseFromDate:
                      type: string
                      nullable: true
                    breakPenalty:
                      type: number
                      format: double
                      nullable: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/leaseholds:
    get:
      operationId: getTitleLeaseholds
      tags:
        - Titles
      summary: Get leasehold information for a title
      description: |
        Retrieve all leasehold titles associated with a specific freehold title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Leasehold information retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    titleId:
                      type: string
                      format: uuid
                      description: The freehold title ID
                    leaseholdTitleId:
                      type: string
                      format: uuid
                      description: The leasehold title ID
                    leaseholdTitleNumber:
                      type: string
                      description: The leasehold title number
                    companyName:
                      type: string
                      description: Leaseholder company name
                    companyNumber:
                      type: string
                      description: Leaseholder company registration number
                    companyAddress:
                      type: string
                      description: Leaseholder company address
                    tenantInAdministration:
                      type: boolean
                      description: Whether leaseholder is in administration
                    tenantLateFiling:
                      type: boolean
                      description: Whether leaseholder has late filings
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/geometry:
    get:
      operationId: getTitleGeometry
      tags:
        - Titles
      summary: Get geometry data for a title
      description: >
        Retrieve geographic data including boundaries and location for a
        specific title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Geometry data retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  area:
                    type: number
                    format: double
                    description: Area in square meters
                  centroid:
                    type: object
                    properties:
                      lat:
                        type: number
                        format: double
                      lon:
                        type: number
                        format: double
                  multiPolygonGeometry:
                    type: object
                    description: GeoJSON MultiPolygon geometry
                    properties:
                      type:
                        type: string
                        enum:
                          - MultiPolygon
                      coordinates:
                        type: array
                        description: GeoJSON coordinate arrays
                        items:
                          type: array
                          items:
                            type: array
                            items:
                              type: array
                              items:
                                type: number
                  titleId:
                    type: string
                    format: uuid
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/buildings:
    get:
      operationId: getTitleBuildings
      tags:
        - Titles
      summary: Get building information for a title
      description: |
        Retrieve all buildings associated with a specific property title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Building information retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                      format: uuid
                    titleId:
                      type: string
                      format: uuid
                    builtYear:
                      type: string
                      nullable: true
                      description: Construction year or range
                    height:
                      type: number
                      format: double
                      description: Building height in meters
                    eavesHeight:
                      type: number
                      format: double
                    heightAboveSea:
                      type: number
                      format: double
                    footprintAreaSqFt:
                      type: number
                      format: double
                    uprns:
                      type: array
                      items:
                        $ref: '#/components/schemas/UPRN'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/epcs:
    get:
      operationId: getTitleEPCs
      tags:
        - Titles
      summary: Get EPC (Energy Performance Certificate) data for a title
      description: |
        Retrieve all EPC data from UPRNs associated with a specific title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: EPC data retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    uprnIdentifier:
                      type: string
                      description: Associated UPRN
                    currentRating:
                      type: string
                      enum:
                        - A
                        - A+
                        - B
                        - B+
                        - C
                        - C+
                        - Carbon Neu
                        - D
                        - D+
                        - E
                        - E+
                        - F
                        - F+
                        - G
                    potentialRating:
                      type: string
                      enum:
                        - A
                        - A+
                        - B
                        - B+
                        - C
                        - C+
                        - Carbon Neu
                        - D
                        - D+
                        - E
                        - E+
                        - F
                        - F+
                        - G
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/properties:
    get:
      operationId: getTitleProperties
      tags:
        - Titles
      summary: Get top-level property data for a title
      description: >
        Retrieve core property information including main address, tenure,
        category, and area measurements.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Property data retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  number:
                    type: string
                  countryIsoCode:
                    type: string
                    enum:
                      - ENG
                      - SCT
                  tenure:
                    type: string
                  propertyCategory:
                    type: string
                    enum:
                      - Residential
                      - Commercial
                      - Land
                      - Mixed
                      - Unknown
                      - ''
                    description: High-level property category
                  propertyType:
                    type: array
                    items:
                      type: string
                  areaInAcres:
                    type: number
                    format: double
                  areaInSquareFeet:
                    type: number
                    format: double
                  areaInSquareMeters:
                    type: number
                    format: double
                  buildingFootprint:
                    type: number
                    format: double
                  siteCoverage:
                    type: number
                    format: double
                  totalFloorAreaSqFt:
                    type: number
                    format: double
                  sizeOfCommercialSpaceInSquareFeet:
                    type: number
                    format: double
                    nullable: true
                  ownerType:
                    type: array
                    items:
                      type: string
                    description: >-
                      Owner category list. English titles with no recorded
                      owners will include "Private".
                  mainAddress:
                    type: object
                    properties:
                      fullAddress:
                        type: string
                      buildingNumber:
                        type: string
                      buildingName:
                        type: string
                      street:
                        type: string
                      town:
                        type: string
                      county:
                        type: string
                      postcode:
                        type: object
                        properties:
                          value:
                            type: string
                          outcode:
                            type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/use-classes:
    get:
      operationId: getTitleUseClasses
      tags:
        - Titles
      summary: Get planning use classes for a title
      description: >
        Retrieve all UK Planning Use Class codes from UPRNs associated with a
        specific title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Use classes retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    uprnIdentifier:
                      type: string
                      format: uuid
                      description: UUID identifier for the UPRN record
                    useClass:
                      type: array
                      nullable: true
                      items:
                        type: string
                      description: UK Planning Use Class codes (simple string array)
                    useClasses:
                      type: array
                      nullable: true
                      description: Structured use class data with descriptions
                      items:
                        type: object
                        properties:
                          code:
                            type: string
                          description:
                            type: string
                            nullable: true
                          classificationDescription:
                            type: string
                            nullable: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/owners:
    get:
      operationId: getTitleOwners
      tags:
        - Titles
      summary: Get owner information for a title
      description: >
        Retrieve all owner information for a specific title. May be empty for
        privacy reasons.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: Owner information retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    companyName:
                      type: string
                    companyNumber:
                      type: string
                    companyAddress:
                      type: string
                    ownerCategory:
                      type: string
                      enum:
                        - Company
                        - Council
                        - HousingAssociation
                        - Private
                    ownerInAdministration:
                      type: boolean
                    ownerLateFiling:
                      type: boolean
                    titleId:
                      type: string
                      format: uuid
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /titles/{titleId}/voa:
    get:
      operationId: getTitleVOA
      tags:
        - Titles
      summary: Get VOA classifications and rateable values for a title
      description: >
        Retrieve Valuation Office Agency (VOA) property classifications and
        rateable values from UPRNs associated with a specific title.
      parameters:
        - name: titleId
          in: path
          required: true
          description: Title UUID
          schema:
            type: string
            format: uuid
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
      responses:
        '200':
          description: VOA data retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    uprnIdentifier:
                      type: string
                    rateableValue:
                      type: number
                      format: double
                      description: Rateable value for business rates
                    vOA:
                      type: array
                      items:
                        type: object
                        properties:
                          typeCode:
                            type: string
                            description: VOA special category code
                          typeDescription:
                            type: string
                            description: VOA property type description
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /address:
    get:
      operationId: searchAddress
      tags:
        - Address search
      summary: Search for an address
      description: >
        Find matching addresses by free-text query. Returns up to 10 results
        with full address, coordinates, UPRN UUID, and Title UUID/tenure.


        **Filtering:** Addresses that cannot be resolved to a UPRN are excluded
        from results.


        **Title selection:** When a UPRN is linked to multiple titles, the best
        match is selected by tenure priority

        (Freehold > OWNERSHIP > PROPRIETOR > SUPERIOR, then smallest area). If
        no title is linked, `titleRef` is `null`.
      security:
        - OAuth2:
            - search.address
      parameters:
        - name: address
          in: query
          required: true
          description: >-
            Free-text address to search (e.g., "30 Penarth Road Bolton" or "BL3
            5RJ")
          schema:
            type: string
            minLength: 3
          example: 30 Penarth Road Bolton
      responses:
        '200':
          description: Address search results
          content:
            application/vnd.api+json:
              schema:
                $ref: '#/components/schemas/AddressSearchResponse'
              examples:
                successful:
                  summary: Successful address search with title
                  value:
                    jsonapi:
                      version: '1.0'
                    data:
                      - type: searchIdResult
                        attributes:
                          uprnRef:
                            id: 92403a5a-58da-4870-b021-762e673d34be
                          titleRef:
                            id: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                            tenure: Leasehold
                          fullAddress: 30, Penarth Road, Bolton, BL3 5RJ
                          latitude: 53.582
                          longitude: -2.428
                      - type: searchIdResult
                        attributes:
                          uprnRef:
                            id: 0d136a81-f323-4c2d-b87a-202155b55438
                          titleRef:
                            id: 5775cd1c-7a89-5084-928d-4814266a263b
                            tenure: Freehold
                          fullAddress: 32, Penarth Road, Bolton, BL3 5RJ
                          latitude: 53.583
                          longitude: -2.428
                    meta:
                      requestId: 550e8400-e29b-41d4-a716-446655440000
                      totalResults: 2
                      maxResults: 10
                noTitle:
                  summary: UPRN found but no associated title
                  value:
                    jsonapi:
                      version: '1.0'
                    data:
                      - type: searchIdResult
                        attributes:
                          uprnRef:
                            id: cb69a86c-8c3d-4d92-96aa-cb392a342b24
                          titleRef: null
                          fullAddress: 1, Market Street, Manchester, M1 1AA
                          latitude: 53.481
                          longitude: -2.237
                    meta:
                      requestId: 550e8400-e29b-41d4-a716-446655440000
                      totalResults: 1
                      maxResults: 10
                empty:
                  summary: No results found
                  value:
                    jsonapi:
                      version: '1.0'
                    data: []
                    meta:
                      requestId: 550e8400-e29b-41d4-a716-446655440000
                      totalResults: 0
                      maxResults: 10
        '400':
          description: Missing or invalid address parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingAddress:
                  summary: Missing address parameter
                  value:
                    error:
                      code: MISSING_PARAMETERS
                      message: Parameter 'address' is required
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
                addressTooShort:
                  summary: Address query too short
                  value:
                    error:
                      code: INVALID_FORMAT
                      message: Parameter 'address' must be at least 3 characters
                      correlationId: 550e8400-e29b-41d4-a716-446655440000
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: Address search service temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
  /health:
    get:
      operationId: healthCheck
      tags:
        - Utilities
      summary: Health check
      description: Check the health status of the API
      security: []
      responses:
        '200':
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: healthy
                timestamp: '2026-01-10T12:34:56Z'
                version: 2.0.0
        '503':
          description: Service is unhealthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      description: Microsoft Entra ID OAuth 2.0 Authorization Code Flow
      flows:
        authorizationCode:
          authorizationUrl: >-
            https://login.microsoftonline.com/d2a91423-0dc1-4853-8515-7b7b7d262791/oauth2/v2.0/authorize
          tokenUrl: >-
            https://login.microsoftonline.com/d2a91423-0dc1-4853-8515-7b7b7d262791/oauth2/v2.0/token
          refreshUrl: >-
            https://login.microsoftonline.com/d2a91423-0dc1-4853-8515-7b7b7d262791/oauth2/v2.0/token
          scopes:
            search.titles: Search and retrieve title information
            search.comps: Search and retrieve comparable deal information
            search.address: Search for addresses and retrieve associated property IDs
  schemas:
    ComparableDeal:
      type: object
      description: Public comparable deal document.
      required:
        - id
        - dealData
      properties:
        id:
          type: string
          format: uuid
          description: Unique deal identifier
          example: f1a0087f-8647-420f-9c72-fbcc9320ecb0
        titleId:
          type: string
          format: uuid
          nullable: true
          description: Linked HMLR title identifier
          example: cb534ec8-6059-49ad-b40d-3e836a91c36e
        uprnId:
          type: string
          format: uuid
          nullable: true
          description: Linked UPRN identifier
        comparableType:
          type: string
          enum:
            - Land
            - Residential
            - Commercial
          nullable: true
        comparableStatus:
          type: string
          enum:
            - RegisteredSale
            - QuotingPrice
            - Availability
        dealType:
          type: string
          enum:
            - Investment
            - VacantPossession
            - OccupationalLease
            - BusinessLease
            - Residential
            - Lease
            - Sale
            - Unknown
        ownershipType:
          type: string
          enum:
            - Freehold
            - Leasehold
            - VirtualFreehold
            - Unknown
        propertyType:
          type: array
          items:
            type: string
          example:
            - Detached
        siteSizeInAcres:
          type: number
          format: double
          nullable: true
        mixedUse:
          type: boolean
          example: false
        validationStatus:
          type: string
          nullable: true
          example: Validated
        dealData:
          $ref: '#/components/schemas/DealData'
    DealData:
      type: object
      description: Aggregated and calculated public fields for a comparable deal
      properties:
        dealId:
          type: string
          format: uuid
        dealDate:
          type: string
          format: date-time
          nullable: true
        endDate:
          type: string
          format: date-time
          nullable: true
        leaseTerm:
          type: integer
          nullable: true
        dealPrice:
          type: number
          format: double
          nullable: true
        indexedPrice:
          type: number
          format: double
          nullable: true
        premiumPrice:
          type: number
          format: double
          nullable: true
        isPremium:
          type: boolean
        dealPricePerSquareFoot:
          type: number
          format: double
          nullable: true
        dealPricePerAcre:
          type: number
          format: double
          nullable: true
        dealPricePerUnit:
          type: number
          format: double
          nullable: true
        numberOfUnits:
          type: integer
          nullable: true
        siteAreaInSquareMeters:
          type: number
          format: double
          nullable: true
        siteAreaInSquareFeet:
          type: number
          format: double
          nullable: true
        totalFloorAreaInSquareFeet:
          type: number
          format: double
          nullable: true
        dealAddress:
          type: string
          nullable: true
        comparableType:
          type: string
          enum:
            - Land
            - Residential
            - Commercial
          nullable: true
        transactionType:
          type: string
          enum:
            - Sale
            - Letting
          nullable: true
        dealType:
          type: string
          nullable: true
        propertyType:
          type: string
          nullable: true
        propertyTypeArray:
          type: array
          items:
            type: string
        propertyCondition:
          type: string
          enum:
            - Newbuild
            - Secondhand
            - Unknown
          nullable: true
        useClass:
          type: array
          nullable: true
          items:
            type: string
        useClasses:
          type: string
          nullable: true
        voaCode:
          type: array
          nullable: true
          items:
            type: string
        floors:
          type: string
          nullable: true
        builtYears:
          type: string
          nullable: true
        daysOnMarket:
          type: integer
          nullable: true
        netYield:
          type: number
          format: double
          nullable: true
        listingAgent:
          type: string
          nullable: true
        dataSources:
          type: string
          nullable: true
        countryIsoCode:
          type: string
          enum:
            - ENG
            - SCT
          nullable: true
        lastModified:
          type: string
          format: date-time
          nullable: true
        dealGeometry:
          $ref: '#/components/schemas/DealGeometry'
    DealGeometry:
      type: object
      nullable: true
      properties:
        entryId:
          type: string
          format: uuid
          nullable: true
        centroid:
          $ref: '#/components/schemas/GeoPoint'
        area:
          type: number
          format: double
          nullable: true
    GeoPoint:
      type: object
      nullable: true
      properties:
        lat:
          type: number
          format: double
        lon:
          type: number
          format: double
    ComparableDealSummary:
      type: object
      description: Flattened public comparable deal summary for a title
      properties:
        id:
          type: string
          format: uuid
        comparableType:
          type: string
          nullable: true
        comparableStatus:
          type: string
          nullable: true
        dealType:
          type: string
          nullable: true
        ownershipType:
          type: string
          nullable: true
        propertyType:
          type: array
          items:
            type: string
        mixedUse:
          type: boolean
        validationStatus:
          type: string
          nullable: true
        dealDate:
          type: string
          format: date-time
          nullable: true
        dealPrice:
          type: number
          format: double
          nullable: true
        indexedPrice:
          type: number
          format: double
          nullable: true
        dealPricePerSquareFoot:
          type: number
          format: double
          nullable: true
        transactionType:
          type: string
          nullable: true
        dealAddress:
          type: string
          nullable: true
        totalFloorAreaInSquareFeet:
          type: number
          format: double
          nullable: true
        siteAreaInSquareFeet:
          type: number
          format: double
          nullable: true
        useClass:
          type: array
          nullable: true
          items:
            type: string
        countryIsoCode:
          type: string
          nullable: true
    CompsAgent:
      type: object
      description: Agent linked to a comparable detail record
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        nameAbbreviation:
          type: string
          nullable: true
        websiteUrl:
          type: string
          nullable: true
    CompsTenant:
      type: object
      description: Tenant linked to a comparable lease
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        companyId:
          type: string
          format: uuid
          nullable: true
    CompsCorporateOwner:
      type: object
      description: Corporate owner linked to a sale ownership record
      properties:
        id:
          type: string
          format: uuid
        proprietorName:
          type: string
          nullable: true
        designatedRoleTitle:
          type: string
          nullable: true
        companyId:
          type: string
          format: uuid
          nullable: true
    CompsSaleOwnership:
      type: object
      description: >-
        Sale ownership record with ownership type and corporate owner details
        where available
      properties:
        id:
          type: string
          format: uuid
        ownerType:
          type: string
          enum:
            - Company
            - Council
            - HousingAssociation
            - Private
          nullable: true
        corporateOwners:
          type: array
          items:
            $ref: '#/components/schemas/CompsCorporateOwner'
    CompsLinkedLeaseSummary:
      type: object
      description: Lease summary linked to an investment sale
      properties:
        id:
          type: string
          format: uuid
        startDate:
          type: string
          format: date-time
          nullable: true
        endDate:
          type: string
          format: date-time
          nullable: true
        termYears:
          type: integer
          nullable: true
        termMonths:
          type: integer
          nullable: true
        leaseType:
          type: string
          nullable: true
        rent:
          type: number
          format: double
          nullable: true
        rentAnnually:
          type: number
          format: double
          nullable: true
        rentPerSquareFoot:
          type: number
          format: double
          nullable: true
        tenants:
          type: array
          items:
            $ref: '#/components/schemas/CompsTenant'
    CompsSaleRecord:
      type: object
      description: Sale detail record for a comparable deal
      properties:
        id:
          type: string
          format: uuid
        dealId:
          type: string
          format: uuid
        titleId:
          type: string
          format: uuid
          nullable: true
        dateAcquired:
          type: string
          format: date-time
          nullable: true
        price:
          type: number
          format: double
          nullable: true
        pricePerSquareFoot:
          type: number
          format: double
          nullable: true
        pricePerSquareMeter:
          type: number
          format: double
          nullable: true
        sizeInSquareFeet:
          type: number
          format: double
          nullable: true
        sizeInSquareMeters:
          type: number
          format: double
          nullable: true
        ageType:
          type: string
          nullable: true
        investmentSale:
          type: boolean
          nullable: true
        vacantPossession:
          type: boolean
          nullable: true
        landWithoutPlanning:
          type: boolean
          nullable: true
        totalRentalIncome:
          type: number
          format: double
          nullable: true
        netYield:
          type: number
          format: double
          nullable: true
        dataExchangeUseType:
          type: string
          nullable: true
        dataSourceType:
          type: string
          nullable: true
        modifiedDate:
          type: string
          format: date-time
          nullable: true
        ownerships:
          type: array
          items:
            $ref: '#/components/schemas/CompsSaleOwnership'
        linkedLeases:
          type: array
          items:
            $ref: '#/components/schemas/CompsLinkedLeaseSummary'
        acquisitionAgents:
          type: array
          items:
            $ref: '#/components/schemas/CompsAgent'
        disposingAgents:
          type: array
          items:
            $ref: '#/components/schemas/CompsAgent'
    CompsBreakClause:
      type: object
      nullable: true
      properties:
        id:
          type: string
          format: uuid
        breakClauseDescription:
          type: string
          nullable: true
        breakClauseTermInMonths:
          type: integer
          nullable: true
        breakClauseFromDate:
          type: string
          nullable: true
        breakPenalty:
          type: number
          format: double
          nullable: true
    CompsCovenant:
      type: object
      properties:
        id:
          type: string
          format: uuid
        covenantType:
          type: string
          nullable: true
        partyRole:
          type: string
          nullable: true
        summary:
          type: string
          nullable: true
        financialYear:
          type: string
          nullable: true
        netAssets:
          type: number
          format: double
          nullable: true
        preTaxProfitLoss:
          type: number
          format: double
          nullable: true
        turnover:
          type: number
          format: double
          nullable: true
    CompsLeaseRecord:
      type: object
      description: Lease detail record for a comparable deal
      properties:
        id:
          type: string
          format: uuid
        dealId:
          type: string
          format: uuid
        startDate:
          type: string
          format: date-time
          nullable: true
        endDate:
          type: string
          format: date-time
          nullable: true
        termYears:
          type: integer
          nullable: true
        termMonths:
          type: integer
          nullable: true
        leaseType:
          type: string
          nullable: true
        isPremium:
          type: boolean
          nullable: true
        rent:
          type: number
          format: double
          nullable: true
        rentAnnually:
          type: number
          format: double
          nullable: true
        rentMonthly:
          type: number
          format: double
          nullable: true
        rentPerSquareFoot:
          type: number
          format: double
          nullable: true
        rentPerSquareMeter:
          type: number
          format: double
          nullable: true
        rentFreePeriodInMonths:
          type: integer
          nullable: true
        serviceCharge:
          type: number
          format: double
          nullable: true
        rentDeposit:
          type: number
          format: double
          nullable: true
        fullRepairAndInsuring:
          type: boolean
          nullable: true
        scheduleOfCondition:
          type: string
          nullable: true
        baseRentInterval:
          type: string
          nullable: true
        rentReviewDate:
          type: string
          nullable: true
        landlordTenantAct:
          type: string
          nullable: true
        guarantorName:
          type: string
          nullable: true
        totalFloorAreaInSquareFeet:
          type: number
          format: double
          nullable: true
        useClauses:
          type: array
          items:
            type: string
        amenities:
          type: array
          items:
            type: string
        floorLevel:
          type: array
          items:
            type: string
        isVacant:
          type: boolean
          nullable: true
        dataSourceType:
          type: string
          nullable: true
        modifiedDate:
          type: string
          format: date-time
          nullable: true
        breakClause:
          $ref: '#/components/schemas/CompsBreakClause'
        covenants:
          type: array
          items:
            $ref: '#/components/schemas/CompsCovenant'
        tenants:
          type: array
          items:
            $ref: '#/components/schemas/CompsTenant'
        acquisitionAgents:
          type: array
          items:
            $ref: '#/components/schemas/CompsAgent'
        disposingAgents:
          type: array
          items:
            $ref: '#/components/schemas/CompsAgent'
    CompsListingHistory:
      type: object
      properties:
        id:
          type: string
          format: uuid
        changeDate:
          type: string
          format: date-time
          nullable: true
        price:
          type: number
          format: double
          nullable: true
        pricePerSquareFoot:
          type: number
          format: double
          nullable: true
        pricePerSquareMeter:
          type: number
          format: double
          nullable: true
        rent:
          type: number
          format: double
          nullable: true
        rentAnnually:
          type: number
          format: double
          nullable: true
        rentMonthly:
          type: number
          format: double
          nullable: true
        netYield:
          type: number
          format: double
          nullable: true
        grossYield:
          type: number
          format: double
          nullable: true
        rateableValue:
          type: number
          format: double
          nullable: true
    CompsListingImage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        imageUrl:
          type: string
          nullable: true
        displayOrder:
          type: integer
          nullable: true
    CompsListingResource:
      type: object
      properties:
        id:
          type: string
          format: uuid
        advertUrl:
          type: string
          nullable: true
        brochurePdfUrl:
          type: string
          nullable: true
        images:
          type: array
          items:
            $ref: '#/components/schemas/CompsListingImage'
    CompsListingRecord:
      type: object
      description: Listing detail record for a comparable deal
      properties:
        id:
          type: string
          format: uuid
        dealId:
          type: string
          format: uuid
        firstListingDate:
          type: string
          format: date-time
          nullable: true
        removalDate:
          type: string
          format: date-time
          nullable: true
        daysOnMarket:
          type: integer
          nullable: true
        listingStatus:
          type: string
          nullable: true
        salesNoticeType:
          type: string
          nullable: true
        isVacant:
          type: boolean
          nullable: true
        isInvestment:
          type: boolean
          nullable: true
        totalFloorAreaInSquareFeet:
          type: number
          format: double
          nullable: true
        totalFloorAreaInSquareMeters:
          type: number
          format: double
          nullable: true
        passingRent:
          type: number
          format: double
          nullable: true
        rateableValue:
          type: number
          format: double
          nullable: true
        ratesPayable:
          type: number
          format: double
          nullable: true
        propertyCondition:
          type: string
          nullable: true
        propertyDescription:
          type: string
          nullable: true
        dataExchangeUseType:
          type: string
          nullable: true
        dataSourceType:
          type: string
          nullable: true
        useClass:
          type: array
          items:
            type: string
        amenities:
          type: array
          items:
            type: string
        floorLevel:
          type: array
          items:
            type: string
        proximateLandmarks:
          type: array
          items:
            type: string
        modifiedDate:
          type: string
          format: date-time
          nullable: true
        history:
          type: array
          items:
            $ref: '#/components/schemas/CompsListingHistory'
        resources:
          type: array
          items:
            $ref: '#/components/schemas/CompsListingResource'
        agents:
          type: array
          items:
            $ref: '#/components/schemas/CompsAgent'
    CompsElasticsearchResponse:
      type: object
      description: Search response for comparable deal searches
      required:
        - hits
        - took
        - timed_out
      properties:
        took:
          type: integer
          example: 42
        timed_out:
          type: boolean
          example: false
        _shards:
          type: object
          properties:
            total:
              type: integer
              example: 3
            successful:
              type: integer
              example: 3
            skipped:
              type: integer
              example: 0
            failed:
              type: integer
              example: 0
        hits:
          type: object
          required:
            - total
            - hits
          properties:
            total:
              type: object
              properties:
                value:
                  type: integer
                  example: 123
                relation:
                  type: string
                  enum:
                    - eq
                    - gte
                  example: eq
            max_score:
              type: number
              nullable: true
              example: 1.5
            hits:
              type: array
              items:
                $ref: '#/components/schemas/CompsElasticsearchHit'
    CompsElasticsearchHit:
      type: object
      description: Individual comparable deal search hit
      properties:
        _index:
          type: string
          example: comparable-deals
        _id:
          type: string
          example: f1a0087f-8647-420f-9c72-fbcc9320ecb0
        _score:
          type: number
          nullable: true
          example: 1.5
        _source:
          $ref: '#/components/schemas/ComparableDeal'
    NaturalLanguageCompsQueryResponse:
      type: object
      description: Natural language comparable deal search response
      required:
        - queryDescription
        - elasticsearchQuery
        - results
      properties:
        queryDescription:
          type: string
          example: >-
            Searching for commercial comparable sales in Manchester over 500000
            pounds
        elasticsearchQuery:
          type: object
        results:
          $ref: '#/components/schemas/CompsElasticsearchResponse'
    NaturalLanguageQueryRequest:
      type: object
      description: >
        Request body for natural language property search. The query is
        interpreted by an AI model

        and automatically executed.


        **Best practices:**

        - Be specific about location (town, city, postcode area)

        - Specify property characteristics (residential, commercial, freehold,
        leasehold)

        - Use comparative terms for areas ("over 3 acres", "between 0.5 and 1
        acre")

        - Mention constraints or features if relevant ("with listed building
        constraints")


        **Examples:**

        - "houses in Bolton"

        - "commercial properties in Cambridge over 2 acres"

        - "freehold residential properties in London between 0.5 and 1 acre"

        - "properties with listed building constraints in Oxford"
      required:
        - query
      properties:
        query:
          type: string
          description: Natural language query describing the properties you're looking for
          minLength: 1
          maxLength: 500
          example: commercial properties in Cambridge over 2 acres
        size:
          type: integer
          description: Maximum number of results to return (1-25, default 25)
          minimum: 1
          maximum: 25
          default: 25
          example: 25
    NaturalLanguageQueryResponse:
      type: object
      description: >
        Response from natural language search including a description of the
        query that was

        executed and the search results.


        The queryDescription field enables conversational refinement — read it
        to verify

        the query matched your intent and adjust your next request accordingly.
      required:
        - queryDescription
        - elasticsearchQuery
        - results
      properties:
        queryDescription:
          type: string
          description: >
            Natural language description of the query that was actually
            executed.

            Use this to verify the AI understood your intent and to guide
            refinement.
          example: >-
            Searching for residential terraced properties in Bolton with an area
            between 0.02 and 0.05 acres
        elasticsearchQuery:
          type: object
          description: >
            The generated query that was executed.

            Useful for debugging and for converting a natural language search to
            a structured POST /titles request.
          example:
            query:
              bool:
                must:
                  - match:
                      mainAddress.town: Bolton
                filter:
                  - range:
                      areaInAcres:
                        gte: 0.02
                        lte: 0.05
            size: 10
        results:
          $ref: '#/components/schemas/ElasticsearchResponse'
    ElasticsearchQuery:
      type: object
      description: >
        Property search query.


        **Common Query Types:**

        - `match`: Full-text search with analysis

        - `term`: Exact match on keyword fields

        - `bool`: Combine multiple queries (must, should, filter)

        - `range`: Numeric or date ranges

        - `prefix`: Prefix matching


        See the [Field
        Reference](https://docs.nimbusmaps.co.uk/guides/elasticsearch-schema)
        for available fields.
      required:
        - query
      properties:
        query:
          type: object
          description: |
            Query object describing the search criteria.

            Examples:
            - `{"match": {"mainAddress.town": "Bolton"}}`
            - `{"term": {"tenure": "Freehold"}}`
            - `{"bool": {"must": [...], "filter": [...]}}`
            - `{"range": {"areaInAcres": {"gte": 0.1, "lte": 10}}}`
          example:
            bool:
              must:
                - match:
                    mainAddress.town: Bolton
              filter:
                - term:
                    countryIsoCode: ENG
        size:
          type: integer
          description: 'Number of results to return (enforced max: 25)'
          minimum: 0
          maximum: 25
          default: 10
          example: 10
        from:
          type: integer
          description: Offset for pagination (from + size must be ≤ 10,000)
          minimum: 0
          default: 0
          example: 0
        sort:
          type: array
          description: |
            Sort specification.

            Examples:
            - `[{"areaInAcres": {"order": "desc"}}]`
            - `[{"_score": "desc"}, {"lastModified": "desc"}]`
          items:
            type: object
          example:
            - areaInAcres:
                order: desc
        _source:
          description: >
            Fields to include/exclude in response.


            Examples:

            - `["id", "number", "mainAddress"]` - include only these fields

            - `{"includes": ["mainAddress.*"], "excludes": ["geometry"]}` -
            include/exclude patterns

            - `false` - exclude all fields (return only metadata)
          oneOf:
            - type: boolean
            - type: string
            - type: array
              items:
                type: string
            - type: object
              properties:
                includes:
                  type: array
                  items:
                    type: string
                excludes:
                  type: array
                  items:
                    type: string
          example:
            - id
            - number
            - mainAddress
            - tenure
            - propertyCategory
            - areaInAcres
        track_total_hits:
          type: boolean
          description: >
            Whether to track total hits accurately (can be expensive for large
            result sets).

            Default: true for queries, but limited to 10,000.
          example: true
    ElasticsearchResponse:
      type: object
      description: Standard search response containing matching property records.
      required:
        - hits
        - took
        - timed_out
      properties:
        took:
          type: integer
          description: Time in milliseconds the search took
          example: 42
        timed_out:
          type: boolean
          description: Whether the search timed out
          example: false
        _shards:
          type: object
          description: Shard information
          properties:
            total:
              type: integer
              example: 3
            successful:
              type: integer
              example: 3
            skipped:
              type: integer
              example: 0
            failed:
              type: integer
              example: 0
        hits:
          type: object
          required:
            - total
            - hits
          properties:
            total:
              type: object
              description: Total matching documents
              properties:
                value:
                  type: integer
                  description: Number of matching documents (limited to 10,000)
                  example: 123
                relation:
                  type: string
                  enum:
                    - eq
                    - gte
                  description: Whether value is exact ("eq") or lower bound ("gte")
                  example: eq
            max_score:
              type: number
              nullable: true
              description: Highest relevance score
              example: 1.5
            hits:
              type: array
              description: Array of matching documents
              items:
                $ref: '#/components/schemas/ElasticsearchHit'
    ElasticsearchHit:
      type: object
      description: Individual search result document
      properties:
        _index:
          type: string
          description: Index name
          example: properties
        _id:
          type: string
          description: Document ID (same as id field in _source)
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
        _score:
          type: number
          nullable: true
          description: Relevance score (higher = better match)
          example: 1.5
        _source:
          $ref: '#/components/schemas/TitleDocument'
    TitleDocument:
      type: object
      description: Property title document with comprehensive property data
      required:
        - id
        - number
        - countryIsoCode
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for the title
          example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
        number:
          type: string
          description: Title number (e.g., "LA135828")
          example: LA135828
        countryIsoCode:
          type: string
          enum:
            - ENG
            - SCT
          description: Country ISO code
          example: ENG
        tenure:
          type: string
          enum:
            - Freehold
            - Leasehold
            - NotRegistered
            - OWNERSHIP
            - PROPRIETOR
            - Rent
            - SUPERIOR
            - TENANCY
            - TENANT
            - Unknown
          description: >
            Property tenure type as recorded in the underlying land registry
            data.


            Values such as "OWNERSHIP", "PROPRIETOR", "SUPERIOR", "TENANCY", and
            "TENANT" are

            Scottish‑specific legal or legacy register terms and may not have a
            direct

            equivalent in English property law. They are exposed in uppercase to
            match the

            source system and should be interpreted in the context of Scottish
            title data.
          example: Freehold
        propertyCategory:
          type: string
          enum:
            - Residential
            - Commercial
            - Land
            - Mixed
            - Unknown
            - ''
          description: High-level property category
          example: Residential
        propertyType:
          type: array
          items:
            type: string
            enum:
              - Bungalow
              - Detached
              - Flat
              - HMO
              - Hotels
              - Industrial
              - Land
              - LicensedAndLeisure
              - Offices
              - Other
              - Retail
              - SemiDetached
              - Terraced
          description: Array of specific property types
          example:
            - Terraced
        areaInAcres:
          type: number
          format: double
          description: Property area in acres
          example: 0.023483
        areaInSquareFeet:
          type: number
          format: double
          description: Property area in square feet
          example: 1022.906872
        areaInSquareMeters:
          type: number
          format: double
          description: Property area in square meters
          example: 95.03125
        buildingFootprint:
          type: number
          format: double
          description: Total building footprint area in square feet
          example: 463.321213719283
        siteCoverage:
          type: number
          format: double
          description: Percentage of site covered by buildings
          example: 48.75461637295974
        totalFloorAreaSqFt:
          type: number
          format: double
          description: Total floor area across all floors in square feet
          example: 723.33408
        totalRentalIncome:
          type: number
          format: double
          description: Total annual rental income
          example: 0
        ownerType:
          type: array
          items:
            type: string
          description: >
            Owner categories for this title. English titles with no recorded
            owners

            will include `["Private"]`.
          example:
            - Company
        sizeOfCommercialSpaceInSquareFeet:
          type: number
          format: double
          nullable: true
          description: Size of commercial floor space in square feet
          example: 1500
        lastModified:
          type: string
          format: date-time
          description: Last modification timestamp
          example: '2025-11-15T10:43:35.9171574+00:00'
        mainAddress:
          type: object
          description: Primary address information
          properties:
            fullAddress:
              type: string
              description: Complete formatted address
              example: 30, Penarth Road, Bolton, BL3 5RJ
            buildingNumber:
              type: string
              description: Building number
              example: '30'
            buildingName:
              type: string
              nullable: true
              description: Building name (if applicable)
              example: LAND AT TYNYWERN, MAERDY
            subBuildingNumber:
              type: string
              nullable: true
              description: Sub-building number (e.g., flat number)
              example: Flat 2
            locality:
              type: string
              nullable: true
              description: Locality name
              example: Kensington
            county:
              type: string
              nullable: true
              description: County name
              example: Greater Manchester
            street:
              type: string
              description: Street name
              example: PENARTH ROAD
            town:
              type: string
              description: Town or city
              example: BOLTON
            postcode:
              type: object
              description: Postcode information
              properties:
                value:
                  type: string
                  description: Full postcode
                  example: BL3 5RJ
                outcode:
                  type: string
                  description: Postcode outcode
                  example: BL3
        owners:
          type: array
          description: Array of property owners
          items:
            type: object
            properties:
              companyName:
                type: string
                description: Company name (for corporate owners)
                example: THE NATIONAL ASSEMBLY FOR WALES
              companyNumber:
                type: string
                description: Companies House registration number
                example: '12345678'
              companyAddress:
                type: string
                description: Registered company address
                example: 123 Main Street, London
              ownerCategory:
                type: string
                enum:
                  - Company
                  - Council
                  - HousingAssociation
                  - Private
                description: Category of owner
                example: Company
              ownerInAdministration:
                type: boolean
                description: Whether the owner company is in administration
                example: false
              ownerLateFiling:
                type: boolean
                description: Whether the owner company has late filings at Companies House
                example: false
              titleId:
                type: string
                format: uuid
                description: Reference to the title
        geometry:
          type: object
          description: Geographic data including boundaries and location
          properties:
            area:
              type: number
              format: double
              description: Area in square meters
              example: 269.1138433850564
            centroid:
              type: object
              description: Geographic center point
              properties:
                lat:
                  type: number
                  format: double
                  example: 53.567564623862395
                lon:
                  type: number
                  format: double
                  example: -2.455388735857697
            multiPolygonGeometry:
              type: object
              description: GeoJSON MultiPolygon geometry
              properties:
                type:
                  type: string
                  enum:
                    - MultiPolygon
                coordinates:
                  type: array
                  description: Array of polygon coordinate arrays (GeoJSON format)
                  items:
                    type: array
                    description: Array of linear rings (each polygon)
                    items:
                      type: array
                      description: Array of coordinate pairs
                      items:
                        type: number
                        description: Longitude or latitude value
                      minItems: 2
                      maxItems: 2
            titleId:
              type: string
              format: uuid
        buildings:
          type: array
          description: Array of buildings on the property
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              titleId:
                type: string
                format: uuid
              builtYear:
                type: string
                nullable: true
                description: Construction year or range
                example: 1980-1989
              height:
                type: number
                format: double
                description: Building height in meters
                example: 7.9
              eavesHeight:
                type: number
                format: double
                description: Eaves height in meters
                example: 4.8
              heightAboveSea:
                type: number
                format: double
                description: Height above sea level in meters
                example: 12.8
              footprintAreaSqFt:
                type: number
                format: double
                description: Building footprint area
                example: 329.50211625656
              uprns:
                type: array
                description: Unique Property Reference Numbers associated with building
                items:
                  $ref: '#/components/schemas/UPRN'
        uprns:
          type: array
          description: Array of Unique Property Reference Numbers for this title
          items:
            $ref: '#/components/schemas/UPRN'
        leases:
          type: array
          description: Array of lease information
          items:
            type: object
            properties:
              titleId:
                type: string
                format: uuid
              leaseStartDate:
                type: string
                format: date-time
                example: '1909-05-12T00:00:00Z'
              leaseEndDate:
                type: string
                format: date-time
                example: '2899-05-11T23:00:00Z'
              leaseLength:
                type: integer
                description: Lease length in years
                example: 990
              leaseType:
                type: string
                enum:
                  - Business
                  - Occupational
                  - VirtualFreehold
                description: Type of lease
                example: VirtualFreehold
              tenantInAdministration:
                type: boolean
                description: Whether the tenant company is in administration
                example: false
              tenantLateFiling:
                type: boolean
                description: Whether the tenant company has late filings
                example: false
              rent:
                type: number
                format: double
                nullable: true
                description: Rent amount as recorded in the lease
                example: 15000
              rentAnnually:
                type: number
                format: double
                nullable: true
                description: Annual rent amount
                example: 15000
              rentPerSquareFoot:
                type: number
                format: double
                nullable: true
                description: Rent per square foot
              rentReviewDateText:
                type: string
                nullable: true
                description: Rent review date as free text
              breakClauseDescription:
                type: string
                nullable: true
                description: Description of break clause terms
              breakClauseTermInMonths:
                type: integer
                nullable: true
                description: Break clause term length in months
              breakClauseFromDate:
                type: string
                nullable: true
                description: Date from which the break clause applies
              breakPenalty:
                type: number
                format: double
                nullable: true
                description: Financial penalty for exercising the break clause
        leaseholds:
          type: array
          description: Leasehold titles associated with this freehold title
          items:
            type: object
            properties:
              titleId:
                type: string
                format: uuid
                description: The freehold title ID
              leaseholdTitleId:
                type: string
                format: uuid
                description: The leasehold title ID
              leaseholdTitleNumber:
                type: string
                description: The leasehold title number
                example: EGL123456
              companyName:
                type: string
                description: Leaseholder company name
              companyNumber:
                type: string
                description: Leaseholder company registration number
              companyAddress:
                type: string
                description: Leaseholder company address
              tenantInAdministration:
                type: boolean
                description: Whether leaseholder is in administration
              tenantLateFiling:
                type: boolean
                description: Whether leaseholder has late filings
        sales:
          type: array
          description: Property sale transactions
          items:
            type: object
            properties:
              titleId:
                type: string
                format: uuid
              dateAcquired:
                type: string
                format: date-time
                example: '2024-05-02T23:00:00Z'
              price:
                type: number
                format: double
                description: Sale price in GBP
                example: 368500
              pricePerSquareFoot:
                type: number
                format: double
                description: Price per square foot
                example: 503.4529907430228
        planning:
          type: array
          description: Planning applications associated with the property
          items:
            type: object
            properties:
              titleId:
                type: string
                format: uuid
              reference:
                type: string
                description: Planning application reference
                example: C/99/1017
              authority:
                type: string
                description: Planning authority
                example: Cambridge
              description:
                type: string
                description: Application description
                example: Erection of two storey side extension to house.
              dateSubmitted:
                type: string
                format: date-time
                example: '1999-10-22T00:00:00Z'
              decision:
                type: string
                nullable: true
                description: >-
                  Planning decision text (many values possible due to variations
                  across planning authorities)
                example: Approved with conditions
              decisionStatus:
                type: string
                nullable: true
                description: >
                  Normalised decision status. Use this field for filtering
                  rather than the raw decision text.

                  Falls back to the application status when no decision has been
                  recorded.
                example: GRANTED
              status:
                type: string
                nullable: true
                description: Planning application status (e.g. "DECIDED", "WITHDRAWN")
                example: DECIDED
              decisionDate:
                type: string
                format: date-time
                nullable: true
                description: Date the planning decision was issued
                example: '2008-01-14T00:00:00Z'
              applicationUrl:
                type: string
                format: uri
                nullable: true
                description: URL to planning application details
              agentName:
                type: string
                nullable: true
                description: Agent name
              agentCompany:
                type: string
                nullable: true
                description: Agent company name
              agentAddress:
                type: string
                nullable: true
                description: Agent full address
              applicantName:
                type: string
                nullable: true
                description: Applicant name
              applicantCompany:
                type: string
                nullable: true
                description: Applicant company name
              applicantAddress:
                type: string
                nullable: true
                description: Applicant full address
              units:
                type: integer
                description: Number of units
                example: 1
              year:
                type: integer
                description: Year of application
                example: 1999
        constraints:
          type: array
          description: Environmental and planning constraints affecting the property
          items:
            type: object
            properties:
              titleId:
                type: string
                format: uuid
              name:
                type: string
                description: Constraint type name
                enum:
                  - AgriculturalLandClassification
                  - AncientWoodland
                  - AreaOfNaturalBeauty
                  - Battlefield
                  - ConservationArea
                  - CountrysideAndRightOfWay
                  - FloodZone
                  - GreenBelt
                  - HistoricParkAndGarden
                  - ListedBuilding
                  - LocalNatureReserve
                  - LondonOtherOpenSpace
                  - NationalNatureReserve
                  - NationalPark
                  - PublicRightsOfWay
                  - Radon
                  - Ramsar
                  - ScheduledMonument
                  - SiteOfSpecialScientificInterest
                  - SpecialAreaOfConservation
                  - SpecialProtectionArea
                  - SurfaceWaterFlooding
                  - WorldHeritageSite
                example: SiteOfSpecialScientificInterest
              categoryName:
                type: string
                nullable: true
                description: Constraint category or severity (varies by constraint type)
                example: 100m Impact Risk Zone
              isOverlapping:
                type: boolean
                description: Whether constraint overlaps with property
                example: true
              overlapPercentage:
                type: number
                format: double
                description: Percentage of property affected
                example: 100
    UPRN:
      type: object
      description: Unique Property Reference Number with associated property data
      properties:
        uprnIdentifier:
          type: string
          format: uuid
          description: UUID identifier for the UPRN record
          example: 2a13433e-3119-418d-b937-2bd199568038
        address:
          type: string
          nullable: true
          description: Full address string
          example: 9, Blackthorn Close, Cambridge, CB4 1FZ
        buildingNumber:
          type: string
          nullable: true
          description: Building number
          example: '9'
        buildingName:
          type: string
          nullable: true
          description: Building name (if applicable)
        subBuildingNumber:
          type: string
          nullable: true
          description: Sub-building or flat number
        street:
          type: string
          nullable: true
          description: Street name
          example: BLACKTHORN CLOSE
        locality:
          type: string
          nullable: true
          description: Locality name
        town:
          type: string
          nullable: true
          description: Town or city
          example: CAMBRIDGE
        county:
          type: string
          nullable: true
          description: County
        postcode:
          type: object
          nullable: true
          description: Postcode information
          properties:
            value:
              type: string
              description: Full postcode
              example: CB4 1FZ
            outcode:
              type: string
              description: Postcode outcode
              example: CB4
        floor:
          type: string
          nullable: true
          description: Floor level(s) as comma-separated string
          example: 0, 1
        floorAreaSqFt:
          type: number
          format: double
          nullable: true
          description: Floor area in square feet
          example: 731.95
        rateableValue:
          type: number
          format: double
          nullable: true
          description: Rateable value for business rates
          example: 5000
        hasBuildings:
          type: boolean
          description: Whether UPRN has associated buildings
          example: true
        useClass:
          type: array
          nullable: true
          items:
            type: string
          description: UK Planning Use Class codes (simple string array)
          example:
            - C3
        useClasses:
          type: array
          nullable: true
          description: Structured UK Planning Use Class data with descriptions
          items:
            type: object
            properties:
              code:
                type: string
                description: Planning use class code
                example: C3
              description:
                type: string
                nullable: true
                description: Generic planning use class description
                example: Single-family homes
              classificationDescription:
                type: string
                nullable: true
                description: Specific AddressBase/VOA classification description
                example: Detached
        rooms:
          type: object
          nullable: true
          description: Room counts
          properties:
            beds:
              type: integer
              nullable: true
              example: 2
            baths:
              type: integer
              nullable: true
              example: 1
            receps:
              type: integer
              nullable: true
              description: Reception rooms
              example: 2
        epc:
          type: object
          nullable: true
          description: Energy Performance Certificate data
          properties:
            currentRating:
              type: string
              enum:
                - A
                - A+
                - B
                - B+
                - C
                - C+
                - Carbon Neu
                - D
                - D+
                - E
                - E+
                - F
                - F+
                - G
              description: Current EPC rating
              example: C
            potentialRating:
              type: string
              enum:
                - A
                - A+
                - B
                - B+
                - C
                - C+
                - Carbon Neu
                - D
                - D+
                - E
                - E+
                - F
                - F+
                - G
              description: Potential EPC rating
              example: B
        vOA:
          type: array
          nullable: true
          description: Valuation Office Agency property type codes
          items:
            type: object
            properties:
              typeCode:
                type: string
                description: VOA special category code
                example: '203'
              typeDescription:
                type: string
                description: VOA property type description
                example: Offices (Inc Computer Centres)
    HealthResponse:
      type: object
      required:
        - status
        - timestamp
      properties:
        status:
          type: string
          enum:
            - healthy
            - unhealthy
          example: healthy
        timestamp:
          type: string
          format: date-time
          example: '2026-01-10T12:34:56Z'
        version:
          type: string
          example: 2.0.0
    AddressSearchResponse:
      type: object
      description: Address search results in JSON:API format
      required:
        - data
      properties:
        jsonapi:
          type: object
          description: JSON:API version information
          properties:
            version:
              type: string
              default: '1.0'
              example: '1.0'
        data:
          type: array
          description: |
            Array of address search result resources (maximum 10).
            Results that cannot be resolved to a UPRN are excluded.
          maxItems: 10
          items:
            $ref: '#/components/schemas/AddressSearchResult'
        meta:
          type: object
          description: Response metadata
          properties:
            requestId:
              type: string
              nullable: true
              description: Request identifier for traceability
              example: 550e8400-e29b-41d4-a716-446655440000
            totalResults:
              type: integer
              nullable: true
              description: >-
                Number of results returned (after filtering deleted/unmatched
                UPRNs)
              example: 2
            maxResults:
              type: integer
              nullable: true
              description: Maximum results requested (always 10 when called via this API)
              example: 10
    AddressSearchResult:
      type: object
      description: A single address search result resource in JSON:API format
      required:
        - type
        - attributes
      properties:
        type:
          type: string
          description: Resource type
          default: searchIdResult
          example: searchIdResult
        attributes:
          type: object
          required:
            - fullAddress
          properties:
            uprnRef:
              type: object
              nullable: true
              description: Internal UPRN record reference
              properties:
                id:
                  type: string
                  format: uuid
                  description: UPRN internal UUID (not the numeric UPRN)
                  example: 9d3e873f-7998-47f0-aaa2-a74a563d2654
            titleRef:
              type: object
              nullable: true
              description: >
                Associated title reference. `null` when no title is linked to
                this UPRN.

                When multiple titles are linked, the best match is selected by
                tenure priority

                (Freehold > OWNERSHIP > PROPRIETOR > SUPERIOR) then smallest
                area.
              properties:
                id:
                  type: string
                  format: uuid
                  nullable: true
                  description: Title UUID
                  example: 4665bc0b-69d6-4e04-8117-48bb25dfba2c
                tenure:
                  type: string
                  nullable: true
                  description: Title tenure
                  example: Freehold
            fullAddress:
              type: string
              description: Complete formatted address
              example: 30, Penarth Road, Bolton, BL3 5RJ
            latitude:
              type: number
              nullable: true
              description: Latitude coordinate (WGS84)
              example: 53.582
            longitude:
              type: number
              nullable: true
              description: Longitude coordinate (WGS84)
              example: -2.428
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code
              example: PAGINATION_LIMIT
            message:
              type: string
              description: Human-readable error message
              example: from + size cannot exceed 10,000
            correlationId:
              type: string
              format: uuid
              description: Unique correlation ID for tracing
              example: 550e8400-e29b-41d4-a716-446655440000
  responses:
    BadRequest:
      description: Invalid query or parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            sizeLimit:
              summary: Size limit exceeded
              value:
                error:
                  code: SIZE_LIMIT
                  message: size cannot exceed 25
                  correlationId: 550e8400-e29b-41d4-a716-446655440000
            paginationLimit:
              summary: Pagination limit exceeded
              value:
                error:
                  code: PAGINATION_LIMIT
                  message: >-
                    from + size cannot exceed 10,000. Use search_after for deep
                    pagination.
                  correlationId: 550e8400-e29b-41d4-a716-446655440000
            invalidQuery:
              summary: Invalid query
              value:
                error:
                  code: INVALID_QUERY
                  message: Query validation failed
                  correlationId: 550e8400-e29b-41d4-a716-446655440000
    Unauthorized:
      description: Unauthorized - missing or invalid authentication
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: INVALID_TOKEN
              message: >-
                Authentication required: provide either valid
                Ocp-Apim-Subscription-Key or OAuth Bearer token
              correlationId: 550e8400-e29b-41d4-a716-446655440000
    Forbidden:
      description: Forbidden - insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: INSUFFICIENT_SCOPE
              message: >-
                Token does not have required scope 'search.titles' or invalid
                subscription key
              correlationId: 550e8400-e29b-41d4-a716-446655440000
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: NOT_FOUND
              message: Title not found
              correlationId: 550e8400-e29b-41d4-a716-446655440000
    TooManyRequests:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: RATE_LIMITED
              message: Too many requests. Please try again in 30 seconds.
              correlationId: 550e8400-e29b-41d4-a716-446655440000
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: INTERNAL_ERROR
              message: An unexpected error occurred
              correlationId: 550e8400-e29b-41d4-a716-446655440000
    GatewayTimeout:
      description: Backend service timeout
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: BACKEND_TIMEOUT
              message: >-
                The search request took too long. Please try a more specific
                query.
              correlationId: 550e8400-e29b-41d4-a716-446655440000
