Property Deed & Lien Records Search

Design notes for a recorder-index API that finds every deed, mortgage, release, lien, satisfaction, assignment, and easement recorded for a company or LLC across 128 jurisdictions in 19 states and territories — plus NYC address and BBL search — and returns one normalized JSON schema without document images.

Property Deed & Lien Records Search

Design notes for property-deed-records, a county recorder records API for searching public deed and lien indexes. Give it a company or LLC name and it finds the recorded documents associated with that entity in any covered jurisdiction; in Manhattan, Bronx, Brooklyn, and Queens, it also accepts a street address or parcel BBL. The output is normalized JSON for deed transfers, mortgages, releases, liens, satisfactions, assignments, easements, and related instruments — grantors, grantees, recording dates, instrument numbers, book/page references, parcel or legal data where published, consideration where published, and cross-references. It returns recorder index metadata, never document images.

What this is

A property’s recorded history does not live in the assessor record. The assessor tells you what is on the tax roll now: parcel, assessed value, characteristics, and an owner name where the jurisdiction publishes it. The recorder index tells a different story: the deed that transferred the property, the mortgage that financed it, the later assignment, the release or satisfaction, a recorded lien, and the easements or other instruments attached to the chain.

That history is public, but it is fragmented by recording office. Field names differ. One office identifies a filing by instrument number; another uses book and page or liber and folio. Document types arrive as local abbreviations and office-specific labels. Some indexes expose a parcel or legal description and some do not. Some publish consideration and cross-references; others publish neither. A developer who wants a property deed records API across several offices otherwise has to learn and maintain each office’s vocabulary separately.

This Actor turns those indexes into one output contract. Each matching entry becomes a document row with both a normalized document_type and the office’s document_type_raw, plus the source identifiers and parties needed to verify and correlate it. Each accepted lookup ends with a free lookup_summary row that says whether documents were delivered, no records matched, the lookup type is unsupported, the jurisdiction is outside coverage, the source failed, or the run budget was exhausted.

The main workflow is entity-level portfolio research. A company or LLC may appear as grantor on one filing, grantee on another, borrower on a mortgage, or assignor on a later instrument. partyLookups searches both sides of the recorder’s party index and returns the complete matching recorded-document set up to the caller’s cap. That makes it possible to ask, “find every recorded document for this company,” and receive one normalized dataset instead of a collection of office-specific search pages.

Coverage is broad but still explicitly finite and enumerable: 128 jurisdictions across 19 states and territories. That count spans counties, registry districts, towns, a city, statewide Alaska, and territory-wide U.S. Virgin Islands rather than implying uniform county or statewide coverage. The COVERAGE key-value artifact written by each completed run is the authoritative per-jurisdiction capability snapshot for that run. Staten Island is not in ACRIS and is not covered. Washington, DC is also excluded because its recorder search requires a registered account, and this Actor does not create or manage customer accounts.

Lookup support is not uniform, and the distinction is part of the contract:

State or territory Jurisdictions Party search Address / parcel search Notes
Alaska (AK) 1 yes no / no Statewide across recording districts
Arkansas (AR) 1 yes no / no County route
Colorado (CO) 5 yes no / no County and equivalent recorder routes
Connecticut (CT) 6 yes no / no Town routes; coverage is town-level, not county-wide
Delaware (DE) 1 yes no / no County route
Illinois (IL) 1 yes no / no County route
Massachusetts (MA) 11 yes no / no Registry districts; broad-search guidance below
Maine (ME) 1 yes no / no Registry route
Michigan (MI) 12 yes no / no County routes
New Mexico (NM) 3 yes no / no County routes
New York (NY) 6 yes NYC only / NYC only Address and BBL search cover Manhattan, Bronx, Brooklyn, and Queens; party-only coverage also includes county routes outside NYC
Ohio (OH) 20 yes no / no County routes
Pennsylvania (PA) 5 yes no / no County routes
Rhode Island (RI) 1 yes no / no City route; coverage is city-level, not statewide or county-wide
South Carolina (SC) 3 yes no / no County routes
Texas (TX) 48 yes no / no County routes
Utah (UT) 1 yes no / no County route
U.S. Virgin Islands (VI) 1 yes no / no Territory-wide
Vermont (VT) 1 yes no / no Town route; coverage is town-level, not county-wide

Company and entity party search works everywhere in current coverage. Address and parcel search are NYC-only. That asymmetry is not buried in a footnote because it determines whether an agent can construct a valid call.

Why I built it this way

The recorder index is its own product

I already maintain a county property records API for assessor data. It would have been easy to treat recorded documents as another optional history array on the assessor record. I decided against that because the two datasets answer different questions and have different failure modes.

An assessor lookup starts with a property identifier: address or parcel. A recorder lookup often starts with a party: show every recorded instrument associated with an LLC, including documents across multiple parcels. The assessor is a current property snapshot; the recorder is an event index. Combining them would make callers pay for and parse fields they did not ask for, and it would hide the fact that recorder coverage and assessor coverage grow independently.

