---
title: "Practitioner read on the public record"
method: POST
path: "/api/locus-practitioner-read"
tags: ["data", "search", "real-estate", "property", "property-research", "due-diligence", "public-records", "x402", "practitioner-read", "land-deal", "due-diligence", "assessment-appeal", "category:economics"]
---

# Practitioner read on the public record

`POST /api/locus-practitioner-read`

## Request

- Base URL: `https://api.locus.report`
- URL: `https://api.locus.report/api/locus-practitioner-read`
- Auth: one of:
  - API key in header `PAYMENT-SIGNATURE`
  - API key in header `Authorization`

Hedged prose from cited property records for one place. Each reading names the confirming record; an optional question sets the audience and focus. No value, person motive, or buildability finding. Allow at least 60 seconds (90 recommended). If disconnected, retry the same request for the same payer with fresh valid payment within 15 minutes; no charge occurs for the disconnected run. npx @velinussage/locus-agent-skill add.

## Request body

- object: US street address, optional free-text question (never an instruction: read only for a persona and a focus group (records, hazards, permits, taxes, zoning) that weights which readings are offered), optional caller persona (shapes selection through each reading's audiences), optional comparison radius.
  - `address` string, required: US street address for the subject property.
  - `question` string: Optional free-text question. Never read as an instruction and never sent to the reading-selection model or the prose model. Read separately by Jev for two enum signals only: the caller's persona (when `persona` is not declared) and a focus group (records, hazards, permits, taxes, zoning) that weights reading selection.
  - `persona` 'buyer' | 'owner' | 'seller' | 'investor' | 'renter' | 'lender' | 'developer' | 'commercial_tenant': Optional caller-declared persona; wins over any inference from `question`. Shapes reading selection: each catalogue reading names its audiences, and Jev sees the persona beside them.
  - `radiusMeters` integer: Optional comparison radius for the underlying free-tool lanes; default 400m, max 1500m.

## Response `200`

Successful paid artifact, or a free pre-charge diagnostic when the request is outside current coverage.

- union: A `kind`-discriminated union: `practitioner_read` (charged:true; catalogueVersion, persona, focus, questionUse { personaFrom, focusGroup } showing how the optional question was used, readings[] with a categorical matched/partial status and the record that confirms or would confirm each, recordRefs, catalogue { evaluated, gated } saying how many catalogue readings this route can currently evaluate vs how many are gated off pending a slot adapter (those are listed in readingsNotEvaluated), hedged prose, caveats) for a resolved address, or `practitioner_read_diagnostic` (charged:false; readings/recordRefs still attached when known) when the address is unresolved, the reading-selection engine is unavailable, the prose composer is unavailable, or the composed prose fails the guardrail. Flat price: an empty reading set is still charged.
  - object
    - `kind` 'practitioner_read', required
    - `generatedAt` string, required
    - `address` string, required
    - `catalogueVersion` string, required
    - `persona` object, required
      - `role` 'buyer' | 'owner' | 'seller' | 'investor' | 'renter' | 'lender' | 'developer' | 'commercial_tenant'
      - `source` 'caller' | 'inferred' | 'none', required
    - `focus` object, required
      - `group` 'records' | 'hazards' | 'permits' | 'taxes' | 'zoning'
      - `source` 'inferred' | 'none', required
    - `questionUse` object, required
      - `personaFrom` 'question' | 'caller' | 'none', required
      - `focusGroup` 'records' | 'hazards' | 'permits' | 'taxes' | 'zoning', nullable, required
    - `readings` object[], required
      - `id` string, required
      - `title` string, required
      - `status` 'matched' | 'partial', required
      - `missingRecord` string
      - `readingText` string, required
      - `recordRefs` string[], required
      - `checkNext` string, required
    - `readingsNotEvaluated` object[], required
      - `id` string, required
      - `reason` string, required
    - `catalogue` object, required
      - `evaluated` integer, required
      - `gated` integer, required
    - `recordRefs` string[], required
    - `permitMix` object, required
      - `available` boolean, required
      - `coverage` 'covered' | 'not_covered' | 'unresolved', required
      - `reason` string
      - `classColumn` string
      - `useColumn` string
      - `currentCounts` object
      - `priorCounts` object
      - `currentTotal` number
      - `priorTotal` number
      - `unmatchedClassShare` number
      - `missingCostShare` number
      - `heavyUse` object[]
        - `useEnum` string, required
        - `estCostUsd` number
        - `issuedDate` string
        - `recordId` string
      - `truncated` boolean
    - `waterContext` object, required
      - `servingWaterPwsid` string
      - `servingWaterSystemName` string
      - `violationCount12mo` number, required
      - `violations` object[], required
        - `pwsid` string
        - `violationName` string
        - `violationCategory` string
        - `date` string
      - `pfasDetectionCount` number, required
      - `pfasGap` string
      - `pfasSamplePeriod` object
        - `from` string
        - `to` string
      - `citations` string[], required
      - `nonInference` string, required
    - `timing` object, required
      - `totalMs` number, required
      - `recordsCached` boolean, required
      - `stages` object, required
    - `prose` string, required
    - `caveats` string[], required
    - `verifyNext` object[], required
      - `label` string, required
      - `url` string, required
    - `charged` true, required
  - object
    - `kind` 'practitioner_read_diagnostic', required
    - `reason` 'address_unresolved' | 'engine_unavailable' | 'prose_unavailable' | 'prose_guardrail' | 'retry_limited' | 'payment_authorization_expired', required
    - `detail` string, required
    - `guardrailReason` string
    - `readings` union[]
      - union
        - object
          - `id` string, required
          - `title` string, required
          - `status` 'matched' | 'partial', required
          - `missingRecord` string
          - `readingText` string, required
          - `recordRefs` string[], required
          - `checkNext` string, required
        - object
          - `id` string, required
          - `status` 'matched' | 'partial', required
          - `readingText` string, required
          - `checkNext` string, required
    - `recordRefs` string[]
    - `caveats` string[], required
    - `charged` false, required

## Other responses

- `400`: Invalid request body.
- `402`: Payment Required. For x402 v2, repeat the identical request with a valid PAYMENT-SIGNATURE header (legacy X-PAYMENT is accepted for compatibility), or use Authorization: Payment for MPP Tempo when advertised in x-payment-info.offers.

---

[API](https://skmtc.dev/locus/apis/locus-api.md) · [All operations](https://skmtc.dev/locus/apis/locus-api/llms.txt) · [OpenAPI document](https://skmtc.dev/locus/apis/locus-api/revisions/ba7786b74adb?raw)
