Parcel records from the county that keeps them
UrbanKit Studio reads each county's own published GIS layer and serves it through one contract. Use the REST API, the npm SDK, the MCP server or the uks CLI. 296 of the 303 indexed counties have an endpoint you can query.
- Address submitted
- 1200 Market St, Philadelphia, PA
- County
- Philadelphia, PA
- County FIPS
- 42101
- Latitude
- 39.951865266782
- Longitude
- -75.159970396307
- Result
- matched trueatlas_indexed true
Quickstart
You can make 10 lookups a day without a key, so the first call needs no sign-up.
- Send a full address
Send street, city and state. A partial address can geocode to nothing and return
matched: false. - Check
matchedandatlas_indexedBoth true means the county answered. A miss still returns 200, so branch on the fields and not on the status code.
- Add a key when you need more
A free
uk_live_key raises the limit to 500 calls a day. Send it as a bearer token.
curl -G "https://urbankitstudio.com/api/atlas/lookup" \ --data-urlencode "address=1200 Market St, Philadelphia, PA"Authentication
Lookup works without a key at a per-IP limit of 10 calls a day. A key swaps that limit for your plan's quota on lookup. The free plan allows 500 keyed calls per UTC day.
Send your uk_live_ key as Authorization: Bearer. The key authenticates the API directly, and a session JWT will not. Create a key on your account page.
The CLI reads UKS_API_KEY from the environment first and falls back to a key saved with uks login.
export UKS_API_KEY="uk_live_..."curl -G "https://urbankitstudio.com/api/atlas/lookup" \ --data-urlencode "address=1200 Market St, Philadelphia, PA" \ -H "Authorization: Bearer $UKS_API_KEY"SDK, MCP, CLI and REST
The SDK, the CLI's county search and the npm MCP package work offline against bundled data and need no account. Owner data comes from the REST API and from 2 paid-plan tools on the hosted MCP server.
npm SDK
@urbankitstudio/atlas is TypeScript with zero dependencies. It carries the whole atlas, so county search runs in your bundle. Unknown input returns undefined and does not throw.
MCP server
@urbankitstudio/mcp-atlas runs over stdio with 4 offline discovery tools. The hosted server at /api/mcp has 7 tools that need no key, and adds enrich_address and radius_owners on a key. See the tool list.
uks CLI
uks needs Node.js 22 or newer. Every command prints one JSON document with --json and exits with a code a script can branch on. See the commands and exit codes.
import { findCountyByFips, listStates } from "@urbankitstudio/atlas"; const kane = findCountyByFips("17089"); // Kane County, ILconsole.log(kane?.endpoints[0].url); // the county's ArcGIS REST URL const populated = listStates().filter((s) => s.populated);REST API reference
9 endpoints on https://urbankitstudio.com, from public/openapi.json 1.4.0. Lookup is documented in full below, and the OpenAPI spec carries every schema.
| Endpoint | What it does |
|---|---|
Atlas /api/atlas/ | |
| GET /api/atlas/lookup | Resolve one address to its parcel record |
| GET /api/atlas/county | What a county publishes, and whether it is indexed |
| POST /api/atlas/radius | Every parcel within a radius of an address |
| POST /api/atlas/bulk | Enrich a list of addresses in one call |
| GET /api/atlas/status | Live health of the indexed county endpoints |
| POST /api/atlas/request | Request a county the atlas does not cover |
Account /api/v1/ | |
| GET /api/v1/plans | Every plan, its price, quota and entitlements |
| GET /api/v1/me | What this key is, can do, and has left |
| GET /api/v1/usage | This account's usage history, aggregated |
Resolve one address to its parcel record
Geocodes the address, routes it to the county that owns it, and queries that county's parcel layer.
- Without a key
- 10 a day per IP
- Free key
- 500 a day
- Keyed call
- 1 quota unit
Usable without a key at a lower IP-based rate limit; a free key raises it to 500/day. On a plan or free key, every keyed call counts one unit against the key's quota (a per-call rate limit), a call answered from a stale cached geocode included. Pay-as-you-go bills one unit only for a fresh answer from a county with a public parcel endpoint; a no-match, an unindexed county, a county with no endpoint, an outage, or a stale geocode is not billed.
Query parameters
addressstring, min length 3RequiredFull street address including city and state. A partial address will usually fail to geocode.
opstringOptionalIdempotency token for a retry. Reusing it on an identical request reuses the reservation instead of billing again. It must match
^uklk[|][0-9a-fA-F-]{20,80}$.
Request headers
AuthorizationBearer uk_live_...OptionalA key swaps the anonymous per-IP limit of 10 calls a day for your plan's quota.
Response
okbooleanAlwaysAlways true on a 200.
queryobjectThe request, echoed back.
matchedbooleanAlwaysFalse when the address could not be geocoded.
atlas_indexedbooleanWhether the resolved county is in the atlas. False means we geocoded it but cannot query it.
geocodeobjectWhere the address resolved.
lat, numberlon, numbercountyName, stringstateAbbrev, stringcountyFips, string
geocode_stalebooleanTrue when the U.S. Census geocoder was unavailable and the last cached geocode was served instead. Pay-as-you-go is not billed for such an answer. A plan or free key's call still counts its one unit, because that unit is a per-call rate limit rather than a charge for the answer.
fallbackstringPresent when the county is not indexed: where to go instead.
requestobjectPresent on a not-indexed answer.
bodyis ready to POST tourlas-is; keys the server did not know are omitted. Requesting is free.how, stringurl, stringbody, objectmessage, string
messagestringHuman-readable detail, such as why a geocode failed. Do not match on it.
countyobjectA county as served by the public API (snake_case; the registry's internal camelCase is not the wire shape). See observed health.
Observed health
The county record's observed_health is what the liveness probe last saw. Each state has a distinct shape, so it reads without color. A null value means no observation exists, so treat it as unknown.
The endpoint's response passed the probe's check.
It answered with a 2xx, but the response failed the check.
An error, a timeout or a non-2xx response.
No observation yet. It is not a degree of health.
Endpoints and parcel rows also carry endpoint_status, one of live, stale, unreachable or unverified. That is the registry's curated record, set by a person. It is not a probe result.
Errors
Every non-2xx response has one shape, with ok false, a stable error code and an optional message. Branch on error and never on message.
| Status | When |
|---|---|
| 400 | The request was malformed: a missing parameter, or a body that is not the documented shape. |
| 401 | An Authorization header was sent but its key is not accepted: malformed, unknown or revoked (`invalid-key`). A request with no Authorization header is served anonymously instead, and is never a 401. |
| 402 | usage_not_recorded: a pay-as-you-go call whose unit could not be recorded, so the result was not returned. Every such denial is this one identical answer, whatever the cause. Retry after Retry-After. |
| 405 | Wrong HTTP method for this endpoint. |
| 429 | Rate limit or quota exhausted. |
| 500 | The key could not be checked: server-misconfigured, hash-failed, hash-format, key-lookup-failed or key-lookup-threw. The body is the error code alone, with no message. Only a request that sent a key can get this; the call is not answered and not billed. Retry later. |
| 502 | geocode-upstream-error: the U.S. Census geocoder returned an error and no cached resolution could stand in for it. A pay-as-you-go call is not billed; a plan or free key's call still counts as one unit of its quota. Retry shortly. |
| 503 | atlas-unavailable: the address geocoded but that state's atlas data could not be loaded. payg_unavailable: a pay-as-you-go key while pay-as-you-go is switched off; nothing the caller does fixes it. metering_unavailable: the usage meter could not be reached for a pay-as-you-go call, so no lookup is started; retry after Retry-After, which only this code sends. A pay-as-you-go call is never billed on any of these; atlas-unavailable still counts as one unit of a plan or free key's quota. |
| 504 | geocode-unavailable: the U.S. Census geocoder timed out or could not be reached and no cached resolution could stand in for it. A pay-as-you-go call is not billed; a plan or free key's call still counts as one unit of its quota. Retry shortly. |
Rate limits
A 200 and a 429 carry the rate-limit headers below. A 429 adds retry-after.
| Header | Meaning | Sent on |
|---|---|---|
| x-ratelimit-limit | Calls allowed in the current window. | 200 and 429 |
| x-ratelimit-remaining | Calls left in the current window. | 200 and 429 |
| x-ratelimit-reset | Unix seconds at which the window resets. | 200 and 429 |
| retry-after | Seconds to wait before retrying. | 429 only |
curl -G "https://urbankitstudio.com/api/atlas/lookup" \ --data-urlencode "address=1200 Market St, Philadelphia, PA" \ --data-urlencode "op=uklk|$RETRY_ID" \ -H "Authorization: Bearer $UKS_API_KEY"{ "ok": true, "query": { "address": "1200 Market St, Philadelphia, PA" }, "matched": true, "atlas_indexed": true, "geocode": { "lat": 39.951865266782, "lon": -75.159970396307, "countyName": "Philadelphia", "stateAbbrev": "PA", "countyFips": "42101" }}For agents and scripts
Run the stdio server with npx -y @urbankitstudio/mcp-atlas, or point a client at the hosted endpoint. The hosted server takes an optional bearer key, which the 2 paid-plan tools need.
With the CLI, pass --json on every call and set UKS_API_KEY in the environment rather than running uks login.
MCP tools
| Tool | Server | Key |
|---|---|---|
| list_counties | stdio | No |
| find_county | stdio | No |
| get_parcel_endpoint | stdio | No |
| build_owner_query | stdio | No |
| find_parcel_by_address | Hosted | No |
| geocode_address | Hosted | No |
| list_indexed_states | Hosted | No |
| list_counties_in_state | Hosted | No |
| find_county_by_fips | Hosted | No |
| get_county_endpoint | Hosted | No |
| request_county | Hosted | No |
| enrich_address | Hosted | Paid plan or pay-as-you-go |
| radius_owners | Hosted | Paid plan |
Tools marked No need no key. Every request to the hosted server, keyed or not, counts against a per-IP cap of 120 a minute.
uks commands
| Command | Network | Key | What it does |
|---|---|---|---|
| uks plans | Yes | No | Plans: price, quota, window, owner data |
| uks whoami | Yes | Yes | Your tier, key prefix and label, quota, subscription |
| uks usage [--from D] [--to D] | Yes | Yes | Calls and units in total, by endpoint, by day |
| uks states | No | No | States the atlas covers, with county and endpoint counts |
| uks find <query> [--limit N] | No | No | Counties by name, name plus state, or FIPS |
| uks county <state> <county> | No | No | One county's endpoint record (also uks county <FIPS>) |
| uks lookup "<address>" | Yes | Optional | Address to county and parcel endpoint |
| uks request-county ... | Yes | No | Ask for a county the atlas lacks (free) |
| uks login --key uk_live_... | No | No | Save a key |
| uks logout | No | No | Remove the saved key |
CLI exit codes
| Code | Meaning | Retry |
|---|---|---|
| 0 | Success, including "no match" and "not in the atlas" answers | Not needed |
| 1 | Refused: no key where one is needed, a key the server rejects (401/402/403), or a plan without the feature | No |
| 2 | Could not run: bad arguments, a network failure, a request the server rejects (other 4xx), a server error (5xx), an unreadable response | Once after a network failure or a 5xx; never for bad-arguments or another 4xx |
| 3 | Rate limit, quota or county-request budget exhausted (429) | After reset_at |
{ "mcpServers": { "urbankit-atlas": { "url": "https://urbankitstudio.com/api/mcp", "headers": { "Authorization": "Bearer uk_live_REPLACE_WITH_YOUR_KEY" } } }}Data coverage
Atlas 0.3.0, updated 2026-10-10. 303 counties across 50 states and DC, with 322 parcel endpoints. 7 indexed counties have no queryable endpoint yet and are not counted as covered. Missing a county? uks request-county asks for it, free and with no key.
Liveness probe. Every two hours the probe checks 320 of the 322 endpoints, across 295 counties. Any county it last saw as unhealthy is checked again every 30 minutes.
The Massachusetts statewide record has 2 endpoints and no county FIPS code, so the probe does not check it. Results reach the API as observed_health.
openapi.json 1.4.0. Prefer the overview? See Developers.Back to top