Keeping the Actor recorder-specific also creates a clean refusal: it does not return valuations, tax bills, building characteristics, listing data, or document images. It returns the index needed to identify and verify recorded instruments. If the scanned instrument itself is required, the instrument number or book/page reference is the handoff to the official ordering path.

Party search is the portable lookup

The initial concept accepted address, parcel, and party inputs equally. Source research showed that would overstate the product. Across recorder offices, grantor/grantee indexing is the common denominator. Portable address and parcel indexing is not.

NYC is the exception because ACRIS publishes a street index and borough-block-lot relationships alongside its document and party tables. That supports address → BBL → documents and direct BBL → documents. The other covered offices provide party search but no equivalent portable property-key search that can be promised consistently. Alaska likewise exposes a statewide grantor/grantee index without an address index.

So the interface preserves all three lookup fields but tells the truth about their support matrix. partyLookups is the broad path and is framed as company/entity portfolio search. addresses and parcelLookups are useful, but only in the four ACRIS boroughs. An address sent to Dallas is not silently converted into a no-match. It returns a free summary saying that address search is unsupported there and that the caller should use a company or entity party lookup instead.

A no-match and a coverage miss are different facts

An empty list is not an adequate recorder-search result. It can mean at least four things: the source searched successfully and found no documents; the requested office is not covered; the office is covered but the requested lookup type is not; or the source failed before it could answer. Those outcomes drive different next actions, especially for an AI agent.

Every lookup therefore emits a free lookup_summary row after any document rows. Its status, reason, records_returned, total_matches, and coverage_miss fields make the result explicit. A caller can treat no_matches as a real source answer, route unsupported to a supported lookup type, log coverage_miss as a product gap, and retry a transient source_error without conflating them.

That summary is free because it is control-plane data, not the customer result. Charging for an unsupported jurisdiction would make the customer fund the coverage gap. Charging for a no-match would make broad entity portfolio searches unpredictable. The billable unit is a document index row that was actually delivered.

Normalize the type, preserve the source label

Recorder offices do not share a document taxonomy. A mortgage may be labeled with an abbreviation in one office and a longer local label in another. Releases and satisfactions may be separate classes or variants of the same local category. Flattening those labels without preserving the original would make the output easier to aggregate but harder to audit.

Each document row therefore carries both fields. document_type maps the entry into one of eight stable classes: deed, mortgage, release, lien, satisfaction, assignment, easement, or other. document_type_raw retains the office’s published text. Pipelines can filter and aggregate on the normalized value while retaining the raw label for source verification and edge cases.

The same restraint applies to optional fields. If an office does not publish consideration, parcel linkage, legal text, book/page, or a stable detail URL, the corresponding field is null or an empty array. The Actor does not infer consideration, manufacture cross-references, or treat a party name as proof that two entities are the same.

Freshness belongs in the coverage claim

ACRIS itself is live, but the machine-readable NYC Open Data extract used here refreshes monthly. A property deed history by NYC address can therefore lag the live ACRIS search by up to a month. That is material for monitoring workflows, so it belongs next to the coverage claim rather than in an operational footnote.

The upside of the extract is a structured, joinable index beginning in 1966 across the four covered boroughs. The tradeoff is freshness. A caller researching established document history usually benefits from the depth; a caller looking for a filing recorded yesterday should verify against live ACRIS. The output includes retrieved_at, and index_coverage_start is populated where the source states a reliable start date.

Exclusion is better than nominal coverage

Washington, DC illustrates why a coverage count should represent reliable delivery rather than code that exists or a website that can be opened. Its recorder search requires a registered account, and this Actor does not create or manage customer accounts.

Massachusetts was excluded at launch for a different reliability problem, but is now covered across 11 registry districts. Its public search sits behind a bot-defense interstitial that plain HTTP cannot clear, so the whole search is driven inside one browser session instead. The boundary remains deliberate: a nominally supported source that frequently returns a defensive page is worse than an honest coverage miss because it encourages callers to depend on a capability that is not dependable. New jurisdictions move into the coverage table only when they can answer the advertised lookup path reliably enough to sell.

Honest partials for broad Massachusetts searches

The Massachusetts source limits very broad searches to its first 1,000 records. When a query reaches that boundary, the Actor automatically narrows it using recent date windows so it can still return useful results. Because the source does not expose enough information to establish the complete count after that narrowing, the summary reports total_matches as unknown rather than presenting a truncated slice as complete. Callers can supply dateFrom and dateTo when they need a controlled window. This is the same honest-partial principle used elsewhere in the contract: preserve useful evidence, but make the boundary explicit.

Push first, charge second

Each normalized document is pushed to the dataset before its per-record charge is created. If the push fails, there is no charge for that row. If a lookup returns 20 documents and delivery stops after 12, only the 12 visible rows are eligible for billing. Summary rows are never charged.

The run’s maximum-cost limit is enforced as a delivery boundary. Once the remaining budget cannot cover more document rows, the Actor stops before pushing or charging the remainder and emits budget_exhausted in the free summary. This avoids the more dangerous failure mode where results are visible but the corresponding charge could not be reconciled.

