Urbankit/StudioDeveloper docs

    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.

    RequestGET /api/atlas/lookup?address=1200 Market St, Philadelphia, PA
    Status200
    Plat of the geocoded point at latitude 39.951865266782, longitude -75.159970396307, on a 0.001 degree graticule-75.163-75.162-75.161-75.160-75.159-75.158-75.15739.95139.95239.95339.951865, -75.15997039°57'06.7"N 75°09'35.9"W
    ExampleAtlas 0.3.0Updated 2026-10-10
    Address submitted
    1200 Market St, Philadelphia, PA
    County
    Philadelphia, PA
    County FIPS
    42101
    Latitude
    39.951865266782
    Longitude
    -75.159970396307
    Result
    matched trueatlas_indexed true
    An example lookup response, drawn as a survey plat. The point sits at its geocoded position on a 0.001 degree graticule, with a true north arrow and a 100 meter scale bar.

    Quickstart

    You can make 10 lookups a day without a key, so the first call needs no sign-up.

    1. Send a full address

      Send street, city and state. A partial address can geocode to nothing and return matched: false.

    2. Check matched and atlas_indexed

      Both true means the county answered. A miss still returns 200, so branch on the fields and not on the status code.

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

    Bearer key
    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.

    EndpointWhat it does
    Atlas /api/atlas/
    GET /api/atlas/lookupResolve one address to its parcel record
    GET /api/atlas/countyWhat a county publishes, and whether it is indexed
    POST /api/atlas/radiusEvery parcel within a radius of an address
    POST /api/atlas/bulkEnrich a list of addresses in one call
    GET /api/atlas/statusLive health of the indexed county endpoints
    POST /api/atlas/requestRequest a county the atlas does not cover
    Account /api/v1/
    GET /api/v1/plansEvery plan, its price, quota and entitlements
    GET /api/v1/meWhat this key is, can do, and has left
    GET /api/v1/usageThis account's usage history, aggregated

    Resolve one address to its parcel record

    GET/api/atlas/lookup

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

      Full street address including city and state. A partial address will usually fail to geocode.

    • opstringOptional

      Idempotency 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_...Optional

      A key swaps the anonymous per-IP limit of 10 calls a day for your plan's quota.

    Response

    • okbooleanAlways

      Always true on a 200.

    • queryobject

      The request, echoed back.

    • matchedbooleanAlways

      False when the address could not be geocoded.

    • atlas_indexedboolean

      Whether the resolved county is in the atlas. False means we geocoded it but cannot query it.

    • geocodeobject

      Where the address resolved.

      • lat, number
      • lon, number
      • countyName, string
      • stateAbbrev, string
      • countyFips, string
    • geocode_staleboolean

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

    • fallbackstring

      Present when the county is not indexed: where to go instead.

    • requestobject

      Present on a not-indexed answer. body is ready to POST to url as-is; keys the server did not know are omitted. Requesting is free.

      • how, string
      • url, string
      • body, object
      • message, string
    • messagestring

      Human-readable detail, such as why a geocode failed. Do not match on it.

    • countyobject

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

    ok

    The endpoint's response passed the probe's check.

    degraded

    It answered with a 2xx, but the response failed the check.

    down

    An error, a timeout or a non-2xx response.

    unknown

    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.

    StatusWhen
    400The request was malformed: a missing parameter, or a body that is not the documented shape.
    401An 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.
    402usage_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.
    405Wrong HTTP method for this endpoint.
    429Rate limit or quota exhausted.
    500The 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.
    502geocode-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.
    503atlas-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.
    504geocode-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.

    HeaderMeaningSent on
    x-ratelimit-limitCalls allowed in the current window.200 and 429
    x-ratelimit-remainingCalls left in the current window.200 and 429
    x-ratelimit-resetUnix seconds at which the window resets.200 and 429
    retry-afterSeconds 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

    ToolServerKey
    list_countiesstdioNo
    find_countystdioNo
    get_parcel_endpointstdioNo
    build_owner_querystdioNo
    find_parcel_by_addressHostedNo
    geocode_addressHostedNo
    list_indexed_statesHostedNo
    list_counties_in_stateHostedNo
    find_county_by_fipsHostedNo
    get_county_endpointHostedNo
    request_countyHostedNo
    enrich_addressHostedPaid plan or pay-as-you-go
    radius_ownersHostedPaid 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

    CommandNetworkKeyWhat it does
    uks plansYesNoPlans: price, quota, window, owner data
    uks whoamiYesYesYour tier, key prefix and label, quota, subscription
    uks usage [--from D] [--to D]YesYesCalls and units in total, by endpoint, by day
    uks statesNoNoStates the atlas covers, with county and endpoint counts
    uks find <query> [--limit N]NoNoCounties by name, name plus state, or FIPS
    uks county <state> <county>NoNoOne county's endpoint record (also uks county <FIPS>)
    uks lookup "<address>"YesOptionalAddress to county and parcel endpoint
    uks request-county ...YesNoAsk for a county the atlas lacks (free)
    uks login --key uk_live_...NoNoSave a key
    uks logoutNoNoRemove the saved key

    CLI exit codes

    CodeMeaningRetry
    0Success, including "no match" and "not in the atlas" answersNot needed
    1Refused: no key where one is needed, a key the server rejects (401/402/403), or a plan without the featureNo
    2Could not run: bad arguments, a network failure, a request the server rejects (other 4xx), a server error (5xx), an unreadable responseOnce after a network failure or a 5xx; never for bad-arguments or another 4xx
    3Rate 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.

    StateCountiesEndpoints
    Alabama33
    Alaska11
    Arizona33
    Arkansas99
    California1515
    Colorado1111
    Connecticut99
    Delaware11
    District of Columbia11
    Florida6777
    Georgia66
    Hawaii22
    Idaho11
    Illinois98
    Indiana33
    Iowa11
    Kansas11
    StateCountiesEndpoints
    Kentucky1717
    Louisiana22
    Maine24
    Maryland33
    Massachusetts12
    Michigan65
    Minnesota33
    Mississippi22
    Missouri44
    Montana55
    Nebraska44
    Nevada33
    New Hampshire20
    New Jersey66
    New Mexico11
    New York1224
    North Carolina1515
    StateCountiesEndpoints
    North Dakota11
    Ohio88
    Oklahoma21
    Oregon88
    Pennsylvania66
    Rhode Island10
    South Carolina33
    South Dakota11
    Tennessee44
    Texas1111
    Utah44
    Vermont11
    Virginia55
    Washington66
    West Virginia66
    Wisconsin44
    Wyoming11

    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.

    Spec openapi.json 1.4.0. Prefer the overview? See Developers.Back to top