openapi: 3.0.3

info:
  title: Texas Estate Records API
  version: 1.0.0
  description: >
    Structured Texas estate & heirship records — affidavits of heirship, small
    estate affidavits, affidavits of death, and executor's/administrator's/
    personal representative's deeds — sourced from county records and parsed into
    clean JSON.


    **Free:** browsing and searching the index (`/records`, `/records/{county}/{docNumber}`,
    `/counties`, `/document-types`) is unmetered — it's the demo and the funnel.


    **Metered:** only `GET /records/{county}/{docNumber}/details` counts against your
    plan quota. It returns the parsed payload (decedent, heirs, normalized
    addresses) plus the original document page images. One successful call = one
    record.


    **Auth on RapidAPI:** send your key as `X-RapidAPI-Key`; RapidAPI proxies the
    request to this API. (Direct callers must present `X-RapidAPI-Proxy-Secret`.)
  contact:
    name: Texas Estate Records
    url: https://texasestaterecords.com
  license:
    name: Proprietary

servers:
  - url: https://texasestaterecords.com/api/v1

security:
  - rapidApiProxy: []

tags:
  - name: Records
    description: Search and retrieve estate records.
  - name: Reference
    description: Coverage and taxonomy.

paths:
  /records:
    get:
      tags: [Records]
      operationId: listRecords
      summary: List / search records (free)
      description: >
        Latest records for a county and/or document type, newest first. Returns
        pre-processed index fields only — no parsed details. Unmetered.
      parameters:
        - $ref: "#/components/parameters/County"
        - $ref: "#/components/parameters/Type"
        - $ref: "#/components/parameters/From"
        - $ref: "#/components/parameters/To"
        - $ref: "#/components/parameters/Query"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: A page of records.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RecordList"
        "400":
          $ref: "#/components/responses/BadRequest"

  /records/{county}/{docNumber}:
    get:
      tags: [Records]
      operationId: getRecord
      summary: Get a record's index data (free)
      description: >
        The pre-processed record (recording metadata, parties, legal
        description, property city). No parsed/LLM fields. Unmetered.
      parameters:
        - $ref: "#/components/parameters/CountyPath"
        - $ref: "#/components/parameters/DocNumberPath"
      responses:
        "200":
          description: The record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RecordSummary"
        "404":
          $ref: "#/components/responses/NotFound"

  /records/{county}/{docNumber}/details:
    get:
      tags: [Records]
      operationId: getRecordDetails
      summary: Get a record's parsed details + pages (metered)
      description: >
        **This is the metered endpoint — one successful call counts as one record
        against your plan quota.** Returns the parsed payload (decedent, heirs/
        distributees with relationships, normalized property address,
        consideration, key dates) plus URLs for the original page images. Parsing
        runs on first request and is cached, so repeat calls are fast.
      x-metered: true
      parameters:
        - $ref: "#/components/parameters/CountyPath"
        - $ref: "#/components/parameters/DocNumberPath"
      responses:
        "200":
          description: The parsed record details.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RecordDetails"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/QuotaExceeded"

  /counties:
    get:
      tags: [Reference]
      operationId: listCounties
      summary: List covered counties (free)
      responses:
        "200":
          description: Covered counties with record counts.
          content:
            application/json:
              schema:
                type: object
                properties:
                  counties:
                    type: array
                    items:
                      $ref: "#/components/schemas/County"

  /document-types:
    get:
      tags: [Reference]
      operationId: listDocumentTypes
      summary: List document types (free)
      responses:
        "200":
          description: Supported document categories.
          content:
            application/json:
              schema:
                type: object
                properties:
                  documentTypes:
                    type: array
                    items:
                      $ref: "#/components/schemas/DocumentType"