How to use it

A useful non-trivial call asks for several document classes associated with a company in one recorder office, bounded by date and result count:

{
  "partyLookups": ["TX/Dallas/Example Property Holdings LLC"],
  "docTypes": ["deed", "mortgage", "release", "lien", "assignment"],
  "dateFrom": "2022-01-01",
  "dateTo": "2026-08-05",
  "maxRecordsPerLookup": 250
}
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("shelvick/property-deed-records").call(run_input={
    "partyLookups": ["TX/Dallas/Example Property Holdings LLC"],
    "docTypes": ["deed", "mortgage", "release", "lien", "assignment"],
    "dateFrom": "2022-01-01",
    "dateTo": "2026-08-05",
    "maxRecordsPerLookup": 250,
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["record_type"] == "document":
        print(
            row["recording_date"],
            row["document_type"],
            row["instrument_number"] or row["book_page"],
            row["grantors"],
            row["grantees"],
        )
    else:
        print("summary", row["status"], row["records_returned"], row.get("reason"))

A delivered dataset row has one stable shape even when the source office uses different labels or identifiers:

{
  "record_type": "document",
  "lookup": "TX/Dallas/Example Property Holdings LLC",
  "query_type": "party",
  "state": "TX",
  "jurisdiction": "Dallas",
  "instrument_number": "202400018452",
  "document_type": "mortgage",
  "document_type_raw": "DEED OF TRUST",
  "recording_date": "2024-03-18",
  "grantors": ["EXAMPLE PROPERTY HOLDINGS LLC"],
  "grantees": ["EXAMPLE NATIONAL BANK"],
  "book_page": null,
  "parcel_id": null,
  "legal_description": "LOT 12 BLOCK 4 EXAMPLE ADDITION",
  "consideration": null,
  "cross_references": [],
  "index_coverage_start": "1800-01-01",
  "retrieved_at": "2026-08-05T14:03:11Z"
}

For an NYC property deed history search, use a full address in addresses or a BBL in parcelLookups, such as NY/Kings/3-01234-0056. The result can include deeds, mortgages, releases, liens, and cross-referenced documents from the ACRIS index beginning in 1966. Remember that the monthly extract can be up to one month behind live ACRIS.

If calling from an MCP-enabled agent, expose the Actor through the Apify MCP server and instruct the agent to branch first on record_type, then on the summary status. The input descriptions contain the support matrix, so the agent can choose company/entity party search for every covered jurisdiction and reserve address or BBL search for NYC. Calls can be paid through x402 USDC on Base or Skyfire managed tokens.

How it compares to alternatives

Approach Multiple offices, one schema Company/entity portfolio search Explicit coverage misses Document images Billing model
Search each recorder site manually no one office at a time manual judgment sometimes staff time
Build one integration per office only after custom work varies custom work varies infrastructure + maintenance
Bulk recorder datasets often often contract-dependent varies subscription or contract
Property Deed & Lien Records Search yes all covered jurisdictions structured free summary rows never per delivered record

Manual recorder search remains the right fit for occasional one-off work, especially when the final document image must be ordered. A dedicated office integration makes sense when one jurisdiction dominates the workload and its native fields matter more than portability. A bulk data contract makes sense when nationwide coverage and procurement-backed service levels are mandatory.

This Actor is for the middle case: an agent or developer needs normalized property deed and lien records across a known set of offices, wants to search a company’s recorded-document portfolio, and wants a structured answer when coverage or lookup support is absent. Its central tradeoff is breadth with honesty rather than nominal nationwide coverage.

Pricing model

Pricing is per delivered document row. The row is pushed before its charge is created. Free rows and outcomes include every lookup_summary, uncovered jurisdictions, unsupported lookup types, no-match results, source failures, failed runs, and records not delivered after the run reaches its maximum cost.

That unit aligns cost with the useful output. A broad company search that finds nothing costs nothing; a mixed batch only bills the recorded-document entries that arrive in the dataset. The Store Pricing tab is authoritative rather than a copied rate in documentation that can drift.

Current per-event rates are on the Apify Store Pricing tab.

Open questions / future work

  • More recorder offices. Coverage grows office by office, but a source is not added merely because a search page exists. Reliable delivery of the advertised company/entity party lookup is the gate.
  • Massachusetts breadth. Eleven registry districts are covered through party search. Very broad queries are narrowed into recent date windows when the source’s 1,000-record boundary is reached, with an unknown total reported rather than a false complete count.
  • Account-gated sources. Washington, DC demonstrates a recurring boundary: this Actor does not create accounts or ask customers to hand over recorder credentials. A clean public access path would change the decision.
  • Additional property-key search. Address and parcel lookup remain NYC-only. Other offices will gain those paths only when their public indexes expose a reliable property key; party search will not be presented as address support by proxy.
  • Freshness-sensitive monitoring. NYC’s monthly extract is suitable for historical research and periodic portfolio review, but not same-day filing alerts. A future source path would need to preserve the normalized contract while materially improving freshness before that use case could be promised.