Skip to main content
GET
/
v1
/
jurisdictions
/
{slug}
/
cohorts
/
distribution
GET /v1/jurisdictions/{slug}/cohorts/distribution
curl --request GET \
  --url https://api.permitcore.io/v1/jurisdictions/{slug}/cohorts/distribution
{
  "jurisdiction_slug": "<string>",
  "jurisdiction_display_name": "<string>",
  "as_of_utc": "<string>",
  "total_target_permits": 123,
  "cohort_distribution": {}
}

Documentation Index

Fetch the complete documentation index at: https://docs.permitcore.io/llms.txt

Use this file to discover all available pages before exploring further.

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 the as_of_utc timestamp.

Authentication

Authorization: Bearer <your-api-key> — see Authentication.

Path parameters

slug
string
required
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

jurisdiction_slug
string
Echoes the path parameter for round-trip clarity.
jurisdiction_display_name
string
Human-readable metro name, e.g. "New York City, NY".
as_of_utc
string
ISO 8601 UTC timestamp marking when the underlying data was last computed.
total_target_permits
number
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).
cohort_distribution
object
Map of { cohort_slug: integer_count } for all 18 canonical cohorts. Always 18 keys; zero-filled when absent.

Example request

curl https://api.permitcore.io/v1/jurisdictions/nyc/cohorts/distribution \
  -H "Authorization: Bearer $PERMITCORE_API_KEY" \
  -H "Accept: application/json"

Example response

{
  "jurisdiction_slug": "nyc",
  "jurisdiction_display_name": "New York City, NY",
  "as_of_utc": "2026-05-24T02:00:00Z",
  "total_target_permits": 3218856,
  "cohort_distribution": {
    "adu_qualifying": 380,
    "civic": 1820,
    "commercial_alteration": 119800,
    "commercial_demolition": 50100,
    "commercial_mep": 27200,
    "commercial_new": 38400,
    "commercial_pool": 3920,
    "commercial_shell_only": 1200,
    "commercial_signage": 18900,
    "industrial_new": 2840,
    "multifamily_alteration": 2474522,
    "multifamily_new": 73200,
    "residential_alteration": 14820,
    "residential_demolition": 9840,
    "residential_foundation_only": 248,
    "residential_mep": 8400,
    "residential_new_sf": 6240,
    "temporary_construction_support": 146420
  }
}
The 18 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_class in (commercial, mixed, industrial), OR
  • ≥ 5 residential units, OR
  • explicit multifamily flag, OR
  • qualifying ADU
Two different totals coexist for each metro:
TotalNYC valueSource
Full silver permit table~7.63MAll permits indexed; all work types
total_target_permits (matview slice)~3.22MBuyer-targeted subset
The data-model flow:
Silver permits table (NYC: ~7.63M; all types, all time)

      ▼  matview WHERE filter
Buyer-targeted slice (NYC: ~3.22M; = total_target_permits)

      ▼  cohort CASE classification
18 canonical cohorts  +  1 NULL bucket (implicit)

      ▼  aggregation per request
cohort_distribution response field
The relationship in math: 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/permits with since= / until= filters when it ships (Bucket 2).

Errors

HTTPCodeCause
401missing_credentialsNo Authorization header
401invalid_tokenKey malformed / revoked
403insufficient_scopeKey lacks query scope
404http_404Unknown slug OR DEPLOY-PENDING metro
429rate_limitedMonthly quota exhausted (see Rate limits)
500internal_errorUpstream pipeline issue; retry after backoff

Caching

The upstream API responds with Cache-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.