GET /v1/jurisdictions/{slug}/cohorts/distribution
API Reference
GET /v1/jurisdictions/{slug}/cohorts/distribution
Per-jurisdiction breakdown across the canonical 18 cohorts. Cached 1 hour.
GET
GET /v1/jurisdictions/{slug}/cohorts/distribution
Returns the canonical 18-cohort permit distribution for a single jurisdiction. The response is always 18 keys — cohorts with no permits are zero-filled. Counts reflect the rolling-window total permits classified to each cohort for that jurisdiction as of theDocumentation Index
Fetch the complete documentation index at: https://docs.permitcore.io/llms.txt
Use this file to discover all available pages before exploring further.
as_of_utc timestamp.
Authentication
Authorization: Bearer <your-api-key> — see Authentication.
Path parameters
Lowercase jurisdiction slug. Examples:
nyc, la, houston, dc. See
Concepts → Jurisdictions for the coverage list.
Returns 404 for unknown slugs and for slugs in DEPLOY-PENDING status.Response
Echoes the path parameter for round-trip clarity.
Human-readable metro name, e.g.
"New York City, NY".ISO 8601 UTC timestamp marking when the underlying data was last computed.
Count of buyer-targeted permits for this jurisdiction — lifetime
accumulation, no time window. See the
What is
total_target_permits?
section below for the matview filter definition. The sum of
cohort_distribution values is less than total_target_permits
by the implicit NULL-segment count (permits that pass the matview
filter but didn’t match any of the 18 cohort CASE branches).Map of
{ cohort_slug: integer_count } for all 18 canonical cohorts.
Always 18 keys; zero-filled when absent.Example request
Example response
cohort_distribution values sum to 2,998,250 — the remaining
220,606 permits (matview total 3,218,856 − cohort sum) fall in the
implicit NULL segment: rows that pass the matview filter but didn’t
match any of the 18 cohort CASE branches in the classifier. See the
explainer below for the data-model flow.
What is total_target_permits?
total_target_permits is the count of buyer-targeted commercial,
multifamily, and qualifying-ADU permits for this jurisdiction. It is a
lifetime accumulation — no time window applied — of permits matching
PermitCore’s matview criteria:
work_classin (commercial,mixed,industrial), OR- ≥ 5 residential units, OR
- explicit multifamily flag, OR
- qualifying ADU
| Total | NYC value | Source |
|---|---|---|
| Full silver permit table | ~7.63M | All permits indexed; all work types |
total_target_permits (matview slice) | ~3.22M | Buyer-targeted subset |
sum(cohort_distribution) + null_segment = total_target_permits. The NULL bucket isn’t exposed in the response
shape; subtract the cohort sum from the total if you need to know its
size for a given jurisdiction.
When not to use total_target_permits:
- For a metro’s full permit count across all work types (residential single-family, miscellaneous trades, etc.), the matview total understates by ~57% on NYC and roughly similar on other large metros. Use the metro page on permitcore.io for the lifetime silver count.
- For time-bounded counts (e.g., “permits issued in 2025”), this
endpoint is the wrong tool — use
/v1/permitswithsince=/until=filters when it ships (Bucket 2).
Errors
| HTTP | Code | Cause |
|---|---|---|
401 | missing_credentials | No Authorization header |
401 | invalid_token | Key malformed / revoked |
403 | insufficient_scope | Key lacks query scope |
404 | http_404 | Unknown slug OR DEPLOY-PENDING metro |
429 | rate_limited | Monthly quota exhausted (see Rate limits) |
500 | internal_error | Upstream pipeline issue; retry after backoff |
Caching
The upstream API responds withCache-Control: max-age=3600. The
PermitCore site consumes this endpoint via Next.js ISR with
revalidate: 3600, so site-rendered pages lag the underlying pipeline
by at most 1 hour beyond the pipeline’s own daily refresh.
For your own caching, the 1-hour value is a safe upper bound — pipeline
data only changes once per 24 hours per metro.
Related
- Concepts → Cohorts — what each cohort covers
- Permits endpoint — paginate the rows behind each count (~3 weeks)