Plotted API

The Plotted real estate API and property API. REST + JSON, bearer-token auth, 150M+ U.S. property records with owners, contacts, dwelling attributes, and spatial queries. Base URL: https://api.plotted.to

Quickstart

Three steps. Should take under five minutes.

1. Get a key

Sign up at plotted.to/pricing. The free plan gives you 100 credits — enough to try every endpoint.

2. Hit the API

curlshell
curl "https://api.plotted.to/v1/parcels?state=FL&mail_zip=34102&limit=5" \
  -H "Authorization: Bearer plt_live_***"

3. Parse the response

200 OK · application/jsonjson
{
  "data": [
    {
      "row_id": 8421947823,
      "state": "FL",
      "county": "Collier",
      "owner_name": "NAPLES BAY HOLDINGS LLC",
      "is_trust": false,
      "is_homeowner": true,
      "situs_address": "4421 GULF SHORE BLVD N",
      "mail_zip": "34102",
      "year_built": 2003,
      "bedrooms": 4,
      "lat": 26.142441,
      "lon": -81.795822
      // ...35 more columns
    }
  ],
  "page": { "limit": 5, "next_cursor": "eyJyb3dfaWQiOjg0MjE5NDc4MjN9" },
  "meta": { "credits_used": 1, "credits_remaining": 99999, "latency_ms": 31 }
}

Authentication

