NHDdata API · v1

One REST call. Hazards + tax for any California parcel.

Three endpoints share one input shape (APN + county FIPS, or address). Use /v1/parcel for the unified envelope (recommended), or call /v1/tax and /v1/hazards individually if you only need one half.

Endpoints

POST

/v1/parcelRecommended

Unified envelope — NHD + Tax in one call.

POST

/v1/tax

California tax decode only. Drop-in replacement for the legacy CTD Tax Summary JSON.

POST

/v1/hazards

Statewide NHD layers only — FEMA / CAL FIRE / CGS / DWR / CalGEM / EPA.

Auth

Every request must carry an X-API-Key header. Keys start with tl_live_ (or tl_test_ for sandbox) and are issued from the customer dashboard. Treat them like passwords — we hash with SHA-256 before storage and show the raw key once. Rate limit: 100 requests per minute per key.

POST /v1/parcel — unified

The headline endpoint. One call returns property snapshot, statewide hazards, and tax decode. Internally fans out to the tax and hazards lookups in parallel — either half failing degrades to a partial response (the failure shows up in errors[] rather than blowing up the whole call).

request
curl -X POST https://nhddata.com/api/v1/parcel \
  -H "Content-Type: application/json" \
  -H "X-API-Key: tl_live_xxxxxxxxxxxx" \
  -d '{
    "apn": "4286-022-011",
    "county_fips": "06037"
  }'
response
{
  "property": {
    "apn": "4286-022-011",
    "county": "Los Angeles",
    "owner_name": "JAMES E PRICE REVOCABLE TRUST",
    "year_built": 1921,
    "property_type": "DUPLEX",
    "formatted_address": "27 SUNSET AVE, VENICE, CA 90291",
    "centroid": { "lat": 33.993301, "lng": -118.478 },
    "source": "ATTOM"
  },
  "resolved_geom": "polygon",
  "hazards": [
    { "layer": "fema_flood",         "inside_centroid": false, "parcel_intersects": false, "pct_overlap": 0,    "severity": "none",      "details": { "zones": [] },                     "source": "FEMA NFHL" },
    { "layer": "calfire_fhsz",       "inside_centroid": true,  "parcel_intersects": true,  "pct_overlap": 1.0,  "severity": "very_high", "details": { "lra_class": 3, "jurisdiction": "LRA" }, "source": "CAL FIRE FRAP" },
    { "layer": "cgs_alquist_priolo", "inside_centroid": false, "parcel_intersects": false, "pct_overlap": 0,    "severity": "none",      "details": { "mapped": false },                  "source": "CGS" },
    { "layer": "cgs_seismic_hazard", "inside_centroid": true,  "parcel_intersects": true,  "pct_overlap": 0.42, "severity": "high",      "details": { "liquefaction_in_zone": true, "landslide_in_zone": false }, "source": "CGS" },
    { "layer": "cgs_tsunami",        "inside_centroid": true,  "parcel_intersects": true,  "pct_overlap": 1.0,  "severity": "high",      "details": { "county": "Los Angeles" },          "source": "CGS Tsunami Maps" },
    { "layer": "dwr_dam_inundation", "inside_centroid": false, "parcel_intersects": false, "pct_overlap": 0,    "severity": "none",      "details": {},                                   "source": "DWR DSOD" },
    { "layer": "calgem_wells",       "inside_centroid": false, "parcel_intersects": false, "pct_overlap": null, "severity": "none",      "details": { "count": 0 },                       "source": "CalGEM WellSTAR" },
    { "layer": "epa_environmental",  "inside_centroid": false, "parcel_intersects": false, "pct_overlap": null, "severity": "low",       "details": { "total": 0 },                       "source": "EPA / CalEPA" }
  ],
  "tax": {
    "annual_tax": 19648.66,
    "direct_charges": [
      { "name": "SANTA MARGARITA WTR DIST CFD 2013-1", "agency_code": "0734", "amount": 1814.35, "kind": "cfd", "bond_payoff_year": 2042, "cfd_id": "2013-0234" }
    ],
    "has_mello_roos": true,
    "has_1915_bond": false,
    "has_pace": false,
    "data_sources": { "property": "ATTOM", "tax_bill": "la_county_ttc_scrape", "cfd_master": "CDIAC YFSR" },
    "cache_hit": true
  },
  "queried_at": "2026-05-21T16:32:14Z"
}

Parcel polygon vs centroid resolution

California Civil Code §1103.2 requires natural-hazard disclosure when "the property is located in" a hazard zone — meaning the parcel polygon, not its centroid. A legacy centroid-only lookup silently under-discloses on larger lots where the rooftop sits on safe ground but the lot boundary crosses a FEMA flood, CAL FIRE FHSZ, fault, or dam-inundation polygon.

NHDdata runs ST_Intersects(hazard.geom, parcel.geom) directly against the parcel boundary when one is available, and also reports the fraction of the lot that lies inside each hazard (pct_overlap, 0–1). This is the same polygon-vs-polygon model used by every legitimate NHD provider.

Each response surfaces three fields per layer:

  • inside_centroid — true if the parcel centroid is inside the hazard polygon.
  • parcel_intersects — true if any part of the parcel polygon intersects. The legally-defensible disclosure flag.
  • pct_overlap — fraction (0–1) of the parcel area inside the hazard. Null when the layer is proximity-based (oil & gas wells, EPA sites) or when polygon mode is unavailable.

The top-level resolved_geom field tells you which mode ran (polygon or centroid) — when ATTOM doesn't expose a polygon for the lot, the response degrades to centroid mode and flags the degradation explicitly.

POST /v1/tax

