> ## 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.

# GET /v1/jurisdictions/{slug}/cohorts/distribution

> Per-jurisdiction breakdown across the canonical 22 cohorts. Cached 1 hour.

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

## Authentication

`Authorization: Bearer <your-api-key>` — see [Authentication](/authentication).

## Path parameters

<ParamField path="slug" type="string" required>
  Lowercase jurisdiction slug. Examples: `nyc`, `la`, `houston`, `dc`. See
  [Concepts → Jurisdictions](/concepts/jurisdictions) for the coverage list.
  Returns `404` for unknown slugs and for slugs in DEPLOY-PENDING status.
</ParamField>

## Response

<ResponseField name="jurisdiction_slug" type="string">
  Echoes the path parameter for round-trip clarity.
</ResponseField>

<ResponseField name="jurisdiction_display_name" type="string">
  Human-readable metro name, e.g. `"New York City, NY"`.
</ResponseField>

<ResponseField name="as_of_utc" type="string">
  ISO 8601 UTC timestamp marking when the underlying data was last computed.
</ResponseField>

<ResponseField name="total_target_permits" type="number">
  Count of buyer-targeted permits for this jurisdiction — lifetime
  accumulation, no time window. See the
  [What is `total_target_permits`?](#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).
</ResponseField>

<ResponseField name="cohort_distribution" type="object">
  Map of `{ cohort_slug: integer_count }` for all 22 canonical cohorts.
  Always 18 keys; zero-filled when absent.
</ResponseField>

## Example request

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.permitcore.io/v1/jurisdictions/nyc/cohorts/distribution \
    -H "Authorization: Bearer $PERMITCORE_API_KEY" \
    -H "Accept: application/json"
  ```

  ```js Node.js theme={null}
  const res = await fetch(
    "https://api.permitcore.io/v1/jurisdictions/nyc/cohorts/distribution",
    {
      headers: {
        Authorization: `Bearer ${process.env.PERMITCORE_API_KEY}`,
        Accept: "application/json",
      },
    },
  );
  const data = await res.json();
  ```

  ```python Python theme={null}
  import os, httpx

  r = httpx.get(
      "https://api.permitcore.io/v1/jurisdictions/nyc/cohorts/distribution",
      headers={"Authorization": f"Bearer {os.environ['PERMITCORE_API_KEY']}"},
  )
  r.raise_for_status()
  data = r.json()
  ```
</CodeGroup>

## Example response

```json theme={null}
{
  "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:

| 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               |

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
22 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`](/api-reference/permits) with the `issued_after=`
  filter. (Upper-bound `until=` filter is on the roadmap, not yet
  available.)

## Errors

Errors are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)
`application/problem+json` — branch on the `type` slug (see [Errors](/errors)):

| HTTP  | `type` slug                       | Cause                                                              |
| ----- | --------------------------------- | ------------------------------------------------------------------ |
| `401` | `missing-credentials`             | No `Authorization` header                                          |
| `401` | `invalid-credentials`             | Key malformed / revoked                                            |
| `403` | `insufficient-scope`              | Key lacks `query` scope                                            |
| `404` | `not-found`                       | Unknown slug OR DEPLOY-PENDING metro                               |
| `429` | `monthly-requests-quota-exceeded` | Monthly quota exhausted (see [Rate limits](/concepts/rate-limits)) |
| `500` | `internal-error`                  | Upstream 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 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](/concepts/cohorts) — what each cohort covers
* [Permits endpoint](/api-reference/permits) — paginate the rows behind each count.