components:
  securitySchemes:
    rapidApiProxy:
      type: apiKey
      in: header
      name: X-RapidAPI-Proxy-Secret
      description: >
        Shared secret injected by the RapidAPI proxy; the backend rejects
        requests without it. RapidAPI consumers authenticate with X-RapidAPI-Key
        and never send this header directly.

  parameters:
    County:
      name: county
      in: query
      required: false
      description: County slug (e.g. `tarrant`). See `/counties`.
      schema: { type: string, example: tarrant }
    Type:
      name: type
      in: query
      required: false
      description: Document category. Case-insensitive.
      schema:
        $ref: "#/components/schemas/Category"
    From:
      name: from
      in: query
      required: false
      description: Earliest recorded date (inclusive), `YYYY-MM-DD`.
      schema: { type: string, format: date, example: "2026-06-01" }
    To:
      name: to
      in: query
      required: false
      description: Latest recorded date (inclusive), `YYYY-MM-DD`.
      schema: { type: string, format: date, example: "2026-06-15" }
    Query:
      name: q
      in: query
      required: false
      description: Free-text match on party (grantor/grantee) names.
      schema: { type: string, example: "BATTISTONI" }
    Cursor:
      name: cursor
      in: query
      required: false
      description: Opaque cursor from `nextCursor` for the next page.
      schema: { type: string }
    Limit:
      name: limit
      in: query
      required: false
      description: Page size.
      schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
    CountyPath:
      name: county
      in: path
      required: true
      description: County slug (e.g. `tarrant`).
      schema: { type: string, example: tarrant }
    DocNumberPath:
      name: docNumber
      in: path
      required: true
      description: Recorder's instrument/document number (e.g. `D226115302`).
      schema: { type: string, example: D226115302 }

  responses:
    BadRequest:
      description: Invalid request parameters.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: No such record.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    QuotaExceeded:
      description: Plan quota exceeded (handled by RapidAPI).
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

  schemas:
    Category:
      type: string
      enum:
        - AFFIDAVIT_OF_HEIRSHIP
        - SMALL_ESTATE_AFFIDAVIT
        - AFFIDAVIT_OF_DEATH
        - EXECUTOR_DEED
        - ADMINISTRATOR_DEED
        - PERSONAL_REPRESENTATIVE_DEED
        - WILL
        - OTHER

    RecordSummary:
      type: object
      description: Pre-processed index record (free surface).
      properties:
        id: { type: string, example: cmql0abcd0000 }
        source: { type: string, example: publicsearch.us }
        state: { type: string, example: TX }
        county: { type: string, example: tarrant }
        docNumber: { type: string, example: D226115302 }
        docId: { type: integer, example: 296144050 }
        category: { $ref: "#/components/schemas/Category" }
        docType: { type: string, description: "Portal's raw type label", example: "AFFIDAVIT OF HEIRSHIP" }
        recordedDate: { type: string, format: date, example: "2026-06-12" }
        instrumentDate: { type: string, format: date, nullable: true, example: "2026-06-10" }
        grantors:
          type: array
          items: { type: string }
          example: ["BATTISTONI JAMES LOUIS ESTATE"]
        grantees:
          type: array
          items: { type: string }
        legalDescription: { type: string, nullable: true }
        propertyCity: { type: string, nullable: true, example: Benbrook }
        pageCount: { type: integer, example: 5 }
        parsed:
          type: boolean
          description: Whether parsed details have been generated and cached.
        detailsUrl:
          type: string
          format: uri
          description: Link to the metered details endpoint for this record.
      required: [id, county, docNumber, category, recordedDate, parsed, detailsUrl]

    RecordDetails:
      allOf:
        - $ref: "#/components/schemas/RecordSummary"
        - type: object
          description: Parsed payload (metered surface).
          properties:
            documentType:
              type: string
              description: Document kind as classified from the text.
            decedent:
              nullable: true
              $ref: "#/components/schemas/Decedent"
            parties:
              type: object
              properties:
                grantors: { type: array, items: { $ref: "#/components/schemas/Party" } }
                grantees: { type: array, items: { $ref: "#/components/schemas/Party" } }
            people:
              type: array
              description: Every named person with their role and relationship.
              items: { $ref: "#/components/schemas/Person" }
            property: { $ref: "#/components/schemas/Property" }
            consideration: { type: string, nullable: true }
            keyDates:
              type: object
              properties:
                recorded: { type: string, nullable: true }
                instrument: { type: string, nullable: true }
                death: { type: string, nullable: true }
                other: { type: string, nullable: true }
            typeSpecific:
              type: object
              description: Fields unique to this document type.
              additionalProperties: true
            pages:
              type: array
              items:
                type: object
                properties:
                  page: { type: integer }
                  imageUrl: { type: string, format: uri }

    Decedent:
      type: object
      properties:
        name: { type: string }
        dateOfDeath: { type: string, nullable: true }
        placeOfDeath: { type: string, nullable: true }
        residenceAddress: { type: string, nullable: true }
        maritalStatus: { type: string, nullable: true }

    Party:
      type: object
      properties:
        name: { type: string }
        role: { type: string, nullable: true }
        address: { type: string, nullable: true }

    Person:
      type: object
      properties:
        name: { type: string }
        role: { type: string, nullable: true }
        relationship: { type: string, nullable: true }
        address: { type: string, nullable: true }

    Property:
      type: object
      properties:
        legalDescription: { type: string, nullable: true }
        address: { type: string, nullable: true, description: "Normalized street address" }
        addressComponents:
          type: object
          nullable: true
          properties:
            line1: { type: string, nullable: true }
            city: { type: string, nullable: true }
            state: { type: string, nullable: true }
            zip: { type: string, nullable: true }
        addressSource:
          type: string
          nullable: true
          enum: [stated, decedent_residence, party_residence, none]
          description: Where the address came from (stated vs inferred).

    RecordList:
      type: object
      properties:
        items:
          type: array
          items: { $ref: "#/components/schemas/RecordSummary" }
        nextCursor:
          type: string
          nullable: true
          description: Pass as `cursor` to fetch the next page; null when no more.
        total:
          type: integer
          description: Total records matching the filters.
      required: [items, nextCursor]

    County:
      type: object
      properties:
        name: { type: string, example: Tarrant }
        slug: { type: string, example: tarrant }
        state: { type: string, example: TX }
        recordCount: { type: integer, example: 210 }

    DocumentType:
      type: object
      properties:
        code: { $ref: "#/components/schemas/Category" }
        label: { type: string, example: "Affidavit of Heirship" }

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string, example: not_found }
            message: { type: string, example: "No record tarrant/D000 found." }
      required: [error]
