{"openapi":"3.1.0","info":{"title":"PermitCore API","description":"Read API over the PermitCore silver layer — normalized US construction permit data across 14 metros (18 jurisdictions). All `/v1/*` endpoints require a Bearer token. Interactive Swagger UI at `/docs`; ReDoc at `/redoc`; raw OpenAPI JSON at `/openapi.json`. A versioned copy of the OpenAPI schema is checked into the repo at [`docs/api/openapi.json`](https://github.com/permitcore/permit-pipeline/blob/main/docs/api/openapi.json) for buyer-pack and SDK-generation reference.","contact":{"name":"PermitCore","url":"https://permitcore.io/"},"license":{"name":"Proprietary"},"version":"0.1.0"},"paths":{"/healthz":{"get":{"tags":["system"],"summary":"Liveness probe","description":"Cheap liveness check — does NOT touch the database.","operationId":"healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Healthz Healthz Get"}}}}}}},"/health":{"get":{"tags":["system"],"summary":"Liveness + DB reachability check","description":"Liveness + DB-reachability check. Used for monitoring; not auth-protected.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/permits/target":{"get":{"tags":["target_permits"],"summary":"List buyer-targeted permits (commercial / multifamily / qualifying ADU)","description":"Queries the `gv_target_permits` materialized view — the curated slice of the silver permits table that's commercial-adjacent, multifamily, or qualifying ADU. Use `target_segment` filter for sharp segments (commercial_new, commercial_alteration, commercial_mep, commercial_signage, industrial_new, multifamily_new, multifamily_alteration, adu_qualifying, commercial_demolition, residential_mep); omit it for the broader commercial slice including unsegmented commercial work. See `docs/api/permits.md` for the response shape and `docs/api/territory.md` for aggregated rollups.\n\n**Examples:**\n\n1. **Filter-light** — Austin commercial new permits issued in the last 12 months:\n   `GET /v1/permits/target?jurisdiction=austin&target_segment=commercial_new&issued_after=2025-05-16`\n\n2. **Filtered** — TX commercial new permits worth at least $500K:\n   `GET /v1/permits/target?state=TX&target_segment=commercial_new&value_min=500000`\n\n3. **Edge / structured-signal** — NYC permits in the commercial cohort. Rows surface adapter-extracted JSONB in the `description_extracted` response field (5.88M prod cohort; see the field description for the per-jurisdiction key inventory):\n   `GET /v1/permits/target?jurisdiction=nyc&target_segment=commercial_new,commercial_alteration`","operationId":"list_target_permits_v1_permits_target_get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"jurisdiction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated jurisdiction slugs. Optional (state / metro_cbsa filters work too).","title":"Jurisdiction"},"description":"Comma-separated jurisdiction slugs. Optional (state / metro_cbsa filters work too)."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated state codes (e.g. 'TX,CA'). Filters by jurisdiction state.","title":"State"},"description":"Comma-separated state codes (e.g. 'TX,CA'). Filters by jurisdiction state."},{"name":"metro_cbsa","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated CBSA codes (e.g. '41700,19100'). Filters by CBSA.","title":"Metro Cbsa"},"description":"Comma-separated CBSA codes (e.g. '41700,19100'). Filters by CBSA."},{"name":"target_segment","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated segments. Allowed: commercial_new, commercial_alteration, commercial_mep, commercial_signage, industrial_new, multifamily_new, multifamily_alteration, adu_qualifying, commercial_demolition, residential_mep.","title":"Target Segment"},"description":"Comma-separated segments. Allowed: commercial_new, commercial_alteration, commercial_mep, commercial_signage, industrial_new, multifamily_new, multifamily_alteration, adu_qualifying, commercial_demolition, residential_mep."},{"name":"issued_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued After"}},{"name":"issued_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued Before"}},{"name":"value_min","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Value Min"}},{"name":"value_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Value Max"}},{"name":"bbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Spatial filter as 'min_lng,min_lat,max_lng,max_lat'. Uses PostGIS.","title":"Bbox"},"description":"Spatial filter as 'min_lng,min_lat,max_lng,max_lat'. Uses PostGIS."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Per Page"}},{"name":"order_by","in":"query","required":false,"schema":{"enum":["issued_date","applied_date","last_changed_at","valuation_usd"],"type":"string","default":"issued_date","title":"Order By"}},{"name":"order_dir","in":"query","required":false,"schema":{"enum":["asc","desc"],"type":"string","default":"desc","title":"Order Dir"}},{"name":"cohort","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"WS-6'.A5: filter by a per-manufacturer cohort. Accepts an A5 YAML basename (e.g., `roofing-manufacturer`). Unknown values return 400. Composes with the other filters via AND.","title":"Cohort"},"description":"WS-6'.A5: filter by a per-manufacturer cohort. Accepts an A5 YAML basename (e.g., `roofing-manufacturer`). Unknown values return 400. Composes with the other filters via AND."},{"name":"cohort_params","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"WS-6'.A5: override parameterized thresholds on the chosen cohort. Format: `key1:val1,key2:val2` (e.g., `roof_area_threshold:20000`). Unknown keys are ignored; missing keys fall back to cohort defaults.","title":"Cohort Params"},"description":"WS-6'.A5: override parameterized thresholds on the chosen cohort. Format: `key1:val1,key2:val2` (e.g., `roof_area_threshold:20000`). Unknown keys are ignored; missing keys fall back to cohort defaults."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedTargetPermitsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/territory/rollup":{"get":{"tags":["territory"],"summary":"Aggregated rollup over gv_target_permits by territory grain","description":"Aggregates the buyer-targeted matview into one row per group_key for the chosen group_by granularity (state / zip / metro_cbsa / jurisdiction). Returns count + total_valuation_usd + min/max issued_date + distinct_jurisdictions + distinct_contractors + top_segment per group. Filters mirror /v1/permits/target plus the 6 NULL-until-Phase-3'-close columns (architect / engineer / building_type / climate_zone / roof_area / hvac_tonnage); coverage_notes flags 0% coverage areas. See `docs/api/territory.md` for the full reference.\n\n**Examples:**\n\n1. **Filter-light** — state rollup of commercial new permits issued in 2024+:\n   `GET /v1/territory/rollup?group_by=state&target_segment=commercial_new&issued_after=2024-01-01`\n\n2. **Filtered** — top TX zips for commercial_new in 2025+:\n   `GET /v1/territory/rollup?group_by=zip&state=TX&target_segment=commercial_new&issued_after=2025-01-01&per_page=50`\n\n3. **Edge / `filter_required`** — naked `group_by=state` (no selective filter) returns 422 with `error.code=filter_required` and `error.detail.hints[]` listing 5 filters that satisfy the gate. See the error response example below.","operationId":"territory_rollup_v1_territory_rollup_get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"group_by","in":"query","required":true,"schema":{"enum":["state","zip","metro_cbsa","jurisdiction"],"type":"string","description":"Aggregation granularity. Required. One of: state, zip, metro_cbsa, jurisdiction.","title":"Group By"},"description":"Aggregation granularity. Required. One of: state, zip, metro_cbsa, jurisdiction."},{"name":"jurisdiction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated jurisdiction slugs filter.","title":"Jurisdiction"},"description":"Comma-separated jurisdiction slugs filter."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated state codes filter (e.g. 'TX,CA').","title":"State"},"description":"Comma-separated state codes filter (e.g. 'TX,CA')."},{"name":"metro_cbsa","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated CBSA codes filter (e.g. '19100,41700').","title":"Metro Cbsa"},"description":"Comma-separated CBSA codes filter (e.g. '19100,41700')."},{"name":"zip","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated zip codes filter (e.g. '75201,78701').","title":"Zip"},"description":"Comma-separated zip codes filter (e.g. '75201,78701')."},{"name":"target_segment","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated target segments. Allowed: adu_qualifying, civic, commercial_alteration, commercial_demolition, commercial_mep, commercial_new, commercial_pool, commercial_shell_only, commercial_signage, industrial_new, multifamily_alteration, multifamily_new, residential_alteration, residential_demolition, residential_foundation_only, residential_mep, residential_new_sf, temporary_construction_support.","title":"Target Segment"},"description":"Comma-separated target segments. Allowed: adu_qualifying, civic, commercial_alteration, commercial_demolition, commercial_mep, commercial_new, commercial_pool, commercial_shell_only, commercial_signage, industrial_new, multifamily_alteration, multifamily_new, residential_alteration, residential_demolition, residential_foundation_only, residential_mep, residential_new_sf, temporary_construction_support."},{"name":"permit_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated permit_type_normalized values.","title":"Permit Type"},"description":"Comma-separated permit_type_normalized values."},{"name":"is_multifamily","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Multifamily"}},{"name":"issued_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued After"}},{"name":"issued_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued Before"}},{"name":"value_min","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Value Min"}},{"name":"value_max","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Value Max"}},{"name":"bbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Spatial filter as 'min_lng,min_lat,max_lng,max_lat'.","title":"Bbox"},"description":"Spatial filter as 'min_lng,min_lat,max_lng,max_lat'."},{"name":"contractor_name_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}],"description":"ILIKE substring match on contractor_name.","title":"Contractor Name Contains"},"description":"ILIKE substring match on contractor_name."},{"name":"applicant_name_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}],"description":"ILIKE substring match on applicant_name.","title":"Applicant Name Contains"},"description":"ILIKE substring match on applicant_name."},{"name":"architect_name_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}],"description":"ILIKE substring match on architect_name. **0 matches until Phase 3' close backfill (WS-3'.5-NYC).** Surface populated in coverage_notes when this filter is used.","title":"Architect Name Contains"},"description":"ILIKE substring match on architect_name. **0 matches until Phase 3' close backfill (WS-3'.5-NYC).** Surface populated in coverage_notes when this filter is used."},{"name":"engineer_name_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":200},{"type":"null"}],"description":"ILIKE substring match on engineer_name. **0 matches until Phase 3' close backfill (WS-3'.5-NYC).**","title":"Engineer Name Contains"},"description":"ILIKE substring match on engineer_name. **0 matches until Phase 3' close backfill (WS-3'.5-NYC).**"},{"name":"building_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated IBC occupancy codes (building_type_normalized). **0 matches until Phase 3' close backfill (WS-3'.1-BUILD).**","title":"Building Type"},"description":"Comma-separated IBC occupancy codes (building_type_normalized). **0 matches until Phase 3' close backfill (WS-3'.1-BUILD).**"},{"name":"climate_zone","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated IECC 2021 climate zone codes (climate_zone_iecc). **0 matches until Phase 3' close backfill (WS-3'.1-BUILD).**","title":"Climate Zone"},"description":"Comma-separated IECC 2021 climate zone codes (climate_zone_iecc). **0 matches until Phase 3' close backfill (WS-3'.1-BUILD).**"},{"name":"roof_area_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"title":"Roof Area Min"}},{"name":"roof_area_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"title":"Roof Area Max"}},{"name":"hvac_tonnage_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"title":"Hvac Tonnage Min"}},{"name":"hvac_tonnage_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"title":"Hvac Tonnage Max"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Per Page"}},{"name":"order_by","in":"query","required":false,"schema":{"enum":["count","total_valuation_usd","min_issued_date","max_issued_date","group_key"],"type":"string","default":"count","title":"Order By"}},{"name":"order_dir","in":"query","required":false,"schema":{"enum":["asc","desc"],"type":"string","default":"desc","title":"Order Dir"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TerritoryRollupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/permits":{"get":{"tags":["permits"],"summary":"List permits across one or more jurisdictions","description":"Paginated list endpoint. At least one `jurisdiction` slug is required. All other parameters are optional filters that AND together. See `docs/api/permits.md` for examples and the change-feed pattern via `updated_since`.\n\n**Examples:**\n\n1. **Filter-light** — Austin permits, default sort by issued_date desc:\n   `GET /v1/permits?jurisdiction=austin`\n\n2. **Filtered** — NYC permits with a structured signal in description_extracted, issued in Q1 2026:\n   `GET /v1/permits?jurisdiction=nyc&has_extracted=true&issued_after=2026-01-01&issued_before=2026-03-31`\n\n3. **Pagination edge** — multi-jurisdiction broad query: the route uses an estimated `total_count` for unselective queries (`pagination.total_count_is_estimate=true` flags it):\n   `GET /v1/permits?jurisdiction=nyc,la,sf&per_page=500`","operationId":"list_permits_v1_permits_get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"jurisdiction","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated jurisdiction slugs (e.g., 'nyc,sf'). At least one required.","title":"Jurisdiction"},"description":"Comma-separated jurisdiction slugs (e.g., 'nyc,sf'). At least one required."},{"name":"issued_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued After"}},{"name":"issued_before","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued Before"}},{"name":"permit_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated `permit_type_normalized` values.","title":"Permit Type"},"description":"Comma-separated `permit_type_normalized` values."},{"name":"work_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated work classes (residential, commercial, industrial, unknown).","title":"Work Class"},"description":"Comma-separated work classes (residential, commercial, industrial, unknown)."},{"name":"bbox","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Spatial filter as 'min_lng,min_lat,max_lng,max_lat'. Uses PostGIS.","title":"Bbox"},"description":"Spatial filter as 'min_lng,min_lat,max_lng,max_lat'. Uses PostGIS."},{"name":"min_valuation","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Min Valuation"}},{"name":"has_extracted","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"True ⇒ only permits with a structured signal in `description_extracted` (jsonb_typeof = 'object', ~5.88M prod rows). False ⇒ only permits without structured signal (~10.20M prod rows). Null (default) ⇒ both. Note: this filter targets the adapter rule-based extraction column; LLM-extracted features live in a separate column (`description_llm_enriched`) not filtered by this flag.","title":"Has Extracted"},"description":"True ⇒ only permits with a structured signal in `description_extracted` (jsonb_typeof = 'object', ~5.88M prod rows). False ⇒ only permits without structured signal (~10.20M prod rows). Null (default) ⇒ both. Note: this filter targets the adapter rule-based extraction column; LLM-extracted features live in a separate column (`description_llm_enriched`) not filtered by this flag."},{"name":"updated_since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Filter `last_seen_at > this`. Use as a change-feed cursor.","title":"Updated Since"},"description":"Filter `last_seen_at > this`. Use as a change-feed cursor."},{"name":"contractor_canonical_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Filter to a single canonical contractor (WS-A). Pairs with a `jurisdiction` (entities are jurisdiction-scoped) to power the per-metro contractor page body. Served by the partial idx_permits_contractor_canonical_id.","title":"Contractor Canonical Id"},"description":"Filter to a single canonical contractor (WS-A). Pairs with a `jurisdiction` (entities are jurisdiction-scoped) to power the per-metro contractor page body. Served by the partial idx_permits_contractor_canonical_id."},{"name":"parcel_key","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single parcel by its NORMALIZED key (the parcel-key.ts contract: regexp_replace(lower(trim(parcel_apn)),'[^a-z0-9._-]','-','g') — the SAME key as parcel-inventory #627 / parcel-history filenames). Pair with `jurisdiction` to hit ix_permits_parcel_history (jurisdiction_id, key, issued_date DESC). Powers the ParcelWatchlist alert cron: parcel_key=Y + issued_after=<last-seen>.","title":"Parcel Key"},"description":"Filter to a single parcel by its NORMALIZED key (the parcel-key.ts contract: regexp_replace(lower(trim(parcel_apn)),'[^a-z0-9._-]','-','g') — the SAME key as parcel-inventory #627 / parcel-history filenames). Pair with `jurisdiction` to hit ix_permits_parcel_history (jurisdiction_id, key, issued_date DESC). Powers the ParcelWatchlist alert cron: parcel_key=Y + issued_after=<last-seen>."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Per Page"}},{"name":"order_by","in":"query","required":false,"schema":{"enum":["issued_date","applied_date","last_seen_at","valuation_usd"],"type":"string","default":"issued_date","title":"Order By"}},{"name":"order_dir","in":"query","required":false,"schema":{"enum":["asc","desc"],"type":"string","default":"desc","title":"Order Dir"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedPermitsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/permits/{permit_id}":{"get":{"tags":["permits"],"summary":"Get a single permit by silver-table id","description":"Returns one permit by its silver-table primary key. 404 if not found.","operationId":"get_permit_v1_permits__permit_id__get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"permit_id","in":"path","required":true,"schema":{"type":"integer","title":"Permit Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermitResponse"}}}},"404":{"description":"Permit not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/permits/by-canonical-uid/{canonical_uid}":{"get":{"tags":["permits"],"summary":"Get a single permit by canonical_uid","description":"PD-7 detail endpoint. Returns one permit by its stable cross-jurisdictional `canonical_uid` (= `{jurisdiction_id}:{source_permit_id}`). Cohort (`target_segment`), nested `dq_flags`, and nested NYC `pluto` presence + DQ-flag bundle are populated when applicable. 404 if not found; 400 if canonical_uid is malformed. Site SDKs MUST `encodeURIComponent(canonical_uid)` — pipe characters, spaces, AND slashes in source_permit_id values all need standard percent-encoding (e.g. NYC `1:982522764|Sidewalk Shed`, orlando `340:orlando_HIS1974N/C`, philadelphia `333:philadelphia_/././././`, fort_worth `111:CG20-...N/A`).","operationId":"get_permit_by_canonical_uid_v1_permits_by_canonical_uid__canonical_uid__get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"canonical_uid","in":"path","required":true,"schema":{"type":"string","title":"Canonical Uid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermitResponse"}}}},"400":{"description":"Malformed canonical_uid"},"404":{"description":"Permit not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/exports":{"post":{"tags":["exports"],"summary":"Create an async export job","description":"Enqueue a new export. Returns immediately with poll_url.\n\nEnforces a per-tenant max of 3 concurrent (`pending` + `running`)\nexports — beyond that, the buyer must wait for one to drain\nbefore requesting more.","operationId":"create_export_v1_exports_post","security":[{"PermitCoreApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["exports"],"summary":"List this tenant's exports (paginated)","description":"Per-tenant paginated list, most-recent first.\n\nCursor pagination via `before_id` keeps the result stable even if\nnew exports are inserted between page loads.","operationId":"list_exports_v1_exports_get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"before_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Cursor: return rows with exports.id < before_id. Pass `next_before_id` from the previous response.","title":"Before Id"},"description":"Cursor: return rows with exports.id < before_id. Pass `next_before_id` from the previous response."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/exports/{export_id}":{"get":{"tags":["exports"],"summary":"Poll an export job","description":"Return current job state. Generates a fresh signed URL on completion.\n\nMulti-tenant isolation: returns 404 if `export_id` belongs to a\ndifferent tenant — leaking existence would be a side-channel for\nenumerating other tenants' export IDs.","operationId":"get_export_v1_exports__export_id__get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"export_id","in":"path","required":true,"schema":{"type":"string","title":"Export Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/exports/warehouse/latest":{"get":{"tags":["exports"],"summary":"Latest warehouse-native bulk export (Enterprise tier)","description":"Return the newest bulk export's manifest with a fresh signed URL per file.\n\nEnterprise-gated (`plan == enterprise_custom`). Reads `warehouse/latest/manifest.json`\nfrom the private exports bucket, then mints a per-file pre-signed GET URL under the\ndated prefix. URLs are never persisted (incident response = rotate the R2 key).","operationId":"get_warehouse_latest_v1_exports_warehouse_latest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WarehouseManifestResponse"}}}}},"security":[{"PermitCoreApiKey":[]}]}},"/v1/jurisdictions/{slug}/cohorts/distribution":{"get":{"tags":["jurisdictions"],"summary":"Cohort distribution for a jurisdiction","description":"Return the `gv_target_permits` cohort distribution for one jurisdiction.\n\nPowers the site-build chat's 450-page cohort x metro landing-page surface\n(B1.2.2). All 18 canonical cohorts are returned even when count = 0.","operationId":"get_cohort_distribution_v1_jurisdictions__slug__cohorts_distribution_get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"description":"Canonical jurisdiction slug (e.g. `austin`, `nyc`, `chicago`).","title":"Slug"},"description":"Canonical jurisdiction slug (e.g. `austin`, `nyc`, `chicago`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortDistributionResponse"}}}},"401":{"description":"Missing or invalid bearer token."},"403":{"description":"Bearer token lacks the `query` scope."},"404":{"description":"Unknown jurisdiction slug."},"429":{"description":"Rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/metrics":{"get":{"tags":["system"],"summary":"Prometheus metrics (admin-scope-gated)","description":"Emit the in-process metrics registry as Prometheus text format.\n\nReturns a 200 with `Content-Type: text/plain; version=0.0.4`\n(per `prometheus_client.CONTENT_TYPE_LATEST`). Auth: admin scope\nrequired — same model as the future B5 Stripe webhook surface +\nother operator-only endpoints.","operationId":"metrics_endpoint_metrics_get","responses":{"200":{"description":"Successful Response"}},"security":[{"PermitCoreApiKey":[]}]}},"/v1/internal/tier-sync":{"post":{"tags":["internal"],"summary":"Site→backend tier-sync (internal; called from site's Stripe webhook handler)","description":"See module docstring for full contract + 8-step idempotency design.","operationId":"tier_sync_v1_internal_tier_sync_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TierSyncRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TierSyncResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"PermitCoreApiKey":[]}]}},"/v1/keys":{"get":{"tags":["keys"],"summary":"List the authenticated user's API keys (newest first)","description":"List the JWT-authenticated user's tenant's API keys, newest first.\n\nReturns key prefix + name + created_at + last_used_at for each key.\nNever returns the plaintext key (only available at creation time).","operationId":"list_api_keys_v1_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListApiKeysResponse"}}}}},"security":[{"PermitCoreApiKey":[]}]},"post":{"tags":["keys"],"summary":"Create a new API key for the authenticated user's tenant","description":"Mint a new API key for the JWT-authenticated user's tenant.\n\nThe plaintext key is returned EXACTLY ONCE in the response body —\nthe backend stores only its SHA-256 hash. Display it to the user\nimmediately and do not surface it again.\n\nAuth: Auth.js HS256 JWT (NEXTAUTH_SECRET). Tenant is lazy-initialized\nfrom the JWT's `sub`/`userId` claim on first call.","operationId":"create_api_key_v1_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"PermitCoreApiKey":[]}]}},"/v1/keys/{key_prefix}":{"delete":{"tags":["keys"],"summary":"Delete an API key by prefix (tenant-scoped; 404 if not owner)","description":"Revoke an API key by prefix; tenant-scoped.\n\nDeletion is permanent. A 404 is returned for both unknown prefixes\nand prefixes owned by a different tenant to avoid leaking existence\nof cross-tenant keys (SD-NEW-U).","operationId":"delete_api_key_v1_keys__key_prefix__delete","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"key_prefix","in":"path","required":true,"schema":{"type":"string","title":"Key Prefix"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/keys/{key_prefix}/usage":{"get":{"tags":["keys"],"summary":"Get usage metrics for an API key (scoped to authenticated tenant)","description":"Return current billing-period usage metrics for one API key.\n\nCombines aggregate counters (RateLimitBucket) with usage_events\ndrill-down (last-24h throttled count + 50 most recent requests) in\na single response. Inline tier object includes quota + display name.\n\nSupports `If-None-Match`/ETag conditional GETs — clients should send\nback the previous ETag to short-circuit unchanged responses to 304.\nCache-Control: private, max-age=60.\n\nAuth: Auth.js HS256 JWT. Cross-tenant prefixes return 404.","operationId":"get_key_usage_v1_keys__key_prefix__usage_get","security":[{"PermitCoreApiKey":[]}],"parameters":[{"name":"key_prefix","in":"path","required":true,"schema":{"type":"string","title":"Key Prefix"}},{"name":"If-None-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"If-None-Match"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"CohortDistributionResponse":{"properties":{"jurisdiction_slug":{"type":"string","title":"Jurisdiction Slug","description":"Canonical slug (matches `jurisdictions.slug`)."},"jurisdiction_display_name":{"type":"string","title":"Jurisdiction Display Name","description":"Human-readable display name in `\"<jurisdiction_name>, <state_code>\"` form (e.g. `Austin, TX`)."},"as_of_utc":{"type":"string","format":"date-time","title":"As Of Utc","description":"ISO-8601 UTC timestamp of the most recent `gv_target_permits` refresh that populated this slice. Refresh cadence is nightly at approximately 09:00 UTC. Counts reflect that snapshot; live writes to underlying silver between refreshes are not visible."},"total_target_permits":{"type":"integer","minimum":0.0,"title":"Total Target Permits","description":"Total `gv_target_permits` rows for this jurisdiction (incl. NULL segment)."},"cohort_distribution":{"additionalProperties":{"type":"integer"},"type":"object","title":"Cohort Distribution","description":"Map of `target_segment` cohort name -> row count. All 18 canonical cohorts are present even when count = 0. NULL-segment rows are NOT in this map; subtract sum(cohort_distribution) from `total_target_permits` to derive the NULL-segment count."}},"type":"object","required":["jurisdiction_slug","jurisdiction_display_name","as_of_utc","total_target_permits","cohort_distribution"],"title":"CohortDistributionResponse","description":"Response envelope for GET /v1/jurisdictions/{slug}/cohorts/distribution.\n\nReturns the count of `gv_target_permits` rows per `target_segment` cohort\nfor a single jurisdiction. All 18 canonical cohorts (16 structural + 2\nWS-PE-LLM-1) are included even when count = 0 so consumers (e.g. the SEO\nlanding-page generator at cohort x metro grain) can render uniform pages\nwithout conditional logic.\n\n`as_of_utc` is the matview-refresh timestamp — the wall-clock of the most\nrecent `gv_target_permits` REFRESH that populated the underlying summary\nrelation (Sub-phase B1, PR shipping this rename of the semantic). Refresh\ncadence is nightly at approximately 09:00 UTC. Counts reflect that\nsnapshot; live writes to silver between refreshes are NOT visible here.\n\nConsumers needing live counts should query `/v1/permits/target` instead\n(live but heavier; goes against the primary matview directly without the\nsummary pre-aggregation)."},"CoverageNote":{"properties":{"jurisdiction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jurisdiction","description":"Jurisdiction slug affected by the coverage gap, or null for fleet-wide pending-backfill cases."},"field":{"type":"string","title":"Field","description":"Column or filter name affected (e.g. 'address_zip' or 'architect_name')."},"coverage":{"type":"string","title":"Coverage","description":"Coverage percentage as a string (e.g. '0%')."},"impact":{"type":"string","title":"Impact","description":"Human-readable explanation of the gap's effect on the response."}},"type":"object","required":["field","coverage","impact"],"title":"CoverageNote","description":"Structured cohort-completeness signal returned inline with rollups.\n\nPopulated automatically in two cases (per WS-3'.2 CHECKPOINT A ack):\n\n1. `group_by=zip` AND the queried cohort includes any of the 4 metros\n   with 0% `address_zip` population (chicago / scottsdale / miami /\n   mesa per audit §3.12). One note per affected jurisdiction.\n2. Filter applied on a column that's NULL until Phase 3' close\n   backfill (architect_name / engineer_name / building_type_normalized\n   / climate_zone_iecc / roof_area_estimate_sqft /\n   hvac_tonnage_estimate). One note per filter column. `jurisdiction`\n   is null for fleet-wide pending-backfill cases."},"CreateApiKeyRequest":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"}},"type":"object","required":["name"],"title":"CreateApiKeyRequest","description":"POST /v1/keys body. Name validation per SD-NEW-U input-validation discipline."},"CreateApiKeyResponse":{"properties":{"key":{"type":"string","title":"Key"},"key_prefix":{"type":"string","title":"Key Prefix"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["key","key_prefix","name","created_at"],"title":"CreateApiKeyResponse","description":"POST /v1/keys 201 response. Plaintext key returned ONCE; never queryable again."},"DqFlags":{"properties":{"issued_date_is_parse_bug":{"type":"boolean","title":"Issued Date Is Parse Bug","default":false},"issued_date_is_placeholder":{"type":"boolean","title":"Issued Date Is Placeholder","default":false},"issued_date_is_future_dated":{"type":"boolean","title":"Issued Date Is Future Dated","default":false},"valuation_usd_is_placeholder":{"type":"boolean","title":"Valuation Usd Is Placeholder","default":false},"valuation_usd_is_oob":{"type":"boolean","title":"Valuation Usd Is Oob","default":false},"units_residential_is_placeholder":{"type":"boolean","title":"Units Residential Is Placeholder","default":false},"units_total_is_placeholder":{"type":"boolean","title":"Units Total Is Placeholder","default":false},"units_residential_is_oob":{"type":"boolean","title":"Units Residential Is Oob","default":false},"units_total_is_oob":{"type":"boolean","title":"Units Total Is Oob","default":false},"units_commercial_is_placeholder":{"type":"boolean","title":"Units Commercial Is Placeholder","default":false},"geocode_is_sentinel":{"type":"boolean","title":"Geocode Is Sentinel","default":false},"geocode_is_oob":{"type":"boolean","title":"Geocode Is Oob","default":false}},"type":"object","title":"DqFlags","description":"12 silver bounds-rule flag values per WS-PE-DQ-2 registry.\n\nPer SD-039 additive-only enforcement: NULL silver values coerce to False\n(the \"rule has not flagged this value\" semantic — best-effort, not\n\"independently verified clean\"). All 12 keys are always present when\nthe parent `PermitResponse.dq_flags` is non-null."},"ExportCreateResponse":{"properties":{"export_id":{"type":"string","title":"Export Id"},"status":{"$ref":"#/components/schemas/ExportStatus"},"poll_url":{"type":"string","title":"Poll Url"}},"type":"object","required":["export_id","status","poll_url"],"title":"ExportCreateResponse","description":"Response shape for POST /v1/exports — fires-and-returns immediately."},"ExportDateRange":{"properties":{"start":{"type":"string","format":"date","title":"Start"},"end":{"type":"string","format":"date","title":"End"}},"type":"object","required":["start","end"],"title":"ExportDateRange"},"ExportFormat":{"type":"string","enum":["csv","xlsx"],"title":"ExportFormat"},"ExportListItem":{"properties":{"export_id":{"type":"string","title":"Export Id"},"cohort":{"type":"string","title":"Cohort"},"metros":{"items":{"type":"string"},"type":"array","title":"Metros"},"format":{"$ref":"#/components/schemas/ExportFormat"},"status":{"$ref":"#/components/schemas/ExportStatus"},"requested_at":{"type":"string","format":"date-time","title":"Requested At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["export_id","cohort","metros","format","status","requested_at"],"title":"ExportListItem","description":"Element shape for GET /v1/exports list."},"ExportListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ExportListItem"},"type":"array","title":"Items"},"next_before_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Before Id"}},"type":"object","required":["items"],"title":"ExportListResponse"},"ExportRequest":{"properties":{"cohort":{"type":"string","enum":["roofing","hvac","solar","signage"],"title":"Cohort","description":"Cohort filter key. Inline fallback supports: ['hvac', 'roofing', 'signage', 'solar']. WS-6'.A5 YAML library extends this."},"metros":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Metros","description":"List of jurisdiction slugs (e.g., ['chicago_il'])."},"format":{"$ref":"#/components/schemas/ExportFormat"},"date_range":{"$ref":"#/components/schemas/ExportDateRange"},"expires_in_hours":{"type":"integer","maximum":168.0,"minimum":1.0,"title":"Expires In Hours","default":24}},"type":"object","required":["cohort","metros","format","date_range"],"title":"ExportRequest"},"ExportResponse":{"properties":{"export_id":{"type":"string","title":"Export Id"},"status":{"$ref":"#/components/schemas/ExportStatus"},"signed_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signed Url"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"row_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Count"},"file_size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"File Size Bytes"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["export_id","status"],"title":"ExportResponse","description":"Response shape for GET /v1/exports/{export_id}."},"ExportStatus":{"type":"string","enum":["pending","running","completed","failed"],"title":"ExportStatus"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"db_reachable":{"type":"boolean","title":"Db Reachable"}},"type":"object","required":["status","db_reachable"],"title":"HealthResponse"},"ListApiKeyItem":{"properties":{"key_prefix":{"type":"string","title":"Key Prefix"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"}},"type":"object","required":["key_prefix","name","created_at","last_used_at"],"title":"ListApiKeyItem","description":"GET /v1/keys list item. NEVER includes plaintext or key_hash."},"ListApiKeysResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/ListApiKeyItem"},"type":"array","title":"Keys"}},"type":"object","required":["keys"],"title":"ListApiKeysResponse","description":"GET /v1/keys response wrapper."},"PaginatedPermitsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PermitResponse"},"type":"array","title":"Items"},"pagination":{"$ref":"#/components/schemas/Pagination"},"filters_applied":{"additionalProperties":true,"type":"object","title":"Filters Applied","description":"Echo of the filters this query was run with."},"query_time_ms":{"type":"integer","minimum":0.0,"title":"Query Time Ms","description":"Server-side query time in milliseconds (excludes serialization)."}},"type":"object","required":["items","pagination","filters_applied","query_time_ms"],"title":"PaginatedPermitsResponse"},"PaginatedTargetPermitsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TargetPermitResponse"},"type":"array","title":"Items"},"pagination":{"$ref":"#/components/schemas/Pagination"},"filters_applied":{"additionalProperties":true,"type":"object","title":"Filters Applied"},"query_time_ms":{"type":"integer","title":"Query Time Ms"}},"type":"object","required":["items","pagination","filters_applied","query_time_ms"],"title":"PaginatedTargetPermitsResponse"},"Pagination":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page"},"per_page":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Per Page"},"total_pages":{"type":"integer","minimum":0.0,"title":"Total Pages"},"total_count":{"type":"integer","minimum":0.0,"title":"Total Count"},"total_count_is_estimate":{"type":"boolean","title":"Total Count Is Estimate","description":"True when `total_count` is not the exact match count. Two cases: (a) jurisdiction-only queries use a `pg_class.reltuples` estimate to avoid scanning the full 7M-row matview; (b) selective-filter queries cap the exact-count work at TARGET_COUNT_CAP rows — when `has_more` is True, true match count exceeds `total_count` and the caller should render `total_count+` (e.g., '1,000+'). False only when an exact, uncapped match count was computed.","default":false},"has_more":{"type":"boolean","title":"Has More","description":"True when there are more matching rows beyond `total_count`. Set by the capped-count path (2026-05-30 pool-exhaustion fix): selective filters cap exact counting at TARGET_COUNT_CAP rows so a pathological filter cannot drag the pool down. Render `total_count+` when this is True. Mutually consistent with `total_count_is_estimate=True`; an estimate-mode response also sets `has_more=False` because the estimate is the best-effort true count.","default":false},"next_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Page"},"prev_page":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Page"}},"type":"object","required":["page","per_page","total_pages","total_count"],"title":"Pagination"},"PermitResponse":{"properties":{"id":{"type":"integer","title":"Id"},"jurisdiction_slug":{"type":"string","title":"Jurisdiction Slug","description":"Jurisdiction this permit was issued by."},"source_permit_id":{"type":"string","title":"Source Permit Id","description":"The unique key per row in the source."},"canonical_uid":{"type":"string","title":"Canonical Uid","description":"`{jurisdiction_id}:{source_permit_id}`."},"filing_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filing Id","description":"Source's filing/job identifier; multiple rows can share one filing."},"address_full":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Full"},"address_street":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Street"},"address_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address City"},"address_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address State"},"address_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Zip"},"parcel_apn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parcel Apn"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng"},"geocode_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Geocode Confidence"},"geocode_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geocode Source"},"permit_type_raw":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Permit Type Raw","description":"Source's permit-type string. Use `permit_type_normalized` for filtering."},"permit_type_normalized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Permit Type Normalized","description":"One of the fixed buckets in docs/TAXONOMY.md."},"work_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Work Class"},"building_subtype":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Subtype"},"is_new_construction":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is New Construction"},"is_alteration":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Alteration"},"is_demolition":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Demolition"},"units_residential":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Residential"},"units_commercial":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Commercial"},"square_footage_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Square Footage Total"},"valuation_usd":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Valuation Usd","description":"Permit valuation in whole USD. Not adjusted for inflation."},"applied_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Applied Date"},"issued_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued Date"},"finaled_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Finaled Date"},"expired_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expired Date"},"current_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Status"},"status_raw":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Raw"},"description_raw":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Raw"},"description_extracted":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Description Extracted","description":"Adapter-populated structured fields extracted from `description_raw`. Shape is **per-jurisdiction** — the key set varies by adapter (e.g. Austin: `applicant_org`, `contractor_trade`, `number_of_floors`, `trade_valuation_*`; Boston: `permit_category`; Gilbert: `project_name`; San Antonio: `primary_contact_company`; NYC: `bin`, `block`, `lot`, `applicant_professional_title`, `applicant_first_name`, `applicant_last_name`; Atlanta: `type_combo`, `use`). See `docs/jurisdictions/<slug>.md` for the full per-adapter inventory. JSONB-typed: an object = structured signal present (~5.88M prod rows); a JSON `null` literal or SQL NULL = no signal (~10.20M prod rows). For LLM-extracted features see the separate `description_llm_enriched` column (not exposed on this response shape; see `/v1/permits/target` for the shape that does expose it)."},"applicant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Name"},"applicant_role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Role"},"contractor_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contractor Name"},"contractor_license":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contractor License"},"architect_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Architect Name"},"engineer_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Engineer Name"},"owner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Name"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"first_seen_at":{"type":"string","format":"date-time","title":"First Seen At"},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At"},"last_changed_at":{"type":"string","format":"date-time","title":"Last Changed At"},"units_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Total"},"year_built":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year Built"},"building_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Class"},"zoning_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zoning District"},"lot_area":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lot Area"},"climate_zone_iecc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Climate Zone Iecc"},"building_type_normalized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Type Normalized"},"roof_area_estimate_sqft":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roof Area Estimate Sqft"},"hvac_tonnage_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hvac Tonnage Estimate"},"description_llm_enriched":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Description Llm Enriched","description":"LLM-extracted features (e.g. `scope_summary`, `unit_count`, `square_footage`, `tenant_improvement_flag`). Separate column from `description_extracted`. Currently populated for Austin + Chicago commercial_new only (~17,861 prod rows); broader cross-metro LLM extraction is filed under a future WS."},"cohort":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cohort","description":"Buyer-targeting bucket. One of: commercial_new, commercial_alteration, commercial_mep, commercial_signage, multifamily_new, multifamily_alteration, adu_qualifying, commercial_demolition, residential_mep, residential_new_sf, commercial_pool, residential_demolition, residential_foundation_only, residential_alteration, commercial_shell_only, civic, temporary_construction_support. null = row not in matview OR in matview but unsegmented. Identical taxonomy to `/v1/permits/target` target_segment."},"dq_flags":{"anyOf":[{"$ref":"#/components/schemas/DqFlags"},{"type":"null"}],"description":"Per-row data-quality flag bundle from the WS-PE-DQ-2 bounds-rule registry. NULL silver flag values are coerced to False (the \"rule has not flagged this value\" semantic, best-effort, not \"independently verified clean\"). All 12 keys always present when this field is non-null. See `docs/audits/ws-pe-dq-2-*` for rule definitions."},"pluto":{"anyOf":[{"$ref":"#/components/schemas/Pluto"},{"type":"null"}],"description":"NYC PLUTO parcel-data presence and per-flag DQ status. POPULATED on the by-canonical-uid detail endpoint (`GET /v1/permits/by-canonical-uid/{canonical_uid}`); ALWAYS NULL on the list endpoint (`GET /v1/permits`) — the nyc_pluto LEFT JOIN would add ~75ms p95 to the list query, disqualifying for the browse path. For NYC rows where parcel_apn does not match a PLUTO bbl this field is null on detail as well. Non-NYC rows always return null. See `scripts/generate_enriched_recency_feed_artifact.py:152-200` for the canonical presence-check pattern."}},"type":"object","required":["id","jurisdiction_slug","source_permit_id","canonical_uid","first_seen_at","last_seen_at","last_changed_at"],"title":"PermitResponse","description":"One silver-layer permit row.\n\nMoney is exposed as `int` USD (not Decimal) — permit valuations are\nalways reported in whole dollars by the source jurisdictions, and\nconsumer SDKs in JS/Go handle int more cleanly than arbitrary-precision\ndecimals. lat/lng are floats; geom is omitted (the bbox filter operates\nserver-side)."},"Pluto":{"properties":{"is_placeholder":{"type":"boolean","title":"Is Placeholder","description":"year_built flagged as placeholder in PLUTO; SD-039 NULL→False."},"oob":{"type":"boolean","title":"Oob","description":"year_built flagged as OOB in PLUTO; SD-039 NULL→False."}},"type":"object","required":["is_placeholder","oob"],"title":"Pluto","description":"NYC PLUTO presence + per-flag DQ status.\n\nPopulated only on the by-canonical-uid detail endpoint when the\npermit's parcel_apn matches a `nyc_pluto.bbl` row. Per the canonical\npresence-check pattern (PR-B-FUP 2026-06-04), `is_placeholder` and\n`oob` are False when the join landed but the PLUTO DQ flag is NULL\n(legitimate \"rule has not flagged this value\" semantic)."},"TargetPermitResponse":{"properties":{"canonical_uid":{"type":"string","title":"Canonical Uid"},"jurisdiction_slug":{"type":"string","title":"Jurisdiction Slug"},"jurisdiction_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jurisdiction Name"},"state_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State Code"},"metro_cbsa":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metro Cbsa"},"target_segment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Segment","description":"Buyer-targeting bucket. One of: commercial_new, commercial_alteration, commercial_mep (electrical/plumbing/HVAC in commercial buildings), commercial_signage (sign permits in commercial buildings), multifamily_new (5+ unit new construction), multifamily_alteration (5+ unit alteration/addition), adu_qualifying, commercial_demolition (commercial/mixed/industrial demolition; per WS-PE-CLASSIFY-1-EXTEND), residential_mep (residential MEP work incl. mep_fire; per WS-PE-CLASSIFY-1-EXTEND + WS-PE-CLASSIFY-2), residential_new_sf (residential new single-family; per WS-PE-CLASSIFY-2), commercial_pool (commercial pool/spa; per WS-PE-CLASSIFY-2), residential_demolition (residential demolition; per WS-PE-CLASSIFY-2), residential_foundation_only (residential foundation-only; per WS-PE-CLASSIFY-2), residential_alteration (residential alteration; per WS-PE-CLASSIFY-2), commercial_shell_only (commercial shell-only; per WS-PE-CLASSIFY-2), civic (schools/hospitals/government via LLM use_class signal; per WS-PE-LLM-1), temporary_construction_support (scaffold/sidewalk-shed/pipe-scaffold/construction-fence/overhead-protection via LLM scope_summary keyword; per WS-PE-LLM-1). NULL means the row passes the gold-view WHERE filter but doesn't fit any segment (e.g. commercial 'other')."},"address_full":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Full"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng"},"permit_type_normalized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Permit Type Normalized"},"work_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Work Class"},"building_subtype":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Subtype"},"units_residential":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Residential"},"units_commercial":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Commercial"},"square_footage_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Square Footage Total"},"valuation_usd":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Valuation Usd"},"applied_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Applied Date"},"issued_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Issued Date"},"current_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Status"},"contractor_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contractor Name"},"owner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Name"},"description_raw":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description Raw"},"description_extracted":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Description Extracted","description":"Adapter-populated structured fields extracted from `description_raw` (rule-based, per-adapter). Shape varies by jurisdiction; see `docs/jurisdictions/<slug>.md` for the key set each adapter emits. For LLM-extracted features see `description_llm_enriched` on this same response."},"last_changed_at":{"type":"string","format":"date-time","title":"Last Changed At"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"building_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Class"},"zoning_district":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zoning District"},"units_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Total"},"lot_area":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lot Area"},"year_built":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year Built"},"applicant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Name"},"architect_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Architect Name"},"engineer_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Engineer Name"},"parcel_apn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parcel Apn"},"address_zip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Zip"},"description_llm_enriched":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Description Llm Enriched","description":"LLM-extracted features (e.g. `scope_summary`, `unit_count`, `square_footage`, `tenant_improvement_flag`). Separate column from `description_extracted` (which carries adapter rule-based fields). Currently populated for Austin + Chicago commercial_new only (~17,861 prod rows per WS-3'.1-AUDIT §3.9); broader cross-metro LLM extraction is filed under a future WS."},"contractor_canonical_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Contractor Canonical Id"},"owner_canonical_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Owner Canonical Id"},"climate_zone_iecc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Climate Zone Iecc"},"building_type_normalized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Building Type Normalized"},"roof_area_estimate_sqft":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roof Area Estimate Sqft"},"hvac_tonnage_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hvac Tonnage Estimate"},"permit_type_raw":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Permit Type Raw"}},"type":"object","required":["canonical_uid","jurisdiction_slug","last_changed_at"],"title":"TargetPermitResponse","description":"One gold-view row from `gv_target_permits`.\n\nSubset of `PermitResponse` columns plus `target_segment`, exposed so\nbuyers can query the buyer-grade slice (commercial / multifamily /\nqualifying ADU) directly without having to reconstruct the WHERE\nclause client-side.\n\n`id` is intentionally omitted — the materialized view doesn't expose\n`permits.id`. Use `canonical_uid` as the per-row identifier."},"TerritoryRollupResponse":{"properties":{"group_by":{"type":"string","title":"Group By","description":"Aggregation granularity. One of: state, zip, metro_cbsa, jurisdiction."},"items":{"items":{"$ref":"#/components/schemas/TerritoryRollupRow"},"type":"array","title":"Items"},"pagination":{"$ref":"#/components/schemas/Pagination"},"coverage_notes":{"items":{"$ref":"#/components/schemas/CoverageNote"},"type":"array","title":"Coverage Notes","description":"Structured cohort-completeness signals. See CoverageNote schema for the populated cases. Empty list when no gaps apply to this query."},"filters_applied":{"additionalProperties":true,"type":"object","title":"Filters Applied"},"query_time_ms":{"type":"integer","minimum":0.0,"title":"Query Time Ms"}},"type":"object","required":["group_by","items","pagination","filters_applied","query_time_ms"],"title":"TerritoryRollupResponse","description":"Response envelope for /v1/territory/rollup."},"TerritoryRollupRow":{"properties":{"group_key":{"type":"string","title":"Group Key","description":"Aggregation key as TEXT. For group_by=state: 2-letter state code (e.g. 'TX'). For group_by=zip: 5-digit zip (e.g. '75201'). For group_by=metro_cbsa: 5-digit CBSA code (e.g. '19100'). For group_by=jurisdiction: slug (e.g. 'nyc')."},"group_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Label","description":"Human-readable label for the group_key. State name for group_by=state ('Texas'); metro name for group_by=metro_cbsa ('Dallas-Fort Worth-Arlington, TX MSA'); jurisdiction name for group_by=jurisdiction ('New York City'). Returns the raw zip for group_by=zip (no label lookup)."},"count":{"type":"integer","minimum":0.0,"title":"Count","description":"Number of matview rows in this group after filtering."},"total_valuation_usd":{"type":"integer","minimum":0.0,"title":"Total Valuation Usd","description":"Sum of valuation_usd across the group, in whole USD. NULL valuations are excluded from the sum; empty groups return 0."},"min_issued_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Min Issued Date","description":"Earliest issued_date in the group; null if no row has issued_date."},"max_issued_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Max Issued Date","description":"Latest issued_date in the group; null if no row has issued_date."},"distinct_jurisdictions":{"type":"integer","minimum":0.0,"title":"Distinct Jurisdictions","description":"Count of distinct jurisdiction_ids in the group. 1 for most zip groups; up to 18 for state/metro_cbsa groups across all production jurisdictions."},"distinct_contractors":{"type":"integer","minimum":0.0,"title":"Distinct Contractors","description":"Count of distinct non-null contractor_name values in the group. NULL contractor_names excluded. Most expensive metric to compute (COUNT DISTINCT); see endpoint docs for the approx-distinct roadmap."},"top_segment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Top Segment","description":"Modal target_segment in the group (mode() WITHIN GROUP ORDER BY target_segment). Null when no row in the group has a non-null target_segment."}},"type":"object","required":["group_key","count","total_valuation_usd","distinct_jurisdictions","distinct_contractors"],"title":"TerritoryRollupRow","description":"One aggregated territory row (state / zip / metro_cbsa / jurisdiction).\n\n`group_key` is always TEXT regardless of source column type — metro_cbsa\nis coerced from numeric, jurisdiction is coerced from int (returned as\nthe slug). Per WS-3'.2 CHECKPOINT A ack 1: symmetry over type fidelity."},"TierSyncRequest":{"properties":{"external_user_id":{"type":"string","maxLength":255,"minLength":1,"title":"External User Id"},"new_tier_id":{"type":"string","enum":["free","pro","enterprise","hobby","builder","growth","pro_entry","pro_mid","pro_high"],"title":"New Tier Id"},"stripe_event_id":{"type":"string","maxLength":255,"minLength":1,"title":"Stripe Event Id"},"stripe_subscription_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Stripe Subscription Id"},"effective_tier_id_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Effective Tier Id Until"},"triggered_by":{"type":"string","enum":["webhook","admin","system","backfill"],"title":"Triggered By","default":"webhook"}},"type":"object","required":["external_user_id","new_tier_id","stripe_event_id"],"title":"TierSyncRequest","description":"Site → backend tier-sync request body.\n\nPer cross-chat coordination (locked 2026-05-30; extended 2026-06-03 Half B-1):\n  - external_user_id maps to backend Tenant.external_user_id (lazy-init pattern)\n  - new_tier_id is one of 9 accepted slugs. Canonical preferred set post-ASK-2\n    is {free, pro, enterprise}; the 6 legacy paid IDs (hobby/builder/growth/\n    pro_*) remain accepted defensively for backward compat but are not\n    actively used (zero prod tenants on legacy paid as of 2026-06-03). New\n    Pro subscribers should send tier_id=\"pro\".\n  - stripe_event_id is the idempotency key (matches stripe_webhook_events.id PK)\n  - effective_tier_id_until is set on downgrades for F95 grandfather logic.\n    Backend's `_resolve_effective_tier` (rate_limit.py) carries a\n    direction guard (added 2026-06-03) that makes this field INERT on\n    upgrades and same-rank transitions — sending it on a free→pro\n    UPGRADE event is harmless but indicates site-side contract drift.\n    Per the 2026-06-03 cross-chat agreement the site's standing\n    behavior is NEVER-SEND on any event (period-end cancel semantics\n    make the cushion redundant; immediate cancels intend immediate-free).\n    See `docs/cross-chat-coordination/site-tier-model-divergence-flag-2026-06-03.md`.\n  - triggered_by defaults to 'webhook' (Stripe-driven); other values for non-Stripe paths"},"TierSyncResponse":{"properties":{"tenant_id":{"type":"integer","title":"Tenant Id"},"tier_id":{"type":"string","title":"Tier Id"},"display_tier_id":{"type":"string","title":"Display Tier Id"},"tier_history_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tier History Id"},"idempotent_replay":{"type":"boolean","title":"Idempotent Replay"}},"type":"object","required":["tenant_id","tier_id","display_tier_id","idempotent_replay"],"title":"TierSyncResponse","description":"Site → backend tier-sync response body.\n\n`idempotent_replay=true` indicates the stripe_event_id was already processed;\nno state change occurred (site can treat as success without retrying).\n\n`display_tier_id` is the Half A 8→3 collapse (free/pro/enterprise) added in\nPhase 2A — additive alongside `tier_id`; consumers reading `tier_id` for the\ngranular 8-tier slug keep working, consumers wanting the collapsed display\nread `display_tier_id`."},"UsagePeriod":{"properties":{"start":{"type":"string","format":"date","title":"Start"},"end":{"type":"string","format":"date","title":"End"}},"type":"object","required":["start","end"],"title":"UsagePeriod","description":"Current billing-period window for the usage snapshot."},"UsageRecentRequest":{"properties":{"request_at":{"type":"string","format":"date-time","title":"Request At"},"endpoint":{"type":"string","title":"Endpoint"},"method":{"type":"string","title":"Method"},"status_code":{"type":"integer","title":"Status Code"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"}},"type":"object","required":["request_at","endpoint","method","status_code","duration_ms"],"title":"UsageRecentRequest","description":"Single drill-down row (from usage_events table per F129)."},"UsageResponse":{"properties":{"period":{"$ref":"#/components/schemas/UsagePeriod"},"request_count":{"type":"integer","title":"Request Count"},"request_count_today":{"type":"integer","title":"Request Count Today"},"quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota"},"percent_used":{"type":"number","title":"Percent Used"},"throttled_24h":{"type":"integer","title":"Throttled 24H"},"recent_requests":{"items":{"$ref":"#/components/schemas/UsageRecentRequest"},"type":"array","title":"Recent Requests"},"tier":{"$ref":"#/components/schemas/UsageTierInline"}},"type":"object","required":["period","request_count","request_count_today","quota","percent_used","throttled_24h","recent_requests","tier"],"title":"UsageResponse","description":"GET /v1/keys/{prefix}/usage 200 response shape (per site contract).\n\n`request_count_today` was added in Half B-1 (2026-06-03) to support site\nrendering of daily-capped tiers (e.g., grandfathered Free at 1,000/day):\nthe existing `quota` + `percent_used` fields mirror the MONTHLY quota\nonly, so for tiers where `tier.daily_request_quota` is non-null the site\nneeds the daily numerator to render \"N / 1,000 today\" honestly. The new\nfield reads from the same RateLimitBucket today's row that the rate-limit\nenforcer uses for daily-quota checks (rate_limit.py:162-195) — so the\ndisplayed number matches what actually throttles. Calendar-day-aligned\nUTC (NOT sliding-24h); rolls over at UTC midnight."},"UsageTierInline":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"display_tier_id":{"type":"string","title":"Display Tier Id"},"monthly_request_quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Request Quota"},"daily_request_quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Request Quota"},"monthly_export_quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Export Quota"},"daily_export_quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Export Quota"}},"type":"object","required":["id","name","display_tier_id","monthly_request_quota","daily_request_quota","monthly_export_quota","daily_export_quota"],"title":"UsageTierInline","description":"Inline tier object per F140.\n\nSaves a round-trip and surfaces effective/prior tier during grandfather windows.\n\n`display_tier_id` is the Half A 8→3 collapse (free/pro/enterprise) added in\nPhase 2A — additive alongside `id`; consumers reading `id` for the granular\n8-tier slug keep working, consumers wanting the collapsed display read\n`display_tier_id`. See `tiers.display_tier_id` column for the mapping."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WarehouseFile":{"properties":{"table":{"type":"string","title":"Table"},"format":{"type":"string","title":"Format"},"filename":{"type":"string","title":"Filename"},"row_count":{"type":"integer","title":"Row Count"},"bytes":{"type":"integer","title":"Bytes"},"preserves_tristate_dq_flags":{"type":"boolean","title":"Preserves Tristate Dq Flags"},"signed_url":{"type":"string","title":"Signed Url"}},"type":"object","required":["table","format","filename","row_count","bytes","preserves_tristate_dq_flags","signed_url"],"title":"WarehouseFile"},"WarehouseManifestResponse":{"properties":{"schema_version":{"type":"integer","title":"Schema Version"},"data_as_of":{"type":"string","title":"Data As Of"},"generated_at_utc":{"type":"string","title":"Generated At Utc"},"signed_url_expires_in_hours":{"type":"integer","title":"Signed Url Expires In Hours"},"tables":{"additionalProperties":true,"type":"object","title":"Tables"},"honesty_semantics":{"additionalProperties":true,"type":"object","title":"Honesty Semantics"},"files":{"items":{"$ref":"#/components/schemas/WarehouseFile"},"type":"array","title":"Files"}},"type":"object","required":["schema_version","data_as_of","generated_at_utc","signed_url_expires_in_hours","tables","honesty_semantics","files"],"title":"WarehouseManifestResponse"}},"securitySchemes":{"PermitCoreApiKey":{"type":"http","scheme":"bearer"}}},"tags":[{"name":"permits","description":"List + detail endpoints over the full silver layer (buyer-targeted slice is `target_permits`)."},{"name":"target_permits","description":"Curated buyer-targeted slice (commercial / multifamily / qualifying ADU) over the `gv_target_permits` matview."},{"name":"territory","description":"Aggregated rollups over `gv_target_permits` by state / zip / metro_cbsa / jurisdiction (WS-3'.2)."},{"name":"exports","description":"Async buyer-pack exports — CSV / Excel; per-tenant signed R2 URLs (WS-7'.B4)."},{"name":"jurisdictions","description":"Per-jurisdiction surface — cohort distribution + future metadata endpoints. Backs the site-build cohort x metro landing-page generator (Phase 7' B1.2.2)."},{"name":"system","description":"Health checks; not auth-protected."}]}