The same county parcel data behind our tools, in four forms you can call from code: a TypeScript npm package for your own programs, an MCP server for Claude, Cursor, and other AI clients, a hosted REST API that turns a US address into the owner of record, and the uks command line for scripts and agents that work in a terminal. The SDK is MIT-licensed and needs no account. The MCP server's discovery tools need no key either. The hosted lookup API runs on a free key; theenrich_address action and bulk enrichment are on paid plans (from $7/mo) or pay-as-you-go.
@urbankitstudio/atlas v0.7.2. TypeScript, ESM + CJS, zero runtime deps.
JSON-RPC 2.0, MCP spec 2025-06-18. Five tools via npm, eight on the hosted endpoint, no key.
@urbankitstudio/cli v0.1.8. One JSON document per command and exit codes a script can branch on.
481 counties across 50 states and DC, each with verified REST endpoints and searchable fields.
A typed wrapper around the atlas, bundled offline so your code makes no runtime network call to resolve a county. Look up an endpoint by slug or FIPS, read its searchable fields, and build deep-links into the parcel-lookup tool. Works in Node 18+ and every modern bundler.
npm i @urbankitstudio/atlas
Every function below ships in v0.7.2. findCountyByFips and findCounty return undefined on a miss rather than throwing, so you can branch on the result.
import { findCountyByFips, listStates, atlas } from "@urbankitstudio/atlas";
// 1. Resolve a 5-digit county FIPS to its parcel endpoint.
const kane = findCountyByFips("17089"); // Kane County, IL
if (kane) {
const endpoint = kane.endpoints[0];
console.log(endpoint.url); // the live ArcGIS REST URL
console.log(endpoint.searchFields); // [{ name: "PIN", searchable: true }, ...]
}
// 2. Discovery: which states are indexed, and how many counties each.
const populated = listStates().filter((s) => s.populated);
console.log(populated.length, "states");
// 3. Coverage totals are on the bundled atlas root.
console.log(atlas.totals); // { states, counties, endpoints }
// Unknown / malformed input returns undefined. It never throws.listStates, findState, findCounty, findCountyByFips, listCountiesByState, buildParcelLookupDeepLink.
Dual ESM/CJS builds, full .d.ts types, sideEffects: false, zero runtime dependencies.
The parcel atlas is available as a published npm MCP server you can run in any MCP-capable client with one command:
It exposes five tools: list_counties, find_county, get_parcel_endpoint, build_owner_query, and get_city_datasets. The server is public and read-only. Your client pays its own LLM-token cost; there is no key to manage.
@urbankitstudio/mcp-atlas
View on npm (opens in a new tab)GITHUBurbankitstudio/mcp-atlas
Source (opens in a new tab)GLAMAIndependently scored in the MCP directory.
Open claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/; Windows: %APPDATA%\Claude\) and merge in this block, then restart Claude Desktop.
{
"mcpServers": {
"urbankit-atlas": {
"command": "npx",
"args": ["-y", "@urbankitstudio/mcp-atlas"]
}
}
}Clients that connect by URL instead of spawning a subprocess can point at the remote HTTP endpoint: https://urbankitstudio.com/api/mcp. It exposes eight public tools, six for discovery, get_city_datasets for a city's code-violation, building-permit and zoning-case datasets, and request_county to ask for a county the atlas does not cover yet, plus two key-gated actions: enrich_address returns owner, APN, and mailing fields for one address, and radius_ownersreturns every owner parcel inside a radius. enrich_address runs on paid plans or pay-as-you-go and, under the same rule on both, uses one unit when the answer carries an owner name or mailing address. Pay-as-you-go never meters any other answer, such as a no-match, an uncovered county, or an answer with only a parcel number or site address. A plan key draws its unit before the lookup and credits it back for those answers, best-effort, and the answer's plan_quota says whether the credit landed. radius_owners is paid-plans-only. It reserves up to its caller-set cap before the query, keeps one unit per returned parcel row that carries an owner name or mailing address, and credits the rest back, best-effort; the answer's charged says what was kept. A count_only preview bills at most one unit. Some county layers refuse pagination and return at most their per-request cap (2,000 on the Mississippi statewide layers); a radius that matches more comes back truncated with a note, and no larger max_parcels can complete it. Use a smaller radius or upload a parcel GeoJSON.
The full MCP server page has the per-tool reference, an example conversation, a curl walkthrough, and setup notes for Cursor and other clients.
uks puts the atlas in a terminal, for shell scripts and for agents that run commands rather than speak MCP. It finds counties and their verified parcel endpoints, resolves an address to its county, shows your key's plan, quota and usage, and files a request for a county the atlas lacks. Every command prints one JSON document with --json and exits with a code a script can branch on. It needs Node.js 22 or newer.
npx @urbankitstudio/cli --help # run once without installing npm install -g @urbankitstudio/cli # or install the uks command uks --help
find, county and states read the atlas data bundled with the package. They never touch the network, so they need no key and work offline. A query is a county name, a name plus a state, or a 5-digit FIPS code.
$ uks find "Kane IL" FIPS COUNTY STATE ENDPOINTS MATCH 17089 Kane County IL 1 exact Details: uks county illinois kane-county
county returns each endpoint's service URL, its owner-name column, whether the layer takes attribute search or only spatial queries, and whether it pages: pagination is unsupported on a layer that refuses resultRecordCount, with its per-request cap in max_record_count, so a script sends one request and narrows it instead of asking for a second page. The endpoint status is the stamp from when the atlas package was published. live_status_url gives the current answer.
$ uks county illinois kane-county --json
{
"ok": true,
"found": true,
...
"county_name": "Kane County",
"county_fips": "17089",
"has_public_rest": true,
"endpoints": [
{
"url": "https://gistech.countyofkane.org/arcgis/rest/services/KanePINList/MapServer/0",
"service_type": "MapServer",
"status": "live",
"status_is_publish_time": true,
"owner_field": { "name": "TaxName", "label": "Taxpayer Name" },
"query_mode": "attribute_or_spatial",
"scope_where": null,
"pagination": "supported",
"max_record_count": null,
...
}
],
"live_status_url": "https://urbankitstudio.com/api/atlas/status?fips=17089",
...
}With --json, a failure prints one error document on stdout and one line on stderr. Branch on error and the exit code, never on message.
$ uks whoami --json
{
"ok": false,
"error": "missing-key",
"message": "This command needs an API key. Run `uks login --key uk_live_...` or set UKS_API_KEY. Create a key at https://urbankitstudio.com/account (free tier).",
"exit_code": 1
}0123reset_at, or for retry_after_seconds when that is all the error carries.lookup resolves an address to its county and parcel endpoint, with or without a key. plans, whoami and usage read the account API. request-county asks for a missing county for free. Agents should set UKS_API_KEY rather than run uks login.
The SDK and MCP server hand you the verified county endpoint to query. If you would rather send a US street address and get the owner back directly, the hosted /api/atlas/lookup endpoint is theproperty owner lookup API on top of the same 515-county atlas. One authenticated GET resolves the address to its county, the parcel endpoint, and the owner / APN / mailing fields that endpoint exposes. It is the address-to-owner API behind the parcel-lookup tool.
curl "https://urbankitstudio.com/api/atlas/lookup\ ?address=100 N Lake St, Aurora, IL" \ -H "Authorization: Bearer uk_live_..."
{
"ok": true,
"matched": true,
"geocode": {
"lat": 41.760608738516,
"lon": -88.317386439938,
"countyName": "Kane",
"stateAbbrev": "IL",
"countyFips": "17089"
},
"geocode_stale": false,
"atlas_indexed": true,
"county": {
"state_slug": "illinois",
"state_name": "Illinois",
"county_name": "Kane",
"county_slug": "kane-county",
"county_fips": "17089",
"has_public_rest": true,
"verification": { "last_verified": "2026-05-15", "status": "live" },
"observed_health": {
"status": "ok",
"consecutive_failures": 0,
"last_ok_at": "2026-10-10T12:00:41.000Z",
"checked_at": "2026-10-10T12:00:41.000Z"
},
"endpoints": [
{
"url": "https://gistech.countyofkane.org/arcgis/rest/services/KanePINList/MapServer/0",
"service_type": "MapServer",
"layer_name": "Kane.DBO.Parcels_v2025",
"searchable_fields": [
{ "name": "PIN", "label": "Parcel Identification Number (PIN)" },
{ "name": "TaxName", "label": "Taxpayer Name" },
{ "name": "SiteAddress", "label": "Site Address" },
{ "name": "SiteCity", "label": "Site City" },
{ "name": "MailingAddress", "label": "Mailing Address" }
],
"sample_query": "https://gistech.countyofkane.org/arcgis/rest/services/KanePINList/MapServer/0/query?where=UPPER(TaxName)%20LIKE%20UPPER('%25SMITH%25')&outFields=PIN,TaxName,SiteAddress,SiteCity&returnGeometry=false&f=json&resultRecordCount=10",
"license": "public",
"endpoint_status": "live",
"verification": { "last_verified": "2026-05-15", "status": "live" }
}
],
"urbankit_county_page": "https://urbankitstudio.com/parcel-atlas/illinois/kane-county"
}
}The whole public API is described in /openapi.json (OpenAPI 3.1). It documents all nine endpoints, the bearer scheme, the rate-limit headers, and the error shape. Point Postman, an SDK generator or an agent at it rather than reading this page and transcribing it by hand.
It covers /api/atlas/lookup, /api/atlas/county, /api/atlas/radius, /api/atlas/bulk, /api/atlas/status, /api/atlas/request, and the three account routes under /api/v1. A test fails the build if an endpoint ships without being documented there, so the contract cannot quietly fall behind the API it describes.
When a county is not in the atlas, /api/atlas/lookup and /api/atlas/county answer with a request block. Its body can be sent unchanged to POST /api/atlas/request, which stores the request for us to weigh when choosing the next counties to index. It is free on every tier and needs no key. An email address is optional and is used only to tell you when that county ships. The route answers 202 with an id, and allows 5 requests per IP per hour. At most 5 new requests per IP address, and 300 in all, are stored in any 24 hours; past that it answers 429 and stores nothing. Requests are deleted after a year.
curl -X POST "https://urbankitstudio.com/api/atlas/request" \
-H "Content-Type: application/json" \
-d '{"county_fips": "06003", "county_name": "Alpine", "state_abbrev": "CA"}'
# 202 {"accepted": true, "id": "..."}Anonymous calls work up to 10 a day so you can try the response shape; a free key raises that to 500 a day, and the paid plans add bulk address-to-owner enrichment for whole lists. Owner data comes from each county's own public ArcGIS layer. When a county does not publish owner names, the response says so rather than guessing.
Three read-only routes let an agent see what it can buy, what its own key may do, and how much it has used, without a person opening the account page. None of them is billed or counted against quota. uks plans, uks whoami and uks usage call these same routes.
Every plan with its price, quota window and entitlements. It needs no key and is cached publicly for five minutes. A plan's upgrade_url is where a person starts buying. It signs them in first, so an agent hands the link to its human and never puts a key in a URL.
The calling key's tier, its live quota window with used, remaining and reset_at, the MCP tools it can call, its subscription, and the next plan up. A used of null means the counter could not be read, not that nothing was used.
Calls and units by endpoint, by UTC day and by key, for every key the account has held, over a window of up to 92 days. Denied and refunded calls count as calls, and units is what was billed. When truncated or incomplete is true, narrow the window and ask again.
curl "https://urbankitstudio.com/api/v1/plans" curl "https://urbankitstudio.com/api/v1/me" \ -H "Authorization: Bearer $UKS_API_KEY" curl "https://urbankitstudio.com/api/v1/usage?from=2026-09-01&to=2026-09-30" \ -H "Authorization: Bearer $UKS_API_KEY"
The two keyed routes share one limit of 60 calls a minute per IP address and 30 per account. Every key failure is the same 401 invalid-key. Below is a free key's answer from /api/v1/me.
{
"ok": true,
"as_of": "2026-10-10T14:02:11.000Z",
"user_id": "...",
"tier": "free",
"key": {
"key_prefix": "uk_live_...",
"label": "ci agent",
"created_at": "...",
"last_used_at": "..."
},
"quota": {
"window": "day",
"used": 37,
"limit": 500,
"remaining": 463,
"reset_at": "2026-10-11T00:00:00.000Z",
"quota_status": "ok"
},
"entitlements": {
"discovery": true,
"owner_data": false,
"bulk": false,
"radius": false,
"tools": ["find_parcel_by_address", "geocode_address", "list_indexed_states",
"list_counties_in_state", "find_county_by_fips",
"get_county_endpoint", "get_city_datasets", "request_county"]
},
"subscription": null,
"upgrade_url": "https://urbankitstudio.com/login?plan=starter",
"plans_url": "/api/v1/plans"
}package.json.uks whoami before a batch run.Every surface reads the same source, the County Parcel REST API Atlas, a hand-verified index of public ArcGIS endpoints. No hidden database sits behind them. The JSON files are the source of truth, and you can browse the whole set by state.
Some publishers require or request credit when their parcels are reused. The Attribution section of the SDK README lists each one with its credit line.
The browsable index of 515 indexed counties, by state, with each endpoint's searchable fields.
BrowsePer-tool reference, JSON-RPC examples, and client setup for the ten-tool server.
Read the docsThe SDK and the atlas data files it bundles, in their own public repo. The MCP server has its own, linked above.
Open repo (opens in a new tab)