Permits (listing endpoint)
API Reference
Permits (listing endpoint)
Paginate permit rows by jurisdiction × date with the full normalized + source + enrichment fields.
GET
Permits (listing endpoint)
Returns paginated permit rows filtered by jurisdiction + date range.
The response example below is generated from the live OpenAPI spec
at api.permitcore.io/openapi.json —
re-pulled before each docs deploy, never hand-written.
The
List-vs-detail asymmetry: the nested
The nested
See Get a permit by canonical_uid.
Note that the top-level PLUTO-derived fields (The
Authentication
Authorization: Bearer <your-api-key> — see Authentication.
Query parameters
These are the parameters/v1/permits actually accepts, per the live
OpenAPI spec. There is no cohort parameter on /v1/permits — buyer-segment
filtering lives on the /v1/permits/target
variant via target_segment.
Response shape
The example below is generated from thePermitResponse schema in
openapi.json. The enrichment fields (cohort, dq_flags, top-level
PLUTO-derived fields, description_llm_enriched) are now on the wire as
of the 2026-06-04 promotion. The nested pluto DQ-status object is
list-endpoint NULL by design — see the list-vs-detail asymmetry note below.
pagination object above is abbreviated for readability. See
Pagination for the full, canonical envelope both this endpoint
and /v1/permits/target return.
Pagination
/v1/permits and /v1/permits/target return the same response envelope:
total_countis capped at 1,000 on selective-filter queries, and jurisdiction-only queries use a fast row estimate. Whentotal_count_is_estimateorhas_moreistrue, the true total exceedstotal_count— render it as"1,000+"if you surface it directly.- Offset paging (both endpoints): pass
page+per_page. - Keyset / cursor paging (
/v1/permitsonly): pass the prior response’spagination.next_cursorback as thecursorquery parameter to page deep or across jurisdictions without anOFFSETscan. Whencursoris supplied,pageis ignored; the cursor is bound to the request’sorder_by/order_dir, so keep those stable while paging.next_cursorisnullon the last page./v1/permits/targetis offset-only (page/per_page).
What’s on the wire
The list-endpoint shape carries 58 fields organized as:- Identity —
id,jurisdiction_slug,source_permit_id,canonical_uid,filing_id - Address —
address_full,address_street,address_city,address_state,address_zip,parcel_apn - Geocode metadata —
lat,lng,geocode_confidence,geocode_source - Type + classification —
permit_type_raw,permit_type_normalized,work_class,building_subtype,is_new_construction,is_alteration,is_demolition - Scope —
units_residential,units_commercial,units_total,square_footage_total,valuation_usd - PLUTO-derived (denormalized) —
year_built,building_class,zoning_district,lot_area - Dates —
applied_date,issued_date,finaled_date,expired_date - Status + description —
current_status,status_raw,description_raw,description_extracted,description_llm_enriched - People —
applicant_name,applicant_role,contractor_name,contractor_license,architect_name,engineer_name,owner_name - Source link + timestamps —
source_url,first_seen_at,last_seen_at,last_changed_at - Enrichment —
cohort,dq_flags,pluto
Enrichment field semantics
cohort— the buyer-targeting bucket the row classifies into, one of the 23 canonical segments.null= row not in the cohort matview OR in the matview but unsegmented. Identical taxonomy totarget_segmenton/v1/permits/target. See Concepts → Cohorts.dq_flags— 12-key boolean object from the bounds-rule registry. All 12 keys are always present when this field is non-null. Semantic note:falsemeans “the rule has not flagged this value,” NOT “the value was independently verified clean.” NULL silver flag values are coerced tofalseper the SD-039 rule. See Data freshness → field-level DQ flags for the full flag set + semantics.description_llm_enriched— LLM-extracted feature object (e.g.scope_summary,unit_count,square_footage,tenant_improvement_flag). Distinct column fromdescription_extracted(which is adapter-populated, per-jurisdiction shape). Coverage today: Austin + Chicagocommercial_newpermits only (~17,861 prod rows). Broader cross-metro LLM extraction is on the roadmap.
List-vs-detail asymmetry: the nested pluto object
The nested pluto object (PLUTO parcel-data presence + per-flag DQ
status — {is_placeholder, oob}) is ALWAYS null on /v1/permits
by design.
The PLUTO LEFT JOIN required to populate the DQ-status object would
add ~75ms p95 to the list query, which disqualifies it for the browse
path. To get the populated pluto object, query the per-permit detail
endpoint:
year_built,
building_class, zoning_district, lot_area, units_total) are
denormalized into the silver layer and DO populate on this endpoint
when known. Only the nested pluto DQ-status object is detail-only.
The /v1/permits/target variant
/v1/permits/target returns the same response envelope but exposes a curated
commercial/multifamily/ADU slice, filtered by target_segment=<segment>. This is
the cohort-classified slice that the site’s recency surfaces query. It also adds
geo filters (state, metro_cbsa), value bounds (value_min/value_max), and
enrichment filters (climate_zone, roof_area_min, hvac_tonnage_min). It is
offset-only (no cursor).
target_segment accepts a comma-separated list of the 23 canonical segment
slugs (see Cohorts). Behavior:
- Valid segment with zero rows in window:
200 OKwith an emptyitemsarray — no404. Usepagination.total_countto confirm the empty result. - Invalid slug (not in the 23-segment enum):
422 Unprocessable Entity(invalid-target-segment) with a problem+json body enumerating the accepted whitelist. - Newer cohort caveat:
multifamily_mepis filterable and populated but not yet certified to the ≥99% precision bar of the other 22 segments — currently 3 metros (primarily Austin), largely historical. See the note on Cohorts. The other 22 are the established, certified set.
/v1/permits/target also has a separate cohort parameter — but it is a
per-manufacturer cohort (an A5 YAML basename such as roofing-manufacturer),
not a buyer segment. An unknown value returns 400
(unknown-cohort). Use target_segment for segment
filtering; use cohort only for manufacturer-cohort targeting.Quotas
Quotas are tier-based and apply to every request against this endpoint. See Rate limits for the matrix + theX-RateLimit-* response headers + the 429 problem+json shape.
Example request
Related
- Get a permit by canonical_uid — the per-permit detail endpoint with full enrichment + nested
pluto. - Cohort distribution — aggregate counts behind these rows.
- Free sandbox — client-side DuckDB-WASM queries against the last 10 days of permits across the live metros. No signup, no API key.