GET /v1/jurisdictions/{slug}/cohorts/distribution
API Reference
GET /v1/jurisdictions/{slug}/cohorts/distribution
Per-jurisdiction breakdown across the canonical 22 cohorts. Cached 1 hour.
GET
GET /v1/jurisdictions/{slug}/cohorts/distribution
Returns the canonical 22-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 the
The 18 What is
The relationship in math:
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 22 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
The data-model flow:
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/permitswith theissued_after=filter. (Upper-bounduntil=filter is on the roadmap, not yet available.)
Errors
Errors are RFC 9457application/problem+json — branch on the type slug (see Errors):
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 nightly 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.