URBANKIT/STUDIO
    Sign inFREE TOOLS · NO SIGNUP
    URBANKIT/STUDIO · EST. 2026 · ONLINEFREE TOOLS · NO SIGNUP
    For developers & AI agents

    Parcel atlas, npm SDK, MCP server and CLI

    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.

    npm SDK

    @urbankitstudio/atlas v0.7.2. TypeScript, ESM + CJS, zero runtime deps.

    MCP server

    JSON-RPC 2.0, MCP spec 2025-06-18. Five tools via npm, eight on the hosted endpoint, no key.

    Command line

    @urbankitstudio/cli v0.1.8. One JSON document per command and exit codes a script can branch on.

    Underlying data

    481 counties across 50 states and DC, each with verified REST endpoints and searchable fields.

    npm SDK, @urbankitstudio/atlas

    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

    A real example

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

    listStates, findState, findCounty, findCountyByFips, listCountiesByState, buildParcelLookupDeepLink.

    SHAPE

    Dual ESM/CJS builds, full .d.ts types, sideEffects: false, zero runtime dependencies.

    MCP server, for AI agents

    The parcel atlas is available as a published npm MCP server you can run in any MCP-capable client with one command:

    npx -y @urbankitstudio/mcp-atlas

    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.

    Claude Desktop config

    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.

    Command line, @urbankitstudio/cli

    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 a county, offline

    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

    Read one county's endpoint

    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",
      ...
    }

    Errors an agent can branch on

    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
    }
    EXIT CODES
    0
    Success, including “no match” and “not in the atlas” answers.
    1
    Refused. No key, a rejected key, or a plan without the feature. Do not retry.
    2
    Could not run. A network failure or a 5xx is worth one retry. Bad arguments are not.
    3
    Rate limit or quota spent. Wait until reset_at, or for retry_after_seconds when that is all the error carries.
    ONLINE COMMANDS

    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.

    Property owner lookup API

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

    Machine-readable contract

    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.

    Request a county

    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.

    Account API, plans, quota and usage

    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.

    GET /api/v1/plans

    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.

    GET /api/v1/me

    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.

    GET /api/v1/usage

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

    SDK, MCP or CLI, which one?

    Reach for the npm SDK when

    • You are writing your own program and want county lookups inline, with no network round-trip and full TypeScript types.
    • You need deterministic, versioned data you can pin in package.json.
    • You are building a deep-link into the parcel-lookup tool from your own UI.

    Reach for the MCP server when

    • You want Claude, Cursor, or another AI client to resolve a parcel mid-conversation without copy-pasting.
    • You want address-to-APN and geocoding chained by the model, not wired by hand.
    • You do not want to ship or update a dependency. The endpoint tracks the latest atlas on its own.

    Reach for the CLI when

    • A shell script or a CI job needs a county endpoint and you would rather not write a program for it.
    • Your agent runs terminal commands and should branch on an exit code and one JSON document per call.
    • You want to check a key's quota with uks whoami before a batch run.

    The data underneath

    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.