Bearer token in the Authorization header. Keys are scoped to your account — never embed them in client-side code. Keys start with plt_live_ (production) or plt_test_ (sandbox, doesn't count against credits).

Headers
Authorization: Bearer plt_live_***
Content-Type:  application/json     // for POST endpoints
Tip: Rotate keys from your dashboard. Revocation is instant. We log every key's last-used timestamp and IP so you can spot anomalies.

Credits & rate limits

Every API call costs 1 credit, except:

Rate limits by plan:

PlanPer minutePer second burstConcurrent
Free6052
Standard6002010
Pro6,00020050
Enterpriseunlimitednegotiatednegotiated

When you hit a rate limit you get 429 Too Many Requests with a Retry-After header.

Errors

Errors return a JSON envelope with a stable code string and a human-readable message.

{
  "error": {
    "code": "invalid_parameter",
    "message": "radius_mi must be between 0.01 and 50",
    "param": "radius_mi",
    "request_id": "req_01H8Z3X4..."
  }
}
StatuscodeWhen
400invalid_parameterA query/body parameter failed validation
401missing_credentialsNo Authorization header
401invalid_credentialsKey invalid, revoked, or wrong environment
402insufficient_creditsOut of credits — top up or upgrade
404not_foundRecord (e.g., row_id) doesn't exist
429rate_limitedPlan rate limit exceeded
500server_errorOur fault. Retry; request_id if you contact support

Pagination

List endpoints accept limit (max 1,000, default 50) and return a page.next_cursor. Pass it back as cursor to get the next page. Cursors are opaque and expire after 60 minutes.

Core endpoints

GET /v1/parcels cost: 1 credit

Filter and list records. All filters AND-combined. Returns paginated.

ParamTypeDescription
statestring2-letter state code, e.g. FL
mail_zipstring5-digit owner mailing zip
situs_zipstring5-digit property zip
ownerstringOwner-name substring (case-insensitive)
is_homeownerboolFilter homeowners only
is_trustboolFilter trust-held parcels
has_contactboolRecords with email or phone attached
year_built_min / year_built_maxintBuild year range
limit / cursorint / stringPagination
Examplecurl
curl "https://api.plotted.to/v1/parcels?state=FL&is_trust=true&has_contact=true&limit=10" \
  -H "Authorization: Bearer $KEY"
GET /v1/parcels/{row_id} cost: 1 credit

Fetch one record by its stable row_id. Use this for cached deep-links.

GET /v1/owners/search cost: 1 credit

Fuzzy trigram search across owner_name and owner1..owner5.

ParamTypeDescription
namerequiredstringSearch query (≥3 chars)
statestringRestrict to one state
min_similarityfloat0.0–1.0, default 0.4

Spatial endpoints

GET /v1/geocode cost: 1 credit

Address → lat/lon, using our own parcel data (no third-party geocoder). Returns the best-confidence match.

ParamTypeDescription
addressrequiredstringStreet + number, e.g. 4421 Gulf Shore Blvd N
city / state / zipstringAt least one disambiguator recommended
GET /v1/reverse cost: 1 credit

Lat/lon → closest parcel within max_dist_m (default 200m).

GET /v1/parcels/nearby cost: 1 credit

Radius search around a point. Combines with every /v1/parcels filter.

ParamTypeDescription
latrequiredfloatLatitude
lonrequiredfloatLongitude
radius_mirequiredfloat0.01 – 50.0
state, is_trust, is_homeowner, has_contact, owner, year_built_min/max(see /parcels)All filters stack
limit / cursorint / stringPagination
Example: trust holdings within 1mi, with phonescurl
curl "https://api.plotted.to/v1/parcels/nearby" -G \
  -d "lat=26.142&lon=-81.795&radius_mi=1" \
  -d "is_trust=true&has_contact=true" \
  -H "Authorization: Bearer $KEY"

Response adds distance_mi to each record, sorted ascending.

GET /v1/parcels/nearby_address cost: 2 credits

Same as /nearby, but takes an address string instead of lat/lon — we geocode it for you in the same call.

GET /v1/parcels/same_street/{row_id} cost: 1 credit

All neighbors on the same normalized street as the given parcel. Suffix-tolerant (DR ≡ DRIVE ≡ DRV).

POST /v1/parcels/within cost: 2 credits

All parcels inside a GeoJSON Polygon or MultiPolygon. For drawing arbitrary regions on a map.

Bodyjson
{
  "geometry": {
    "type": "Polygon",
    "coordinates": [[[-81.80,26.13],[-81.78,26.13],
                     [-81.78,26.16],[-81.80,26.16],[-81.80,26.13]]]
  },
  "filters": { "is_homeowner": true },
  "limit": 500
}
POST /v1/parcels/bbox cost: 1 credit

Rectangle / map-viewport queries.

Curated views

GET /v1/views/absentee cost: 1 credit

Pre-filtered to mail_zip ≠ situs_zip. Accepts state, min_distance_mi, and all standard filters.

GET /v1/views/trust cost: 1 credit

Pre-filtered to is_trust = true. Accepts state + standard filters.

GET /v1/views/homeowners cost: 1 credit

Pre-filtered to is_homeowner = true — every owner-occupied record across the dataset, in one call.

Bulk endpoints

POST /v1/match cost: 1 credit per record

Send a list of up to 10,000 addresses. Get back the enriched parcel record for each.

Bodyjson
{
  "items": [
    { "address": "4421 Gulf Shore Blvd N", "city": "Naples", "state": "FL" },
    { "address": "100 Bayfront Pl",        "city": "Naples", "state": "FL" }
  ]
}

Response: array of { input, match: parcel|null, confidence: 0–1 }.

POST /v1/match_and_expand cost: 1 credit per input

Match each input, then return all parcels within radius_mi of each match. Includes filters.

Bodyjson
{
  "items": [/* up to 10,000 addresses */],
  "radius_mi": 0.5,
  "filters": { "is_trust": true }
}

Record schema

Every list response contains records with these columns. Nullable unless marked.

FieldTypeNotes
row_idint64PK — stable across rebuilds
sourcestringOrigin layer for the record (county parcel layer or homeowner index)
statestring2-letter
county / parcel_idstringOriginal county parcel identifier
owner_namestringRaw owner string, preserved as published
owner1 ... owner5stringSplit individual owners
owner1_type ... owner5_typeenumperson · entity · trust · gov
owner_countintCount of non-null owner1..5
is_trust / is_homeownerboolFlag columns
fiduciary_name / fiduciary_codestringTrustee / fiduciary fields where the county publishes them (e.g. Florida)
mail_address / mail_city / mail_statestringOwner mailing block
mail_zip / mail_zip4stringAlways 5-digit / +4 separated
situs_address / situs_city / situs_zipstringProperty location block
land_use_code / land_use_descstringCounty land-use classification
year_builtstringOriginal construction year
bldg_sqft / res_units / bedrooms / baths_full / baths_halffloatDwelling attributes
assessed_value / sale_price / sale_date / acresnumericValue attributes
owner_email / owner_email_2stringUp to 2 emails when matched
owner_phone / owner_phone_2stringUp to 2 phones
contact_match_levelenumowner · occupant
contact_confidencefloat0.0 – 1.0
lat / lonfloatParcel centroid (WGS84)
Privacy: Contact fields are returned redacted in sandbox environments and per regional regulation (e.g., California consumer requests). Real values only on production keys with verified business use.

Changelog