# API

> The read-only REST API: authentication, endpoints, rate limits, attribution and errors.

The Cadastre API is a read-only JSON API over the datasets, measures and metro figures described in these docs, with the same screening and ranking the app uses. It is in beta and free while it is. The public datasets need no account: call it with no credential at all, at 30 requests a minute. Sign in (an API key or an approved app) for higher limits.

## Base URL

```text
https://getcadastre.com/api/v1
```

The API is version 1. The OpenAPI 3.1 description is public at [https://getcadastre.com/api/v1/openapi.json](https://getcadastre.com/api/v1/openapi.json).

## Without an account

Every endpoint except `/me` answers a request with no credential:

```sh
curl "https://getcadastre.com/api/v1/metros?q=austin"
```

It serves the same datasets as a signed-in request (the public datasets whose terms allow the API), with the same attribution, at 30 requests a minute and 2,000 a day for each network address. Use is governed by the [Terms of Service](https://getcadastre.com/terms#api) either way; by using the API you agree to them.

## Authentication

For higher limits, send one credential in the `Authorization` header, as a bearer token:

```sh
curl -H "Authorization: Bearer $CADASTRE_API_KEY" https://getcadastre.com/api/v1/me
```

A credential that is not valid (unknown, revoked or expired) is always answered `401`, never served as if there were none, so a client learns its key is bad.

- **API keys**, for your own scripts and servers. Make one in [Settings](https://getcadastre.com/account) under **API keys** (you are asked for your password). A key starts `cad_key_` and is shown once: copy it then. It acts as you, in the account you were working in when you made it. Revoke it in the same place.
- **OAuth 2.1**, for apps and AI agents that sign in on your behalf: an MCP client, or the [Cadastre CLI](https://getcadastre.com/docs/cli). The app opens your browser, you sign in to Cadastre, choose the account and approve; the app receives an access token (`cad_at_`, one hour) and refreshes it itself. Clients register themselves (dynamic client registration) and use the authorization code flow with PKCE (S256). Discovery starts at the authorization server metadata, [https://getcadastre.com/.well-known/oauth-authorization-server](https://getcadastre.com/.well-known/oauth-authorization-server), and the API's protected resource metadata, [https://getcadastre.com/.well-known/oauth-protected-resource/api/v1](https://getcadastre.com/.well-known/oauth-protected-resource/api/v1). See [Connect an AI agent](https://getcadastre.com/agents) for client setup.

Either credential has the one scope there is, `read`. Read Cadastre's datasets, measures and metro figures, and run screens and rankings over them. It cannot see or change your projects, account or settings. It works only while you are a member of that account and have accepted the current Terms of Service.

**API access** is turned on per account, and is what gives a credential its higher limits. A new account does not have it: write to support@getcadastre.com to ask for it (it may become part of a paid plan later). Until it is on, Settings makes no keys for the account and the consent page will not connect an app to it; a key or app the account already has gets what a request without a credential gets (the same data, the per-address limits). `GET /me` says which: its `access.tier` is `api` (the account's limits) or `public` (the per-address limits).

Disconnect an app in Settings under **Authorized apps**.

Never put a key in a query string, a web page or a shared file; anything holding it can act as you.

## Endpoints

All paths are under the base URL. Lists are paged with `page` (from 1) and `per_page` (up to 500, default 100); the answer's `meta` carries `total` and `total_pages`.

### Account

#### `GET /me`

Who the credential acts as. Needs a credential; without one it is 401 with the sign-in challenge.

#### `GET /openapi.json`

This description. Public: no credential needed.

### Datasets

#### `GET /datasets`

The datasets the API serves.

#### `GET /datasets/{key}`

One dataset, with its measures.

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `key` (required) | path | string | A dataset key, as GET /datasets lists them |

### Measures

#### `GET /measures`

Search the measures.

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `q` | query | string | Text in the name, label or unit |
| `geography` | query | `msa`, `county`, `state` | Only measures with values at this geography |
| `dataset` | query | string | Only this dataset's measures (its key) |
| `page` | query | integer | Page number, from 1 |
| `per_page` | query | integer | Items per page, up to 500 (default 100) |

#### `GET /measures/{name}`

One measure.

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `name` (required) | path | string | A measure name, as GET /measures lists them |

#### `GET /measures/{name}/values`

A measure's value for every region of a geography.

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `name` (required) | path | string | A measure name, as GET /measures lists them |
| `geography` | query | `msa`, `county`, `state` | The geography to list; needed (a request without it is a 400) |
| `state` | query | string | USPS code: metros partly in it, its counties, or the state |
| `page` | query | integer | Page number, from 1 |
| `per_page` | query | integer | Items per page, up to 500 (default 100) |

### Metros

#### `GET /metros`

The metropolitan statistical areas.

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `state` | query | string | USPS code |
| `q` | query | string | Text in the name |
| `page` | query | integer | Page number, from 1 |
| `per_page` | query | integer | Items per page, up to 500 (default 100) |

#### `GET /metros/compare`

Metros side by side.

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `ids` (required) | query | string | Up to 25 metro ids, CBSA codes or slugs, comma-separated |
| `measures` | query | string | Up to 50 measure names, comma-separated (default: the register's standard figures) |

#### `GET /metros/{ref}`

A metro's profile. Every measure the API serves that the metro has a value for. `ref` is the metro's id, its five-digit CBSA code or its slug (percent-encode the slug's slashes, `us%2Ftx%2Faustin-12420`).

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `ref` (required) | path | string | The metro's id, CBSA code or slug |

### Analysis

#### `POST /screen`

Screen the metros by scope and Requirements (read-only).

The body is JSON, a `ScreenRequest` (fields below, under Screening and ranking).

#### `POST /rank`

Rank the metros on weighted criteria (read-only).

The body is JSON, a `RankRequest` (fields below, under Screening and ranking).

### Incentives

#### `GET /incentive_programs`

Incentive programs federal, state and local governments offer (paid). Programs read from the jurisdictions' official pages and reviewed by Cadastre, each value with the
sentence it was quoted from (`evidence`) and the page (`source_url`); a field the page does not
state is null (unknown). A program is what a jurisdiction offers: not an award, an amount anyone
received, or a finding that a site qualifies. A jurisdiction with no programs listed is not covered
yet. Only records from pages whose terms allow reuse are served. Part of API access: an account
without it is answered 403 `api_access_required`.

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `state` | query | string | USPS code or state FIPS: federal programs, the state's and its local governments' |
| `metro` | query | string | CBSA code, region id or slug: federal programs, the metro's states' and its local governments' |
| `mechanism` | query | `tax_credit`, `grant`, `loan`, `loan_guarantee`, `abatement`, `exemption`, `training_grant`, `infrastructure`, `financing_bond`, `zone_designation`, `other` | Only programs of this kind |
| `page` | query | integer | Page number, from 1 |
| `per_page` | query | integer | Items per page, up to 200 (default 50) |

## Examples

```sh
# The datasets the API serves
curl -H "Authorization: Bearer $CADASTRE_API_KEY" https://getcadastre.com/api/v1/datasets

# Search the measures
curl -H "Authorization: Bearer $CADASTRE_API_KEY" "https://getcadastre.com/api/v1/measures?q=unemployment&geography=msa"

# A metro's profile, by its CBSA code (Huntsville, AL)
curl -H "Authorization: Bearer $CADASTRE_API_KEY" https://getcadastre.com/api/v1/metros/26620

# Three metros side by side on the standard figures
curl -H "Authorization: Bearer $CADASTRE_API_KEY" "https://getcadastre.com/api/v1/metros/compare?ids=26620,48620,46140"
```

Screen and rank are POSTs with a JSON body. They compute an answer and save nothing:

```sh
curl -X POST https://getcadastre.com/api/v1/screen \
  -H "Authorization: Bearer $CADASTRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"requirements": {"tier": "500k", "min_mfg_share": 8, "max_unemployment": 5.5}}'

curl -X POST https://getcadastre.com/api/v1/rank \
  -H "Authorization: Bearer $CADASTRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"requirements": {"tier": "500k"}, "criteria": [{"measure": "unemployment_rate", "direction": "lower_better", "weight": 2}]}'
```

Measure names (`unemployment_rate` above) come from `GET /measures`.

## Screening and ranking

`POST /screen` applies a scope and the app's Requirements to the metros and answers with the funnel (how many metros each Requirement leaves) and the metros that pass. `POST /rank` adds weighted criteria and answers with ranks, composite scores and each criterion's value, score and contribution. The same inputs give the same answer as a project in the app.

### Requirements

| Field | Type | Meaning |
| --- | --- | --- |
| `tier` | `1m`, `500k`, `250k` | Minimum population |
| `min_mfg_share` | number | Manufacturing share of GDP, percent, at least |
| `max_unemployment` | number | Unemployment rate, percent, at most |
| `min_growth` | number | Five-year population growth, percent, at least |
| `max_hazard` | number | FEMA National Risk Index, at most |
| `max_elec_price` | number | Industrial electricity price, cents per kWh, at most |
| `rtw_only` | boolean | Only metros entirely in right-to-work states |

### Criteria

| Field | Type | Meaning |
| --- | --- | --- |
| `measure` | string | A measure name from GET /measures |
| `direction` | `higher_better`, `lower_better` | Which way scores better |
| `weight` | number | Default 1; 0 keeps the criterion as a gate only |
| `cutoff` | object | Eliminates the metros it holds for. |

`scope.states` limits either to the metros partly in the listed states (USPS codes). A Requirement or criterion that reads a measure the API does not serve is a 422.

## Responses and attribution

A successful answer is JSON: `{ "data": ..., "meta": ..., "attribution": [...] }`. `attribution` lists, once each, the credit every dataset behind `data` requires: the publisher's credit, the licence and its link, and any notices, disclaimers and conditions the publisher attaches.

- **Show the attribution with the figures**, wherever they go: an analysis, a report, a deck, an app, an AI agent's answer. The [Rights and attribution](https://getcadastre.com/docs/rights) page explains each source's requirements, and the [Terms of Service](https://getcadastre.com/terms#api) make it a condition of using the API.
- **Cite `retrieved_at`.** Each attribution entry carries `retrieved_at`, when Cadastre last retrieved that data from its publisher. Give it with the data (the Bureau of Labor Statistics asks API users to cite the retrieval date).
- **Label Cadastre's computed values.** A value marked `computed_by_cadastre` is Cadastre's figure (for example a metro total built from county figures), and `calculation` says how. Credit it as "Source: Cadastre, computed from" the publisher's data, not as the publisher's own figure.

Only datasets whose terms Cadastre has approved for the API are served; any other is absent from every list and a 404. Sources restricted in the app (see [Rights and attribution](https://getcadastre.com/docs/rights)) are never served.

## Not served

- **No geometry.** Answers carry names and codes (region ids, CBSA codes, FIPS), never boundaries, points or lines. Geometry is not available yet; if it is added it will be an explicit option, never a default.
- **No parcel data.** The launch is metro-level; parcels are not in the API.
- **No listings.** Cadastre does not carry property listings.

## Rate limits

- Without a credential, **30 requests a minute** and **2,000 a day** for each network address (an IPv6 /64), the API and the MCP server together. A key or app of an account without API access counts here too.
- With a credential of an account with API access, **60 requests a minute** and **10,000 a day** for each API key, and for each app you approved (across its token refreshes).
- **50,000 requests a day** for each account, over all its keys and apps, so more keys do not add up to more requests.

Every answer carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds). Over a limit the answer is 429 with `Retry-After`: wait that many seconds; without an account, the message says how to sign in for more. These are the beta limits and may change.

## Caching

The data GETs (datasets, measures, metros) carry an `ETag` and `Last-Modified`. Send `If-None-Match` with the ETag you have and an unchanged answer is a 304 with no body, which still counts as a request. The data changes when Cadastre imports a new release, not by the minute. An answer to a request without a credential is the same for everyone, so it is `Cache-Control: public, max-age=60` (with `Vary: Authorization`): a cache may keep it for a minute. An answer to a credential is private and revalidated every time.

## Errors

An error is JSON: `{ "error": "<code>", "message": "<sentence>" }`.

| Status | `error` | What to do |
| --- | --- | --- |
| 400 | `bad_request` | A malformed parameter or body. The message names it. |
| 401 | `unauthorized`, `invalid_token` | A credential that is unknown, revoked or expired, or whose person left the account, or not sent as a bearer token; or none at all on `/me`. Fix or drop the credential (with none, the public datasets are served). The `WWW-Authenticate` header names the protected resource metadata, so an OAuth client can sign in again. |
| 403 | `terms_required` | The Terms of Service were updated. Sign in to Cadastre in a browser and accept them; the key or app works again at once. |
| 403 | `insufficient_scope` | The credential lacks the `read` scope. Sign in again or make a new key. |
| 404 | `not_found` | No such dataset, measure or metro, or one the API does not serve. |
| 415 | `unsupported_media_type` | Send the POST body as `application/json`. |
| 422 | `invalid_request`, `unknown_measure` | Well formed but cannot be answered: an unknown measure, or a Requirement whose source the API does not serve. |
| 429 | `rate_limited` | Over a rate limit. Wait for `Retry-After` seconds, or sign in for higher limits. |
| 503 | `unavailable` | The API is not available on this server. Try again later. |

## Versioning and beta

The version is in the path (`/api/v1`). During the beta Cadastre may add, change or remove endpoints, fields, datasets and limits, and will try to give notice of a change that breaks existing use; adding a field or a dataset is not a breaking change, so read answers tolerantly. There is no service level during the beta. See the [Terms of Service](https://getcadastre.com/terms#api).

## AI agents and the MCP server

An AI agent can use the API directly, with or without a key, or connect to Cadastre's remote MCP server, which offers the same data as tools, with no sign-in or signed in with OAuth in the browser for higher limits. See [MCP server](https://getcadastre.com/docs/mcp) and the [agent guides](https://getcadastre.com/agents).
