US County Property Records API — Owner, Value, Tax & Sales avatar

US County Property Records API — Owner, Value, Tax & Sales

Pricing

from $42.50 / 1,000 property record resolveds

Go to Apify Store
US County Property Records API — Owner, Value, Tax & Sales

US County Property Records API — Owner, Value, Tax & Sales

Look up normalized US property records by address or parcel ID — owner, assessed value, market value, property tax history, and sale history — from public county assessor and recorder data, one schema across many counties. A property-records API for agents, not consumer listing data.

Pricing

from $42.50 / 1,000 property record resolveds

Rating

0.0

(0)

Developer

Scott Helvick

Scott Helvick

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

17 minutes ago

Last modified

Share

Property data is scattered across thousands of county assessor and recorder sites, each with its own portal, format, and quirks. This Actor is a property-records API: pass an address or parcel ID and get a normalized property record — owner, assessed value, market value, property tax history, and sale history by address — sourced from public county assessor and recorder data, in one schema across many counties, so an agent calls one tool instead of learning a new site per county.

What this does

  • Address → property record. Pass full street addresses; each is routed to its county and returned as one normalized record.
  • Parcel ID → property record. Already have the assessor parcel/account number? Pass STATE/County/ParcelID for an exact lookup.
  • One unified schema across counties. Every record has the same shape regardless of which county it came from: owner_name, parcel_id, assessed_value, market_value, tax_year, tax_history, last_sale_date / last_sale_price, sale_history, and characteristics (year built, building/lot area, units).
  • Public-record sourcing. Data comes from county assessor/recorder offices and government open-data — public records you're allowed to use and resell — not from consumer real-estate listing portals.
  • Batch + pay-on-success. Submit many lookups in one run; you're charged only for records actually resolved. Counties not yet covered, and lookups that don't resolve, are returned with a status and are never charged.
  • Live coverage manifest. Each run writes the current supported-county set to the key-value store (COVERAGE), so an agent can check coverage before or after a call.

Use cases:

  • Enrich a list of addresses with owner, value, and last-sale data for diligence or lead research.
  • Pull assessed value + sale history for a specific parcel during underwriting.
  • Build a property dataset across several metros from one agent-callable tool.
  • Verify owner-of-record and tax-year valuation for a known parcel ID.

Why one schema across counties matters

The hard part of property data isn't any single county — it's that every county publishes differently. A scraper built for one county is a commodity: it solves 1/3000th of the problem and breaks on its own when that county redesigns its site. The moment you need a second county you start over.

This Actor absorbs that heterogeneity behind a single contract. Internally each county has its own source — a clean government open-data API where one exists, a rendered public portal where it doesn't — but the output is identical across all of them. Coverage grows over time without changing the call you make or the shape you parse.

Field coverage is best-effort per county: every record uses the same schema, with null where a particular county doesn't publish a particular field (one county may expose full sale history but not building characteristics; another the reverse). The contract is stable; the fill rate depends on what each county makes public. That's an honest tradeoff, surfaced in the data rather than hidden.

How it compares

ApproachMultiple counties, one schemaPublic-record sourcedPay-per-use, no minimumAgent-callable
Single-county scrapervariesOKvaries
Enterprise property-data platformsOKOK— (subscription + contract)
US County Property RecordsOKOKOKOK

Single-county scrapers cover one place and leave you to stitch the rest together. Enterprise data platforms cover the country but gate it behind monthly minimums and sales contracts. This Actor sits in the open middle: many counties behind one schema, public-record data, billed per record with no commitment, callable directly by an agent.

Input

FieldTypeRequiredDefaultDescription
addressesarray of stringsone of[]Full US property street addresses, e.g. "1001 Preston St, Houston, TX 77002". Each is matched to its county and returned as one record. Include city + state (and ZIP if known) for reliable routing.
parcelLookupsarray of stringsone of[]Direct parcel/account lookups as STATE/County/ParcelID, e.g. "IL/Cook/15362060520000". Use when you already know the ID.
countystringno--Optional county hint (without "County"), e.g. "Cook", to disambiguate addresses across county lines.
statestringno--Optional two-letter state code, e.g. "IL", paired with county.
includeHistorybooleannotrueInclude full tax-year and recorded-sale history where the county publishes it. Set false for a current-snapshot-only record (faster, smaller).
maxRecordsintegerno50Safety cap on records resolved per run (1–1000).

Provide addresses, parcelLookups, or both — at least one is required.

Output

One dataset record per lookup. Example (completed):

