Search Properties
Beta. Query parameters, response fields, and the absence-trust surface may still change in response to how the endpoint is used in practice. Treat the shape as unstable while it is in beta. Returns properties within a geo scope (state, county, city, ZIP, ZIP+4, or address) and/or owned by named legal_owners, most recently permitted first. At least one of geo_id and legal_owner is required; a legal_owner search needs no geo scope. Pass cursor from a page’s next_cursor to page through results. Filter by permit_tags (presence and --prefixed exclusion), permit_status, permit_from, and property_type. Properties with no attributed type are excluded; property attributes are available for 70.1% of properties. An absence filter (a --prefixed tag) returns properties with no resolved permit of the excluded type in covered, arrived data: a permit that never linked to an address, or one that has not yet arrived within a jurisdiction’s ingestion lag, is not counted against the claim. Every absence-class row carries a trust object stating three honesty terms — the jurisdiction’s permit coverage_tier, its unresolved_rate (the share of its permits that never linked to an address), and the row’s data_horizon — plus horizon_basis (whether that horizon is measured, pooled, or a state prior) and footprint_basis (whether suppression could resolve the row’s geo); the response trust_summary row-weights them across the page. A presence-only search omits both. Freshness: /properties is served from a whole-table rebuild swapped in by a blue/green cutover — the rebuild is the unit of refresh, and the absence-trust metadata is rebuilt from the same data it describes. An absence claim’s staleness bound is the rebuild cadence plus each jurisdiction’s permit-ingestion lag: permits reach the data a median 84 days (p90 188 days) after their start_date, so a recent ‘no permit since D’ stays under-observed until that lag elapses. Each row’s data_horizon is the per-row handle on that bound — the most recent start_date past which ‘no X since D’ is under-observed for that row. Not supported, each for one reason: permit_to or any upper date bound — a max-date rollup cannot bound a closed date window without dropping addresses whose latest permit postdates it (use /permits/search); ‘before D’ absence — a max-date cannot tell a history that also holds later permits from one that does not; closed-window absence within [D1, D2] — only ‘ever’ and ‘since D’ are expressible on max-date maps; ‘has X since D and no Y since D’ in one query — a mixed query binds an exclusion to ‘ever’, so per-predicate dates cannot be composed; and jurisdiction geo_ids as a scope.
Authorizations
Query Parameters
Scope: a 2-letter state, 5-digit ZIP, ZIP+4, or a Shovels state/county/city/address geolocation id. Jurisdiction ids are not accepted. Optional when legal_owner is given, required otherwise.
Filter by the property's legal owner, matched on the owner's canonical form — case and spacing variants of one name return the same properties. Pass up to 10 legal_owner params to span several owners. Without a geo_id this searches an owner's properties nationwide; with one it narrows that scope to the owner. A name the data uses for an unidentified owner (for example 'RECORD OWNER') is rejected.
10Filter by canonical permit tags. Repeat the key for several tags (e.g. ?permit_tags=roofing&permit_tags=-solar). A positive tag keeps properties with that tag on some permit; a - prefix excludes the tag (properties without it). Multiple positive tags require each tag (address grain — a documented divergence from /permits/search, where they must share one permit). With permit_status, each positive tag must be matched by a permit that also has one of the statuses. With permit_from: a positive tag matches since the date; an exclusion means 'no such tag since the date' in a pure-absence query, or 'never' when combined with any positive filter.
Filter by permit status; repeat the key for several (final, in_review, inactive, active). With positive permit_tags this is a same-permit conjunction; on its own it keeps properties with a permit of one of the statuses. 'unknown' is not a filterable status.
Bind the tag/status/absence filters to this ISO date (YYYY-MM-DD): a positive filter matches since the date, a pure-absence exclusion means 'none since the date'. With no tag/status filter it keeps properties whose most recent permit is on or after the date.
Not supported on /properties/search — use /permits/search for a date-window search.
Filter by property type: residential, commercial, industrial, agricultural, vacant land, exempt, miscellaneous, office, recreational. Repeat the key to match any of several types (e.g. ?property_type=residential&property_type=commercial). Properties with no attributed type are excluded; property attributes are available for 70.1% of properties.
Keep properties with an UNFINALED permit of each named canonical tag. Repeat the key for several (e.g. ?permit_tags_unfinaled=solar&permit_tags_unfinaled=roofing). Permit status — not a permit's final_date — is the finaled flag: a final permit carries a NULL final_date 14.7% of the time, and ~41% of permits with a NULL final_date are in fact finaled by status, so a final_date IS NULL test over-counts unfinaled permits and is wrong ~41% of the time. A tag is unfinaled for an address only when its latest non-final permit of that tag is more recent than its latest final permit of that tag (equal dates count as finaled); this clear rule removes the false positives a plain 'any non-final permit of this tag' test would keep — for solar, 31.5% of them, though the share varies by tag (nearer 13-18% for tags such as roofing and hvac). A permit whose status is unknown (NULL) counts as neither final nor unfinaled. With permit_from, the tag's latest unfinaled permit must be on or after the date.
Minimum assessed market value in integer cents (dollars x 100). A property with no assessed market value never matches a range filter (a value is available for 61.0% of properties).
Maximum assessed market value in integer cents (dollars x 100). A property with no assessed market value never matches a range filter (a value is available for 61.0% of properties).
Minimum lot size in square feet. A property with no lot size never matches a range filter (a value is available for 68.7% of properties).
Maximum lot size in square feet. A property with no lot size never matches a range filter (a value is available for 68.7% of properties).
Minimum building area in square feet. A property with no building area never matches a range filter (a value is available for 60.8% of properties).
Maximum building area in square feet. A property with no building area never matches a range filter (a value is available for 60.8% of properties).
Minimum unit count. A property with no unit count never matches a range filter (a value is available for 70.1% of properties).
Maximum unit count. A property with no unit count never matches a range filter (a value is available for 70.1% of properties).
Minimum year built. A property with no year built never matches a range filter (a value is available for 61.1% of properties).
Maximum year built. A property with no year built never matches a range filter (a value is available for 61.1% of properties).
Opaque keyset cursor from a previous page's next_cursor.
Page size (1-100).
1 <= x <= 100When true and on the first page (no cursor), include a total_count capped at 10,000.
Response
A page of properties in the scope.
Paginated /properties response; carries the honesty summary on absence pages.
The list of items returned in the response following given criteria.
The number of items returned in the response.
The cursor for retrieving the next page of results.
Total result count (capped at 10,000). Present on first-page responses when include_count=true. null if the count query timed out.
Row-weighted trust summary; present only on absence-class responses.