Drop-in replacement for the legacy CTD Tax Summary JSON. Same wire shape, decoded direct charges (Mello-Roos / 1915 / PACE / other), CDIAC bond-payoff enrichment, and the 5,500+ agency fund-code dictionary.

request
curl -X POST https://nhddata.com/api/v1/tax \
  -H "Content-Type: application/json" \
  -H "X-API-Key: tl_live_xxxxxxxxxxxx" \
  -d '{ "apn": "4286-022-011", "county_fips": "06037" }'

POST /v1/hazards

Returns the eight statewide NHD layers for a parcel or arbitrary lat/lng point. Same envelope as the hazards[] array inside /v1/parcel: per-layer parcel_intersects, inside_centroid, pct_overlap, severity, and details (layer-specific shape).

apn-or-point
curl -X POST https://nhddata.com/api/v1/hazards \
  -H "Content-Type: application/json" \
  -H "X-API-Key: tl_live_xxxxxxxxxxxx" \
  -d '{
    "apn": "4286-022-011",
    "county_fips": "06037"
  }'

# or lat/lng directly (no parcel resolution)
curl -X POST https://nhddata.com/api/v1/hazards \
  -H "X-API-Key: tl_live_xxxxxxxxxxxx" \
  -d '{ "point": { "lat": 33.993301, "lng": -118.478 } }'

Billing & credits

NHDdata runs on a prepaid credit balance. You buy credits up front via Stripe Checkout from /dashboard/billing, the balance loads instantly, and each API call atomically debits the balance. No invoices, no Net 30, no AR.

What each call costs

EndpointCost per unique APN per month
POST /v1/parcel$10.00 — NHD + Tax bundled
POST /v1/tax$5.00 — tax-only
POST /v1/hazards$5.00 — NHD-only

If you call a singleton then later call /v1/parcel for the same APN in the same calendar month, you pay the difference, not the full bundle again. The bundled call is strictly cheaper than buying singletons separately.

Cache hits don't consume credits

Every successful lookup is cached per-APN for 30 days. Cache hits within that window return instantly and do not debit your balance. The response includes "cache_hit": true so you can confirm it on your side.

When you run out

A request that would drop your balance below zero is rejected with HTTP 402 Payment Required and a JSON body of the shape:

402 response
{
  "error": {
    "code": "insufficient_credits",
    "message": "Your credit balance is exhausted. Top up at https://nhddata.com/dashboard/billing.",
    "balance_cents": 0,
    "required_cents": 1000
  }
}

Your existing API key stays valid — top up the balance from /dashboard/billing and the next call goes through. Pricing details at /pricing.

Errors

CodeReason
401Invalid, missing, or revoked X-API-Key.
402Insufficient credits. Top up balance at /dashboard/billing.
404Parcel not found — APN unknown to ATTOM, or address not geocodable.
422Malformed body — missing apn/address, bad FIPS, etc.
429Rate limit exceeded — 100 requests per minute per key.
500Upstream failure. Response includes `error_id` + `retry_after`.
501Endpoint understood but not yet implemented for this input shape.

Hazard layer coverage

LayerScopeStatus
FEMA flood (NFHL)StatewideLive
CAL FIRE FHSZStatewideLive
CGS Alquist-Priolo (fault zone)StatewideLive
CGS seismic hazard (liquefaction + landslide)StatewideLive
CGS tsunami inundationCoastal CALive
DWR dam inundationStatewideLive
CalGEM oil/gas wellsStatewideLive
EPA environmental sitesStatewideLive

Tax direct-charge coverage

CountyStatus
Orange (06059)Live — direct-charge scraper
San Diego (06073)Live — special-assessment lookup
Riverside (06065)Live — Grant Street GovHub
San Bernardino (06071)Live — Grant Street GovHub
Los Angeles (06037)Live — TTC scraper
Contra Costa (06013)Live — Grant Street GovHub (JSON)
Butte · Colusa · Del Norte · El Dorado · Glenn · Humboldt · Imperial · Lake · Madera · Mariposa · Merced · Modoc · Mono · Monterey · Napa · Nevada · Placer · Plumas · San Benito · San Joaquin · Shasta · Siskiyou · Sonoma · Stanislaus · Tehama · Trinity · Tulare · Tuolumne · Yolo · YubaLive — MPTS / Manatron
Fresno (06019)Live — custom Kendo scraper
Alpine (06003)Live — Pacific Sky County Tax Retriever
Sacramento · Santa Clara · San Francisco · San Mateo · Alameda · Santa Barbara · Ventura · San Luis Obispo · MarinPartial — scrapers built, awaiting per-county rollout (Playwright pipeline)
All 58 countiesATTOM property snapshot live; per-APN direct-charge coverage rolling out

Full live matrix at /coverage. For counties without a direct-charge scraper yet, the response still returns property + assessed values from ATTOM and sets data_sources.tax_bill: "scraper_pending".

Changelog

  • 2026-05-21Upgraded hazard resolution from centroid-only to parcel polygon. /v1/parcel and /v1/hazards now return resolved_geom plus per-hazard parcel_intersects and pct_overlap when ATTOM returns a parcel boundary.
  • 2026-05-20Added /v1/parcel (unified) and /v1/hazards endpoints. NHD layers now exposed via REST.
  • 2026-05-19Voter-approved bond enrichment added to tax response (voter_approved_bonds[]).
  • 2026-05-15Per-APN tax-bill scrapers shipped for Orange, San Diego, Riverside, San Bernardino.
  • 2026-05-10Initial /v1/tax release. CDIAC YFSR bond-payoff enrichment + 5,500-row agency dictionary.