{
"query": "425 Addison Rd, Riverside, IL 60546",
"query_type": "address",
"status": "completed",
"county": "Cook",
"state": "IL",
"parcel_id": "15362060520000",
"owner_name": "SMITH JOHN A & JANE B",
"situs_address": "425 ADDISON RD, RIVERSIDE, IL 60546",
"mailing_address": "425 ADDISON RD, RIVERSIDE, IL 60546",
"assessed_value": 37000,
"market_value": null,
"land_value": 14350,
"improvement_value": 22650,
"tax_year": 2025,
"tax_history": [],
"last_sale_date": "2018-04-20",
"last_sale_price": 249000,
"sale_history": [
{ "date": "2018-04-20", "price": 249000, "document_type": "Warranty", "doc_number": "1815701295" }
],
"characteristics": { "year_built": 1998, "building_sqft": 2450 },
"source_url": "https://www.cookcountyassessor.com/pin/15362060520000",
"fetched_at": "2026-06-22T14:03:11Z",
"error": null
}

status is one of completed, not_covered (county not yet supported), or failed (covered county, no match — see error). Nullable fields are null where the county doesn't publish them. Every field is described in the dataset schema.

Example

{ "addresses": ["1060 Wakeling St, Philadelphia, PA 19124"], "includeHistory": true }
curl -X POST "https://api.apify.com/v2/acts/shelvick~county-property-records/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"addresses":["1060 Wakeling St, Philadelphia, PA 19124"],"includeHistory":true}'
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("shelvick/county-property-records").call(
run_input={"addresses": ["1060 Wakeling St, Philadelphia, PA 19124"]}
)
for record in client.dataset(run["defaultDatasetId"]).iterate_items():
print(record["owner_name"], record["assessed_value"], record["last_sale_price"])

Calling from an AI agent

Apify MCP server (mcp.apify.com). This Actor is exposed as a callable tool; the input schema is self-documenting, so an LLM can construct correct calls from the tool description alone. Pass an address (or a STATE/County/ParcelID) and read back a normalized property record. Pay per call via x402 USDC on Base or Skyfire managed tokens — this Actor is enabled for agentic payments.

Apify SDK (Python). See the example above (apify_client).

REST API. Use run-sync-get-dataset-items for small synchronous batches; the async /runs endpoint for larger ones.

Pricing

Pay-per-event, billed only on success: one charge per resolved property record, after the record is pushed. not_covered and failed results are returned for transparency but never charged, and a single Actor-start event is amortized across the whole batch. So a batch of addresses costs only for the ones that actually came back with data.

See the Pricing tab on this Store page for the current per-record rate and any active subscriber discounts.

Behavior

Run-level failures (rare): input validation only — at least one of addresses / parcelLookups must be present; maxRecords is 1–1000.

Per-record statuses (normal):

  • completed — record resolved and normalized.
  • not_covered — the county isn't supported yet (error: "county-not-covered"). Check the COVERAGE manifest for the current set.
  • failed — covered county but the lookup didn't resolve (error: "no-match", or a transient source error). Address matching is exact-ish; include city/state/ZIP for best results.

Performance: open-data counties resolve in roughly 1–3 s per record; counties served from a rendered portal take longer. Lookups run concurrently, so a batch finishes far faster than the sum of its parts. Very large batches should use the async API rather than the 5-minute sync endpoint.

FAQ

Which counties are supported? A growing set spanning major metros across multiple states — including Chicago (Cook), New York City, Philadelphia, Phoenix (Maricopa), Houston (Harris), Dallas and Fort Worth (Dallas/Tarrant), Miami (Miami-Dade), Tampa (Hillsborough), Orlando (Orange), and Cleveland (Cuyahoga) — with more added regularly. The authoritative, always-current list is written to the COVERAGE key-value-store record on every run, so an agent can check coverage programmatically rather than trusting a static list.

What happens for a county you don't cover yet? You get a record with status: "not_covered" and you are not charged for it. Coverage expands over time; the same call will start returning data once that county is added.

Why is a field null? Counties publish different things. The schema is constant; a null means that county doesn't expose that field publicly. One county may have full sale history, another rich building characteristics — best-effort per source.

Am I charged for addresses that don't resolve? No. Only completed records are billed.

Can I look up by parcel number instead of address? Yes — pass STATE/County/ParcelID in parcelLookups for an exact lookup that skips address matching.

What this doesn't do

  • No consumer listing data. This returns official county assessor/recorder records, not for-sale listings, Zestimate-style estimates, or agent/MLS data.
  • No nationwide coverage (yet). It covers a curated, growing set of counties; uncovered counties return not_covered, not an error.
  • No owner contact info. Owner of record and mailing address come from public records; no phones, emails, or skip-tracing.
  • No fuzzy/owner-name search. Look up by address or parcel ID, not "all properties owned by X" — that 1→many shape isn't supported.
  • No guaranteed field completeness. Field fill rate varies by county (best-effort).

For for-sale listings and valuations, use a real-estate listings source. For nationwide enterprise coverage with SLAs, a subscription property-data platform is the better fit. For raw geometry/parcel boundaries, use a county GIS/parcel layer.


Design notes: www.scotthelvick.com/tools/county-property-records