Introduction
The ZIP Codes API v2 provides comprehensive postal code data for the United States and Canada. Look up ZIP codes, calculate distances, search by radius, and enrich results with census demographics, political districts, school districts, and more.
Base URL: https://api.zip-codes.com. All API endpoints live under the /v2/ prefix. Responses use a consistent JSON envelope with built-in error handling and credit tracking.
Key Features
- US ZIP & ZIP+4 lookups with demographics, delivery points, and business data
- Canadian FSA & postal code lookups with province-level data
- Radius search with cross-border support (US + Canada)
- Distance calculation between any combination of ZIP codes, FSAs, postal codes, and coordinates
- Opt-in enrichments including ACS demographics, census boundaries, political districts, school districts, timezone, and more
- Five batch routes: up to 250 addresses or 100 items on each other route
Authentication
Get a key and review plans at API pricing. Lookup requests require a valid API key. Health, documentation, OpenAPI, Swagger and Postman are public. Send it in the X-Api-Key header or as Authorization: Bearer:
X-Api-Key header (recommended)
X-Api-Key: your_api_key
Authorization: Bearer
Authorization: Bearer your_api_key
Query parameter (convenience only, e.g. testing in a browser)
GET /v2/quick-zip?code=90210&key=your_api_key
X-Api-Key header (or Authorization: Bearer) in applications. Two different keys in one request are refused. The key query parameter is supported for quick tests, but keys in URLs can end up in server logs, proxies and browser history.Request & Response Format
Code inputs (including distance from/to) are strings of at most 50 characters: 5-digit ZIP, ZIP+4 with or without a dash, FSA, full Canadian postal code with an optional space, or lat,lon (latitude -90..90, longitude -180..180). Include flags are split on commas, trimmed, lowercased, normalized from hyphens to underscores, and de-duplicated. Single POST include fields are strings. Authorization: Bearer followed by your key is accepted as well as X-Api-Key; two different keys in one request are refused.
| Aspect | Details |
|---|---|
| Content-Type | All responses are application/json; charset=utf-8. Send Content-Type: application/json; POST bodies under /v2 are treated as JSON even if this header is omitted. |
| POST Body | Non-batch endpoints accept GET query parameters or POST with a JSON body containing the same parameter names. Body values win; missing body fields fall back to the query. Batch endpoints are POST-only. |
| Compression | The API supports Brotli and Gzip response compression. Send Accept-Encoding: br or Accept-Encoding: gzip for smaller payloads. |
| Caching | All API responses include Cache-Control: no-store. Implement your own caching layer if needed. |
| CORS | CORS allows any origin. Per-key IP and Origin allowlists are enforced when configured. |
| Request ID | Every response includes an X-Request-Id header and meta.request_id field (a ULID). Include this in support requests for faster troubleshooting. |
include Parameter | Controls what data appears in the response. On /zip and /radius, it adds enrichment data (e.g. include=timezone,census). On /suggest, it filters which entity types are returned (e.g. include=zip,place) — all types are included by default. |
Quick Start
Make your first API call in seconds:
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/quick-zip?code=90210"
import requests
resp = requests.get(
"https://api.zip-codes.com/v2/quick-zip",
params={"code": "90210"},
headers={"X-Api-Key": "YOUR_API_KEY"}
)
data = resp.json()
print(data["results"][0]["city"]) # Beverly Hills
const resp = await fetch(
"https://api.zip-codes.com/v2/quick-zip?code=90210",
{ headers: { "X-Api-Key": "YOUR_API_KEY" } }
);
const data = await resp.json();
console.log(data.results[0].city); // Beverly Hills
Your Dashboard
The API Portal dashboard gives you full control over your account:
- API Keys — Create, rotate, and configure keys with per-key IP whitelisting and CORS origin restrictions.
- Usage Analytics — Daily and monthly usage charts, per-key breakdowns, and CSV export for your records.
- Billing — Manage your subscription tier, purchase credit packs, and download invoices.
- Settings — Update account details and notification preferences.
Free tier accounts start with 2,500 credits/day. Sign in to get started →
Fields the schema marks nullable are always present and null when they have no value (for example postal_info on a US ZIP, zip_info on a Canadian code, an empty census layer, a suppressed ACS value); every other field is omitted when it has no value (an enrichment that was not requested, an empty notice list). Postal, radius, distance and suggest items are flattened: {success,input?,error?,notices?,...fields}, with no data wrapper. Address retains data; health uses a top-level data object. A well-formed lookup with no match returns HTTP 200 and a per-item error. Single address no-match uses top-level success:false and an error inside results[0].
Response Format
Every API response follows a consistent envelope structure:
{
"success": true,
"request": {
"code": "90210",
"include": [
"timezone"
]
},
"results": [
"..."
],
"meta": {
"request_id": "01M3SX2V9NY9MRRAMWVJG0C09E",
"version": "2.0.0",
"timing_ms": 45,
"credits": {
"used": 2,
"remaining": 74998,
"breakdown": {
"base": 1,
"enrichments": {
"timezone": 1
}
},
"source": "subscription"
}
}
}
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the request succeeded |
request | object | Echo of parsed request parameters |
results | array | Array of result objects (varies by endpoint) |
error | object (optional) | Omitted on success; whole-request failures contain {code,message,retryable?,url}. Lookup failures may instead have an error inside a result item. |
meta.request_id | string | ULID request identifier (also in X-Request-Id header) |
meta.version | string | API version: the newest released version (the info.version of the OpenAPI spec) |
meta.timing_ms | number | Total processing time in milliseconds |
meta.credits | object | Credit usage breakdown for this request |
meta.notices | array | Informational messages (see Notices) |
Notices
The meta.notices array contains non-error informational messages about request processing. Notices may also appear on result items. Only FREE_TIER_LIMIT_CAPPED includes an optional url, linking to pricing; all other notices omit it:
| Code | Description |
|---|---|
INPUT_NORMALIZED | Input was reformatted (e.g., whitespace trimmed, case corrected) |
FALLBACK_USED | On quick-zip and distance only: Canadian postal code not found; FSA-level data returned instead |
ENRICHMENT_UNAVAILABLE | Requested enrichment cannot be delivered (no credit charged for the unavailable flag) |
ENRICHMENT_FLAG_RENAMED | On /v2/zip and its batch, per item: a legacy flag on a Canadian code (census, census_YYYY, cd, cd_YYYY) was answered under statcan. Charged as statcan; asking for both returns one block, charged once |
ENRICHMENT_FLAG_RETIRED | Reserved, not sent yet. Include flags, years, fields and endpoints are never removed from /v2: one that is retired is still accepted and answered with this notice (with its data while it exists, otherwise not charged) |
VALIDATION_UNKNOWN_ENRICHMENT | Unknown enrichment flags ignored without charge; a notice, not an HTTP 400 error |
REVERSE_GEOCODE | Coordinate was reverse-geocoded to a postal code |
BATCH_DUPLICATES_DETECTED | Duplicates are processed and billed individually |
Address Matching
Parse and validate a raw US street address against USPS ZIP+4 delivery-point data. Returns the standardized address, ZIP+4 code, address components, and delivery metadata. Handles partial input — with a ZIP code, the city and state are optional; without one, give the city and state.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
address | string | Required | Raw street address to match: at most 500 raw characters and 9–300 after cleanup. Accepts full or partial addresses (e.g., 1600 Pennsylvania Ave NW, Washington DC 20500 or 350 5th Ave, New York). |
include | string | Optional | Comma-separated enrichments: coordinates, census, cd, state_leg, school_district, timezone, and the tract ACS flags below. |
Credit Cost
Base 1 credit per request, whether or not a match is found. Requesting coordinates adds 1 credit when a location is returned. Each boundary flag and each tract ACS profile and year adds 1 credit only when delivered. Credit availability is checked against the requested enrichments before matching.
Include Flags
Paths below are relative to results[0].data for single calls and results[i].data for batch. GET takes a comma-separated list; POST /v2/address requires a string (an array returns HTTP 400); batch accepts a string or array. All flags are optional; the default is no enrichments.
| Flag | What it adds | Path below data | Credits |
|---|---|---|---|
| coordinates | ZIP+4 point, latitude/longitude (6 decimals) | location | +1 when returned |
| census[_YYYY] | Counties, subdivisions, places, tracts, block groups, metro areas/divisions | census.{current|YYYY} | +1 per delivered year |
| cd[_YYYY] | Congressional districts | congressional_district.{current|YYYY} | +1 per delivered year |
| state_leg[_YYYY] | State upper/lower legislative districts | state_legislative.{current|YYYY}.upper / lower | +1 per delivered year |
| school_district | NCES school districts | school_district.current | +1 when delivered |
| acs_tract_demographic[_YYYY] | DP05 tract profile | acs.tract.{current|YYYY}.demographic | +1 per delivered profile/year |
| acs_tract_social[_YYYY] | DP02 tract profile | acs.tract.{current|YYYY}.social | +1 per delivered profile/year |
| acs_tract_economic[_YYYY] | DP03 tract profile | acs.tract.{current|YYYY}.economic | +1 per delivered profile/year |
| acs_tract_housing[_YYYY] | DP04 tract profile | acs.tract.{current|YYYY}.housing | +1 per delivered profile/year |
Boundary Enrichments
Use census for counties, county subdivisions, places, tracts, block groups, metro/micro statistical areas, and metropolitan divisions; cd for congressional districts; state_leg for upper and lower state legislative districts; and school_district for school districts. Add a year to census, cd or state_leg for that year’s boundaries (2010–2025, for example census_2022, cd_2012, state_leg_2020); the bare flags return the latest year (2026). Flags accept either dashes or underscores and are case-insensitive; duplicate flags count once.
All four flags use a spatial lookup for one point: the matched ZIP+4 record's centroid. You do not need to request coordinates; without that flag, data.location is omitted. With coordinates, it contains latitude and longitude rounded to 6 decimals.
Each flag fills its own family beside the others in data, in the same names and order as /v2/zip: census, congressional_district, state_legislative, and school_district. Each family holds its latest year under current, and census_YYYY, cd_YYYY and state_leg_YYYY add that year under its own "YYYY" key beside current, newest first (school_district serves current only and timezone the latest release: neither has year flags). A year flag naming the newest year (cd_2026 or census_2026 today) is answered under current, not under its own key; a requested family with nothing found is present with current: null, and an unrequested family is absent. data.census.current holds arrays county (with is_usps_primary), county_subdivision, place, tract, block_group, metro_micro_cbsa_statistical_area, and metropolitan_cbsa_division (an address lies in one of each, so each list has at most one row). data.congressional_district.current contains congress, districts, and primary:{geoid,state,district_number} with an integer district number. data.state_legislative.current contains legislative_year, upper, and lower arrays. Every spatial block on /v2/address (each census, congressional and state legislative period, school_district and timezone) opens with the same attribution fields in the same order: source, product, attribution. They come from the data file's provenance: the publisher, the data product, and the credit line the publisher asks for; each source's license, product page, vintages and refresh cadence are listed under Spatial layers: full provenance. Census, congressional and state legislative periods then give the TIGER/Line year and vintage. Show attribution wherever you display the data. Point rows carry pct_of_zip, pct_of_area and shared_area_sq_mi as null (a point has no area). details.congressional_district is a different field: the district USPS assigns to the ZIP+4, as USPS publishes it, independent of the cd flag. USPS’s assignment can be wrong; the cd flag’s districts are the districts whose legal boundaries (Census TIGER/Line) contain the matched location. In the newest year, primary is the USPS district when USPS assigns one (basis says so), so it can differ from the district containing the location; earlier years’ primary is always the district containing the location, so compare districts, not primary, across years.
A boundary flag costs one credit per year, only when a usable point exists and all its lookups complete. Empty lookups are null and still count as delivered. No match, a ZIP-level answer without a location, or a failed boundary read makes the affected flag unavailable and uncharged. One ENRICHMENT_UNAVAILABLE notice names all unavailable boundary and tract enrichments; the address answer is still returned.
Tract ACS Enrichments
Use acs_tract_demographic, acs_tract_social, acs_tract_economic, or acs_tract_housing. Unsuffixed flags select the latest ACS year (2024). Append a year, _2011 to _2023, for an earlier release (for example acs_tract_housing_2015); _2024 is also accepted. Supported years are 2011–2024. An unsuffixed flag and its latest-year alias count as one enrichment, for 56 distinct tract profile/year tags.
Profiles describe the census tract that contains the matched address's location. They appear under the geography level, results[].data.acs.tract: current for the latest year, and each earlier year asked for under its own "YYYY" key. Each year block uses the same ACS field names as /v2/zip, with geography_level: "tract" and geo_id of 1400000US followed by its 11-digit tract GEOID. The tract is looked up separately for each year, in that year's Census TIGER/Line tracts, the geography that ACS release is published on: tracts are renumbered and redrawn between releases (the 2020 Census tracts from 2020, Connecticut's planning regions from 2022), so geo_id can differ from year to year for the same address. Each block includes source, product, year, vintage ({year-4}-{year}), and tables.
If the address is unmatched, the match has no location, no tract contains it, or a requested profile has no row for that year, that enrichment is not charged. One ENRICHMENT_UNAVAILABLE notice in meta.notices names the unavailable flags and explains why. A missing profile may be null within a block; data.acs is omitted if no requested profile was delivered. Unsupported tract flags or years are ignored without charge and reported through VALIDATION_UNKNOWN_ENRICHMENT. Tract flags are only available on the address endpoints.
data.school_district.current contains the attribution fields (source, product, attribution), school_year, and districts:[{leaid,name,type,grade_low,grade_high,state_fips,land_area_sq_mi,water_area_sq_mi,centroid?,office?,locale?,nces_codes}]. ACS blocks identify source:"U.S. Census Bureau", product:"ACS 5-Year Estimates Data Profiles", and a five-year vintage. timezone adds data.timezone (after school_district): the same object /v2/zip returns, {source, zones:[{iana_name, name, abbreviation, abbreviation_dst, utc_offset, utc_offset_dst, observes_dst, pct_of_zip}]}, plus the attribution fields (source, product, attribution) from the time zone boundary file (timezone-boundary-builder, newest release; OpenStreetMap-derived, ODbL). The address point lies in one zone, so zones has one entry; its pct_of_zip is null (an address is a point; the key is kept so the shape matches /v2/zip). +1 credit when delivered; with no point, or no time zone data, it is ENRICHMENT_UNAVAILABLE and not charged. Unknown flags (including acs_demographic and unsupported tract years) are ignored free with VALIDATION_UNKNOWN_ENRICHMENT: Unknown enrichment flag(s) ignored: {flags}.
Batch Address Enrichment
POST /v2/address/batch accepts up to 250 addresses and the same flags in the body include (a comma-separated string or array). A paid plan or purchased credits are required. Credits are reserved for every requested enrichment, then unused credits are refunded. Each successfully processed item costs the base credit and only its delivered coordinates, boundary flags and tract profiles; rejected or failed items are not charged. Matched items carry data.census, data.congressional_district, data.state_legislative, data.school_district, data.timezone, and data.acs in the same shapes as the single endpoint. One envelope ENRICHMENT_UNAVAILABLE notice counts processed addresses missing requested enrichments, including unmatched addresses. Unknown flags are ignored without charge and named in VALIDATION_UNKNOWN_ENRICHMENT.
{
"addresses": [
"1600 Pennsylvania Ave NW, Washington DC 20500",
"not a real address 99999"
],
"include": [
"coordinates",
"acs_tract_economic"
]
}Items are {success,input:{address},matched,data?,notices?}. An unmatched item is {success:true,input:{address},matched:false}, with no engine reason code; it counts in meta.batch.succeeded. Rejected or failed items have success:false, input, and error, and cost 0 credits. The batch timeout is 45 seconds, as for every request.
{
"success": true,
"request": {
"addresses_count": 2
},
"results": [
{
"success": true,
"input": {
"address": "1600 Pennsylvania Ave NW, Washington DC 20500"
},
"matched": true,
"data": {
"confidence": "high",
"formatted_address": "1600 PENNSYLVANIA AVE NW, WASHINGTON, DC 20500-0005",
"location": {
"latitude": 38.898704,
"longitude": -77.036497
},
"acs": {
"tract": {
"current": {
"source": "U.S. Census Bureau",
"product": "ACS 5-Year Estimates Data Profiles",
"year": 2024,
"vintage": "2020-2024",
"geo_id": "1400000US11001980000",
"geography_level": "tract",
"tables": {
"economic": "DP03"
},
"economic": {
"commuting": {
"drove_alone": {
"column": "DP03_0019",
"est": 12,
"moe": 18,
"pct": 70.6,
"pct_moe": 8.0
},
"public_transportation": {
"column": "DP03_0021",
"est": 5,
"moe": 8,
"pct": 29.4,
"pct_moe": 8.0
}
}
}
}
}
}
},
"notices": [
{
"code": "MATCHED",
"message": "Address matched.",
"class": "match"
}
]
},
{
"success": true,
"input": {
"address": "not a real address 99999"
},
"matched": false
}
],
"meta": {
"request_id": "01M3SX2VE1BEM6ACR6DME1GXK2",
"version": "2.0.0",
"timing_ms": 53,
"credits": {
"used": 5,
"source": "subscription",
"remaining": 1485722,
"breakdown": {
"base": 0,
"enrichments": {},
"note": "2 addresses x 1 base + 3 enrichment = 5 credits"
}
},
"notices": [
{
"code": "ENRICHMENT_UNAVAILABLE",
"message": "Enrichment(s) unavailable: acs_tract_economic for 1 of 2 processed addresses; matched location or requested enrichment data is unavailable; not charged."
}
],
"batch": {
"requested": 2,
"succeeded": 2,
"failed": 0
}
}
}Example Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/address?address=9641+Sunset+Blvd%2C+Beverly+Hills%2C+CA+90210&include=coordinates,census,cd,state_leg,school_district,acs_tract_demographic,acs_tract_housing_2023"
Example Response
Captured response excerpt: fields and ACS profiles are trimmed; the original eight-credit charge is retained.
{
"success": true,
"request": {
"address": "9641 Sunset Blvd, Beverly Hills, CA 90210",
"include": "coordinates,census,cd,state_leg,school_district,acs_tract_demographic,acs_tract_housing_2023"
},
"results": [
{
"input": "9641 Sunset Blvd, Beverly Hills, CA 90210",
"success": true,
"data": {
"confidence": "medium",
"formatted_address": "9641 SUNSET BLVD, BEVERLY HILLS, CA 90210-2938",
"address_line1": "9641 SUNSET BLVD",
"address_line2": "BEVERLY HILLS, CA 90210-2938",
"address_components": {
"number": "9641",
"street": "SUNSET",
"suffix": "BLVD",
"city": "BEVERLY HILLS",
"state": "CA",
"zip": "90210",
"plus4": "2938",
"country": "US"
},
"location": {
"latitude": 34.081348,
"longitude": -118.412499
},
"acs": {
"tract": {
"current": {
"source": "U.S. Census Bureau",
"product": "ACS 5-Year Estimates Data Profiles",
"year": 2024,
"vintage": "2020-2024",
"geo_id": "1400000US06037700700",
"geography_level": "tract",
"tables": {
"demographic": "DP05"
},
"demographic": {
"sex_and_age": {
"total_population": {
"column": "DP05_0001",
"est": 2935,
"moe": 578,
"pct": 2935,
"pct_moe": null
},
"median_age": {
"column": "DP05_0018",
"est": 56.4,
"moe": 8.7
}
}
}
},
"2023": {
"source": "U.S. Census Bureau",
"product": "ACS 5-Year Estimates Data Profiles",
"year": 2023,
"vintage": "2019-2023",
"geo_id": "1400000US06037700700",
"geography_level": "tract",
"tables": {
"housing": "DP04"
},
"housing": {
"occupancy": {
"total": {
"column": "DP04_0001",
"est": 1303,
"moe": 144,
"pct": 1303,
"pct_moe": null
},
"occupied": {
"column": "DP04_0002",
"est": 1170,
"moe": 149,
"pct": 89.8,
"pct_moe": 7.9
}
}
}
}
}
},
"census": {
"current": {
"source": "U.S. Census Bureau",
"product": "TIGER/Line Shapefiles",
"attribution": "Source: U.S. Census Bureau, TIGER/Line Shapefiles 2025",
"year": 2025,
"vintage": "2025",
"county": [
{
"fips": "06037",
"name": "Los Angeles",
"is_usps_primary": true
}
],
"county_subdivision": [
{
"geoid": "0603791750",
"name": "Los Angeles"
}
],
"place": [
{
"geoid": "0606308",
"name": "Beverly Hills"
}
],
"tract": [
{
"geoid": "06037700700",
"name": "7007"
}
],
"block_group": [
{
"geoid": "060377007002",
"name": "2"
}
],
"metro_micro_cbsa_statistical_area": [
{
"cbsa_code": "31080",
"name": "Los Angeles-Long Beach-Anaheim, CA"
}
],
"metropolitan_cbsa_division": [
{
"metdiv_code": "31084",
"name": "Los Angeles-Long Beach-Glendale, CA",
"cbsa_code": "31080"
}
]
}
},
"congressional_district": {
"current": {
"source": "U.S. Census Bureau",
"product": "TIGER/Line Shapefiles",
"attribution": "Source: U.S. Census Bureau, TIGER/Line Shapefiles 2025",
"year": 2025,
"vintage": "2025",
"congress": 119,
"districts": [
{
"geoid": "0636",
"state": "CA",
"district_number": 36,
"name": "Congressional District 36",
"land_area_sq_mi": 97.11,
"water_area_sq_mi": 97.51
}
],
"primary": {
"geoid": "0636",
"state": "CA",
"district_number": 36
}
}
},
"state_legislative": {
"current": {
"source": "U.S. Census Bureau",
"product": "TIGER/Line Shapefiles",
"attribution": "Source: U.S. Census Bureau, TIGER/Line Shapefiles 2025",
"year": 2025,
"vintage": "2025",
"legislative_year": 2024,
"upper": [
{
"geoid": "06024",
"name": "24"
}
],
"lower": [
{
"geoid": "06051",
"name": "51"
}
]
}
},
"school_district": {
"current": {
"source": "National Center for Education Statistics (NCES)",
"product": "EDGE School District Boundaries",
"attribution": "Source: U.S. Department of Education, National Center for Education Statistics (NCES), EDGE School District Boundaries, SY 2024-25 (TIGER 2025)",
"school_year": "2024-2025",
"districts": [
{
"leaid": "0604830",
"name": "Beverly Hills Unified School District",
"type": "unified",
"grade_low": "KG",
"grade_high": "12"
}
]
}
}
},
"notices": [
{
"code": "PARTIAL_MATCH",
"message": "Address matched; some details could not be confirmed.",
"class": "match"
}
]
}
],
"meta": {
"request_id": "01M4CRKA0K3PCJ56G6GMA3BN34",
"version": "2.0.0",
"timing_ms": 24,
"credits": {
"used": 8,
"source": "subscription",
"remaining": 856921,
"breakdown": {
"base": 1,
"enrichments": {
"coordinates": 1,
"acs_tract_demographic": 1,
"acs_tract_housing_2023": 1,
"census": 1,
"cd": 1,
"state_leg": 1,
"school_district": 1
}
}
}
}
}
Result Fields
| Field | Type | Description |
|---|---|---|
input | string | The raw address string you submitted |
success | boolean | true if the address was matched |
data | object (optional) | Matched address, including requested enrichments when available |
Matched Address Data
| Field | Type | Description |
|---|---|---|
formatted_address | string | USPS-standardized full address with ZIP+4 |
address_line1 | string | Street line (number, directional, street, suffix, secondary) |
address_line2 | string | Last line (city, state, ZIP+4) |
address_components | object | Parsed address components (see table below) |
confidence | string | Match confidence: high, medium, or low |
details | object | USPS delivery-point metadata |
pbsa_indicator | boolean (optional) | Included only when true |
location | object (optional) | Requested latitude/longitude |
census | object (optional) | Requested census geography under current and each requested year |
congressional_district | object (optional) | Requested congressional districts under current and each requested year |
state_legislative | object (optional) | Requested upper/lower state legislative districts under current and each requested year |
school_district | object (optional) | Requested school districts under current, with source and school_year |
acs | object | Requested ACS by geography level (tract), then by year (current, 2023, 2022); omitted when no requested profile is delivered |
Address Components
| Field | Type | Description |
|---|---|---|
number | string (optional) | House/building number |
pre_directional | string (optional) | Prefix directional (e.g., N, SW) |
street | string (optional) | Street name |
street_pre_type | string (optional) | Street pre-type (e.g., AVE in AVE OF THE AMERICAS) |
suffix | string (optional) | Street suffix (e.g., ST, AVE, BLVD) |
post_directional | string (optional) | Postfix directional (e.g., NW) |
secondary_type | string (optional) | Secondary address type (e.g., APT, STE, UNIT) |
secondary_number | string (optional) | Secondary address number (e.g., 4B, 200) |
city | string | Matched city name |
state | string | Two-letter state abbreviation |
zip | string | 5-digit ZIP code |
plus4 | string | 4-digit ZIP+4 suffix |
country | string | Country code (US) |
Details (USPS Metadata)
| Field | Type | Description |
|---|---|---|
record_type | object (optional) | USPS record type with code and description: S (Street), H (Highrise), P (PO Box), F (Firm), G (General Delivery), R (Rural Route) |
carrier_route | object (optional) | USPS carrier route with code and description |
county | object (optional) | County fips and name that USPS assigns to the ZIP+4. This is a postal assignment: the address can physically lie in a different county. For the county the address’s point actually falls in, request census (latest boundaries) or census_YYYY (that year’s boundaries) and read census.{current|YYYY}.county. A year flag shows the county as it existed then: in Connecticut, census_2021 and earlier return the former counties, and 2022 onward the planning regions that replaced them. |
congressional_district | string (optional) | Congressional district number |
building_name | string (optional) | Building or firm name (when available) |
government_building | object (optional) | USPS government building indicator as code and description: A (City Government Building), B (Federal Government Building), C (State Government Building), D (Firm Only), E (City Government Building and Firm Only), F (Federal Government Building and Firm Only), G (State Government Building and Firm Only), or an empty code with "Not marked as a government building": the USPS record carries no indicator, which does not mean the building is not a government building. Omitted when no ZIP+4 record was matched |
success is false and data is omitted. The base credit still applies; coordinates, boundary flags and tract profiles are not charged. The per-item error identifies insufficient input, ambiguity, or no match.Address item notices include class and codes MATCHED, PARTIAL_MATCH, ZIP_CORRECTED, CITY_CORRECTED, STATE_CORRECTED, HOUSE_NUMBER_CORRECTED, STREET_CORRECTED, NO_HOUSE_NUMBER, SECONDARY_NEEDED, SECONDARY_NOT_VERIFIED, UNIT_READ_AS_PMB, INPUT_NOT_USED, ZIP4_WITHHELD, and MILITARY_ADDRESS. HOUSE_NUMBER_CORRECTED means the house number was read differently from how it was written (37061/ read as 3706 1/2); its message names both, and the match is then PARTIAL_MATCH. UNIT_READ_AS_PMB (class correction): at a commercial mail receiving agency (a mail drop such as a UPS Store) a unit that does not confirm is the renter's private mailbox, printed as PMB n; the message names the unit as written and the box. MILITARY_ADDRESS (class info, last): an APO/FPO/DPO address, where address_components.street holds the unit (UNIT 2050) and number the box number (4190), USPS's values as they are; the message names both, or says no box number was supplied. Test keys accept only configured demo addresses.
No Match, Ambiguous, or Insufficient Input
HTTP 200, top-level success:false, no top-level error. results[0] contains input, success:false, and error:{code,message,url}. No candidate list is returned.
INSUFFICIENT_INPUT: Not enough information to locate the address. Include a city and state, or a ZIP code.AMBIGUOUS: More than one address matches. Add detail such as a unit number, directional or ZIP code.NOT_FOUND: The address could not be matched.
No match costs 1 credit. Requested coordinates and boundary/tract flags produce one ENRICHMENT_UNAVAILABLE notice and are not charged. On a matched address, unavailable enrichment notices explain that the matched location or requested enrichment data is unavailable.
{
"success": false,
"request": {
"address": "123 Nowhere Lane Faketown ZZ",
"include": "coordinates,census"
},
"results": [
{
"input": "123 Nowhere Lane Faketown ZZ",
"success": false,
"error": {
"code": "INSUFFICIENT_INPUT",
"message": "Not enough information to locate the address. Include a city and state, or a ZIP code.",
"url": "https://www.zip-codes.com/api/"
}
}
],
"meta": {
"request_id": "01M3SX2VHP0M9TXH8WFHW7ZM3G",
"version": "2.0.0",
"timing_ms": 5,
"credits": {
"used": 2,
"source": "subscription",
"remaining": 1485721,
"breakdown": {
"base": 1,
"enrichments": {
"coordinates": 1
}
}
},
"notices": [
{
"code": "ENRICHMENT_UNAVAILABLE",
"message": "Enrichment(s) unavailable: census; address was not matched; not charged."
}
]
}
}Geographic Suggest
Typeahead search for geographic entities. Returns ZIP codes, cities/places, counties, metro areas (CBSAs), states/provinces, and Canadian FSAs/postal codes, school districts and USPS city names matching a query. Results include authoritative identifiers (FIPS, GEOID), coordinates, bounding boxes, and population data.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Required | Search query (1–200 characters). Case-insensitive. |
limit | integer | No | Max results to return. Default: 10. Free/test tiers: max 15. Paid tiers: max 500. |
include | string | No | Comma-separated entity types to include: zip, place, county, cbsa, fsa, state, school_district, ca_postal, uspscity. Default: all nine types. Unlike /zip and /radius where include adds enrichment data, here it filters which entity types are returned. |
state | string | No | US state or CA province abbreviation to filter results (e.g., CA, ON). |
country | string | No | Filter by country: US or CA. Default: both. |
proximity | string | No | Geographic bias for ranking. Accepts multiple formats: lat,lon (coordinates), auto (caller IP), IP address, 5-digit ZIP, 3-char FSA, or 2-letter state/province. Nearby results rank higher (soft bias, not a filter). |
auto or explicit IP) resolves coordinates for the response echo but does not currently influence scoring. Numeric queries (ZIP prefixes) now benefit from proximity bias.Entity Types
| Type | Count | Description | Type-Specific Fields |
|---|---|---|---|
zip | ~42K | US ZIP codes | city, classification |
place | ~30K | US Census places (cities, towns, CDPs) | fips, geoid, associated_zips |
county | ~3.2K | US counties (includes parishes, boroughs) | fips, geoid |
cbsa | ~1K | Metropolitan & micropolitan statistical areas | cbsa_code, geoid, metro_micro |
fsa | ~1.6K | Canadian Forward Sortation Areas | city, province, postal_code_count |
state | ~67 | US states/territories & CA provinces (with abbreviation aliases) | (none) |
school_district | — | School districts | Available entity metadata |
ca_postal | — | Canadian postal codes | Available postal metadata |
uspscity | — | USPS city names | Available city metadata |
Credit Cost
| Limit Range | Credits | Available To |
|---|---|---|
| 1–15 | 1 | All tiers (including free) |
| 16–50 | 2 | Paid only |
| 51–150 | 3 | Paid only |
| 151–500 | 5 | Paid only |
Billing based on requested limit, not results returned: a search is charged even when it matches nothing (the search ran; the answer is no match). Free/test tier requests above 15 are clamped with a FREE_TIER_LIMIT_CAPPED notice. Validation errors (missing q, invalid include) cost 0 credits.
Example Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/suggest?q=spring&limit=3"
Example: With proximity bias
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/suggest?q=spring&proximity=40.7128,-74.006&limit=3"
Example: Filtered by type and state
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/suggest?q=cook&include=county&state=IL&limit=3"
Example Response
{
"success": true,
"request": {
"q": "cook",
"limit": 3,
"include": [
"county"
],
"state": "IL"
},
"results": [
{
"success": true,
"total_matched": 5,
"matches": [
{
"type": "county",
"name": "Cook County",
"state": "IL",
"country": "US",
"fips": "17031",
"geoid": "17031",
"population": 5182090,
"location": {
"lat": 41.8440477,
"lon": -87.820797
},
"bbox": {
"min_lat": 41.46971,
"max_lat": 42.154265,
"min_lon": -88.263641,
"max_lon": -87.524165
}
},
{
"type": "county",
"name": "Rock Island County",
"state": "IL",
"country": "US",
"fips": "17161",
"geoid": "17161",
"population": 142757,
"location": {
"lat": 41.5511206,
"lon": -90.4512621
},
"bbox": {
"min_lat": 41.326838,
"max_lat": 41.7829645,
"min_lon": -91.0719555,
"max_lon": -90.159878
}
},
{
"type": "county",
"name": "Boone County",
"state": "IL",
"country": "US",
"fips": "17007",
"geoid": "17007",
"population": 53230,
"location": {
"lat": 42.3297656,
"lon": -88.849764
},
"bbox": {
"min_lat": 42.15232,
"max_lat": 42.495046,
"min_lon": -88.940992,
"max_lon": -88.70541
}
}
]
}
],
"meta": {
"version": "2.0.0",
"timing_ms": 2,
"credits": {
"used": 1,
"remaining": 74999,
"breakdown": {
"base": 1,
"enrichments": {}
},
"source": "subscription"
},
"request_id": "01M3SX2V9NY9MRRAMWVJG0C09E"
}
}
Match Object Fields
| Field | Type | Description |
|---|---|---|
type | string | Entity type: zip, place, county, cbsa, fsa, state, school_district, ca_postal, uspscity |
name | string | Display name (ZIP code number, place name, county name, etc.) |
state | string | US state abbreviation (or province for FSA) |
country | string | US or CA |
population | integer | Total population (ACS 5-year estimates for the US, the Census of Population for Canada). A ZIP Code: its ZCTA’s total, else our delivery-point estimate; a USPS city: the sum of its ZIP Codes; a place, county, CBSA or state: its own ACS total. Omitted when there is none (PO Box, unique and military ZIP Codes, postal codes, school districts) |
location | object | { lat, lon } — a ZIP or postal code’s point; Census’s internal point for places, counties and CBSAs; the population-weighted centre of their ZIP Codes for USPS cities and states |
bbox | object | { min_lat, max_lat, min_lon, max_lon } — bounding box of the boundary (USPS cities and states: of their ZIP Code boundaries); omitted when there is none (postal codes, ZIP Codes without a boundary) |
fips | string | Places and counties: the Census GEOID (same as geoid); school districts: the NCES LEAID |
geoid | string | Census GEOID (places, counties, CBSAs); school districts: the NCES LEAID |
associated_zips | array | USPS cities: up to 50 of their ZIP Codes (standard first); places: up to 20 ZIP Codes whose USPS city name is the place’s name in the same state; school districts: up to 20 ZIP Codes sharing area with the district |
cbsa_code | string | CBSA code (CBSAs only) |
metro_micro | string | Metropolitan or Micropolitan (CBSAs only) |
city | string | Primary city name (ZIPs and FSAs only) |
classification | string | ZIP type: Standard, PO Box, Military, Unique (ZIPs only) |
province | string | CA province abbreviation (FSAs and CA states only) |
Quick ZIP Lookup
Fast postal code lookup returning basic location data with optional timezone enrichment. Supports US ZIP codes (5-digit), US ZIP+4 codes, Canadian FSAs (3-character), full Canadian postal codes (6-character), and latitude/longitude coordinates for reverse geocoding.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Required | ZIP code, ZIP+4, FSA, Canadian postal code, or coordinates (lat,lon) |
include | string | Optional | Comma-separated enrichment flags. Only timezone is supported on this endpoint. |
Supported Code Types
| Type | Format | Example | Country | Base Cost |
|---|---|---|---|---|
| ZIP | 5 digits | 90210 | US | 1 credit |
| ZIP+4 | 9 digits (with or without dash) | 90210-1234 | US | 2 credits |
| FSA | 3 alphanumeric (A1A) | M5V | CA | 1 credit |
| Postal | 6 alphanumeric (A1A1A1) | M5V2H1 | CA | 1 credit |
| Coordinates | lat,lon (decimal degrees) | 34.1030,-118.4105 | US + CA | 2 or 4 credits (before timezone) |
code parameter as lat,lon.Available Enrichments
| Flag | Adds to Response | Cost | Availability |
|---|---|---|---|
timezone | One flat object: source, product, attribution (the same values /v2/zip and /v2/address return), then iana_name, name, abbreviation, abbreviation_dst, utc_offset, utc_offset_dst, observes_dst. A ZIP on a zone line gives the zone covering most of it | +1 data layer (× base) | US + CA |
Timezone (October 2026, nothing removed): timezone stays one flat object with the fields it always had, and gains product and attribution; source now names the publisher of the time zone boundaries ("timezone-boundary-builder"), the same three values /v2/zip and /v2/address return. Field order: source, product, attribution, iana_name, name, abbreviation, abbreviation_dst, utc_offset, utc_offset_dst, observes_dst. A US ZIP (and a ZIP+4, by its parent ZIP) is answered from the ZIP's boundary: a ZIP crossing a zone line gives the zone covering the largest share of it (use /v2/zip for every zone with its share). Coordinates and ZIPs with only a centroid give the zone containing the point. Canadian codes keep their zone with the source /v2/zip gives Canadian codes; product and attribution are null. Show attribution wherever you display the data. Credits are unchanged.
Credit Cost
Quick ZIP credits = base × (1 + timezone + reverse_geocode). Base is 2 for ZIP+4 and 1 for ZIP, FSA, or Postal. Coordinates use the resolved type and add a reverse-geocode layer. ZIP+4 with timezone costs 4; coordinates resolving to ZIP+4 cost 4, or 6 with timezone. Unknown flags have no effect.
Response Fields
| Field | Type | Description |
|---|---|---|
country | string | "US" or "CA" |
code | string | The postal code |
code_type | string | ZIP, FSA, or Postal |
parent_code | string (optional) | Parent 5-digit ZIP or FSA (omitted for base codes) |
city | string | Primary city name |
state / state_name | string | State/province abbreviation and full name |
state_fips | string (optional) | 2-digit state FIPS code (US only) |
county / county_fips | string (optional) | County name and FIPS code (US only) |
multi_county | boolean (optional) | Whether ZIP spans multiple counties (US only) |
classification | string (optional) | ZIP type code (US only) |
classification_name | string (optional) | ZIP type name: Standard, PO Box, Military, Unique. USPS’s label for how mail is delivered: it does not mean the ZIP Code has residents, deliveries or an area of its own. |
municipality | string (optional) | Municipality name (where applicable) |
location | object | {"lat": ..., "lon": ...} |
detail | object (optional) | Street-level detail for ZIP+4 / CA postal codes |
timezone | object (optional) | Present when include=timezone: {source, product, attribution, iana_name, name, abbreviation, abbreviation_dst, utc_offset, utc_offset_dst, observes_dst}; for a ZIP crossing a zone line, the zone covering the largest share |
FALLBACK_USED notice. No additional credit is charged.ZIP+4 Detail Fields
When a ZIP+4 code is looked up, the detail object is populated with delivery-point data:
| Field | Type | Description |
|---|---|---|
zip4 | string | The 4-digit ZIP+4 suffix |
street[].record_type | string | USPS ZIP+4 record type name: Street, Highrise, Firm, PO Box, Rural Route or General Delivery |
street[].carrier_route | string | USPS carrier route code |
Street Detail Fields
Street-level address range information within the detail.street object:
| Field | Type | Description |
|---|---|---|
addr_low | string | Low end of primary address range |
addr_high | string | High end of primary address range |
prefix | string | Street name prefix directional (e.g., N, SW) |
name | string | Street name |
suffix | string | Street suffix (e.g., St, Ave, Blvd) |
postfix | string | Street name postfix directional |
odd_even | string | Address range parity: O (odd), E (even), B (both) |
sec_addr_abbr | string | Secondary address abbreviation (e.g., APT, STE, UNIT) |
sec_addr_low | string | Low end of secondary address range |
sec_addr_high | string | High end of secondary address range |
sec_addr_odd_even | string | Secondary address range parity: O, E, or B |
building_name | string | Building or firm name |
address | string | Formatted full address line |
Example Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/quick-zip?code=90210&include=timezone"
Example Response
{
"success": true,
"request": {
"code": "90210",
"include": [
"timezone"
]
},
"results": [
{
"code": "90210",
"code_type": "ZIP",
"country": "US",
"city": "Beverly Hills",
"state": "CA",
"state_name": "California",
"state_fips": "06",
"county": "Los Angeles",
"county_fips": "06037",
"classification": null,
"classification_name": "Standard",
"location": {
"lat": 34.103131,
"lon": -118.416253
},
"timezone": {
"source": "timezone-boundary-builder",
"product": "Time zone boundaries (with oceans)",
"attribution": "Time zone boundaries © OpenStreetMap contributors, timezone-boundary-builder (ODbL); time zone rules: IANA tz database",
"iana_name": "America/Los_Angeles",
"name": "Pacific",
"abbreviation": "PST",
"abbreviation_dst": "PDT",
"utc_offset": -8,
"utc_offset_dst": -7,
"observes_dst": true
},
"success": true
}
],
"meta": {
"version": "2.0.0",
"timing_ms": 8,
"credits": {
"used": 2,
"remaining": 856919,
"breakdown": {
"base": 1,
"enrichments": {
"timezone": 1
}
},
"source": "subscription"
},
"request_id": "01M4CRSJ0T2ZK0JDA9A0QFWJ8N"
}
}
{
"success": true,
"request": {
"code": "M5V",
"include": [
"timezone"
]
},
"results": [
{
"code": "M5V",
"code_type": "FSA",
"country": "CA",
"city": "Toronto",
"state": "ON",
"state_name": "Ontario",
"location": {
"lat": 43.638388,
"lon": -79.40428
},
"timezone": {
"source": "IANA Time Zone Database / timezone-boundary-builder",
"product": null,
"attribution": null,
"iana_name": "America/Toronto",
"name": "Eastern",
"abbreviation": "EST",
"abbreviation_dst": "EDT",
"utc_offset": -5,
"utc_offset_dst": -4,
"observes_dst": true
},
"success": true
}
],
"meta": {
"version": "2.0.0",
"timing_ms": 15,
"credits": {
"used": 2,
"remaining": 74998,
"breakdown": {
"base": 1,
"enrichments": {
"timezone": 1
}
},
"source": "subscription"
},
"request_id": "01M3SX2V9NY9MRRAMWVJG0C09E"
}
}
{
"success": true,
"request": {
"code": "99999"
},
"results": [
{
"success": false,
"input": {
"code": "99999"
},
"error": {
"code": "NOT_FOUND_ZIP",
"message": "ZIP code 99999 not found.",
"url": "https://www.zip-codes.com/api/"
}
}
],
"meta": {
"version": "2.0.0",
"timing_ms": 8,
"credits": {
"used": 1,
"remaining": 74999,
"breakdown": {
"base": 0,
"enrichments": {},
"note": "Not found (1 credit charged)."
},
"source": "subscription"
},
"request_id": "01M3SX2V9NY9MRRAMWVJG0C09E"
}
}
ZIP Code Details
Unknown include flags are ignored free with a VALIDATION_UNKNOWN_ENRICHMENT notice on this single endpoint. /v2/zip/batch ignores unknown flags without that notice.
Comprehensive postal code lookup with rich data including demographics, business statistics, delivery points, and optional enrichments and historical ACS profiles. Returns detailed information for US ZIP codes and Canadian postal codes.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Required | US ZIP code, Canadian postal code, or coordinates (lat,lon) |
include | string | Optional | Comma-separated enrichment flags (see Data Enrichments) |
Available Enrichments
| Flag | Adds to Response | Cost | Availability |
|---|---|---|---|
timezone | IANA timezone name, UTC offsets, DST info. Multi-zone ZIPs return array with % coverage. | +1 | US + CA |
census | County, county subdivision, place, tract, block group, CBSA overlaps with % coverage (US Census Bureau). On a Canadian code, a legacy alias of statcan: answered under statcan with an ENRICHMENT_FLAG_RENAMED notice | +1 | US only |
cd | Congressional districts with % coverage (US Census Bureau). On a Canadian code, a legacy alias of statcan (federal electoral districts are inside it), with an ENRICHMENT_FLAG_RENAMED notice | +1 | US only |
statcan | Statistics Canada geography for a Canadian postal code or FSA: census divisions and subdivisions, tracts, dissemination areas and blocks (postal codes), metro areas, population centres, economic regions, consolidated subdivisions, designated places, province, and federal electoral districts, under statcan.current (latest census, 2021). See Census Boundaries | +1 | CA only |
statcan_{YYYY} | An earlier Statistics Canada census under its "YYYY" key: statcan_2016, statcan_2011 (statcan_2021 is the same as statcan) | +1 each | CA only |
state_leg | State senate (upper) and house (lower) legislative district overlaps | +1 | US only |
census_{YYYY}, cd_{YYYY}, state_leg_{YYYY} | An earlier year’s boundaries (2010–2025) under its "YYYY" key beside current (for example census_2022, cd_2012). The bare flags return the latest year (2025). See Census Boundaries | +1 each | US only |
school_district | School districts (NCES) with office address, grade range, locale, area overlap | +1 | US only |
acs_demographic | Population, sex, age distribution, race, ethnicity, citizenship (DP05 — 108 fields) | +1 | US only |
acs_social | Households, education, language, ancestry, marital status, computers/internet (DP02 — 154 fields) | +1 | US only |
acs_economic | Employment, income, poverty, occupation, industry, health insurance, commute (DP03 — 137 fields) | +1 | US only |
acs_housing | Housing units, occupancy, tenure, home value, rent, utilities, structure age (DP04 — 143 fields) | +1 | US only |
acs_{profile}_{YYYY} | Historical ACS for any year from 2011–2024 (14 years). Append _YYYY to any ACS flag (e.g., acs_housing_2022). 528 fields in recent years, 394 in earliest. See ACS Data. | +1 each | US only |
demographics | Population, race, age, households, income, and business establishment counts | +1 | US only |
medicare | Medicare CBSA code/name/type, rating area ID, SSA state/county code | +1 | US only |
ENRICHMENT_UNAVAILABLE notice. No credit is charged. census, census_YYYY, cd and cd_YYYY on a Canadian code are answered under statcan with an ENRICHMENT_FLAG_RENAMED notice; statcan on a US code returns ENRICHMENT_UNAVAILABLE at no charge.Credit Cost
ZIP Details credits = 1 + billable flags + reverse_geocode. Base is 1 for every code type, including ZIP+4 and Postal. Canadian unavailable flags cost 0 with a notice. Example: include=timezone,census,cd on a ZIP = 4 credits. On a Canadian code, include=statcan,statcan_2016,timezone = 4 credits; federal electoral districts are inside statcan at no extra charge, and census/cd there are one statcan charge.
Example Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/zip?code=90210&include=timezone,demographics"
Example Response
{
"success": true,
"request": {
"code": "90210",
"include": [
"timezone",
"demographics"
]
},
"results": [
{
"country": "US",
"code": "90210",
"code_type": "ZIP",
"city": "Beverly Hills",
"state": "CA",
"state_name": "California",
"state_fips": "06",
"county": "Los Angeles",
"county_fips": "06037",
"multi_county": false,
"location": {
"lat": 34.102787,
"lon": -118.416019
},
"area_codes": [
"310",
"323",
"424",
"738"
],
"elevation": {
"ft": 719,
"m": 219
},
"metro": {
"region": "West",
"division": "Pacific",
"csa": "348",
"csa_name": "Los Angeles-Long Beach, CA",
"msa": "4472",
"msa_name": "Los Angeles-Riverside-Orange County, CA"
},
"zip_info": {
"classification": null,
"classification_name": "Standard",
"facility_code": "P",
"city_delivery_indicator": "Y",
"intro_date": "<2004-10",
"land_area_sq_mi": 10.748,
"water_area_sq_mi": 0.054,
"zip4_count": 3516,
"delivery_points": {
"total_active": 10643,
"business": 1472,
"residential": 9301,
"po_box": 0,
"single_family": 6786,
"multi_family": 2419
}
},
"demographics": {
"population": {
"total": 21134,
"estimate": 22973,
"male": 10099,
"female": 11035
},
"race": {
"white": 17130,
"black": 366,
"hispanic": 1296,
"asian": 1406,
"american_indian": 25,
"hawaiian_pacific_islander": 12,
"other": 481
},
"age": {
"median": 49.3,
"median_male": 49.3,
"median_female": 49.2
},
"households": {
"total": 8553,
"persons_per_household": 2.47,
"average_family_size": 3.13,
"median_income": 187801,
"average_home_value": 2000001
},
"business": {
"establishments": 2536,
"employees": 32353,
"payroll_q1": 648013000,
"payroll_annual": 2653591000
}
},
"timezone": {
"source": "timezone-boundary-builder",
"product": "Time zone boundaries (with oceans)",
"attribution": "Time zone boundaries © OpenStreetMap contributors, timezone-boundary-builder (ODbL); time zone rules: IANA tz database",
"zones": [
{
"iana_name": "America/Los_Angeles",
"name": "Pacific",
"abbreviation": "PST",
"abbreviation_dst": "PDT",
"utc_offset": -8,
"utc_offset_dst": -7,
"observes_dst": true,
"pct_of_zip": 100
}
]
},
"success": true
}
],
"meta": {
"version": "2.0.0",
"timing_ms": 10,
"credits": {
"used": 3,
"remaining": 856905,
"breakdown": {
"base": 1,
"enrichments": {
"timezone": 1,
"demographics": 1
}
},
"source": "subscription"
},
"request_id": "01M4CRSKYEX0XGT5H4VGGYQN6B"
}
}
Base Fields (no include flag required)
| Field | Type | Description |
|---|---|---|
country | string | "US" or "CA" |
code | string | The postal code |
code_type | string | ZIP, FSA, or Postal |
parent_code | string (optional) | Parent 5-digit ZIP (for ZIP+4 queries) or parent FSA (for CA postal queries). Omitted for base codes. |
city | string | Primary city name |
state / state_name | string | State/province abbreviation and full name |
state_fips | string (optional) | 2-digit state FIPS code (US only) |
county / county_fips | string | County name and FIPS code (US only) |
multi_county | boolean (optional) | Whether the ZIP spans multiple counties (US only) |
municipality | string (optional) | Municipality name (where applicable) |
location | object | {"lat": 34.10, "lon": -118.41}, the code’s centroid; lat and lon are null when it has no location (military ZIP Codes and a few PO Box and unique ZIP Codes) |
area_codes | string[] (optional) | Telephone area codes serving this postal code; null when none is known (military ZIP Codes) |
elevation | object (optional) | {"ft": 719, "m": 219} — centroid elevation in feet and meters (USGS for a US ZIP Code); both null when the code has no location |
metro | object (optional) | MSA/CBSA statistical area info (US only, see sub-fields below) |
zip_info | object (optional) | ZIP classification, facility code, land/water area, delivery points (US only). land_area_sq_mi and water_area_sq_mi are the Census ZIP Code Tabulation Area’s (Census gazetteer), the same basis as every Census block; both null when the ZIP Code has no ZCTA of its own (most PO Box, unique and military ZIP Codes), and water is 0 for a landlocked ZCTA |
postal_info | object (optional) | Canadian postal metadata: record type, address type, FSA stats (CA only) |
city_aliases | array (optional) | Alternative city names with mailing status and intro date |
detail | object (optional) | ZIP+4 / CA postal street-level overlay (omitted for 5-digit ZIP / FSA queries) |
metro Sub-Fields (US only)
| Field | Type | Description |
|---|---|---|
region | string (optional) | Census region (e.g., West, Northeast) |
division | string (optional) | Census division (e.g., Pacific, Middle Atlantic) |
csa / csa_name | string (optional) | Combined Statistical Area code and name |
msa / msa_name | string (optional) | Legacy Metropolitan Statistical Area code and name, from OMB’s 1999 definitions. OMB replaced MSAs with CBSAs in 2003, so these are not current metro areas: for the current CBSAs use the census flag’s metro_micro_cbsa_statistical_area. |
pmsa / pmsa_name | string (optional) | Legacy Primary MSA code and name (where applicable), from the same 1999 definitions |
postal_info Sub-Fields (CA only)
| Field | Type | Description |
|---|---|---|
record_type | string | Record type code |
address_type | string | Address type classification |
fsa | string | Parent FSA (3-character) |
delivery_postal_code | string | Delivery postal code |
fsa_population | number | FSA population |
fsa_dwellings | number | Total dwellings in FSA |
fsa_dwellings_occupied | number | Occupied dwellings in FSA |
fsa_postal_code_count | number | Number of postal codes in the FSA |
Radius Search
Find all postal codes within a given radius of a center point. Specify the center via a single code parameter (ZIP, FSA, Postal Code, or lat,lon coordinates). Cross-border searches include Canadian postal codes in centroid mode and FSAs in spatial mode. Supports donut searches (min radius) and result limiting.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Required | Center point — US ZIP (5-digit), ZIP+4, Canadian FSA, Canadian Postal Code, or lat,lon coordinates (e.g. 41.878,-87.629) |
max | number or string | Required | Maximum radius in miles (e.g. 25), or "auto" (density-based) / "auto-commute" (commute-behavior-based). Numeric max must be greater than zero: centroid up to 500, spatial up to 250. Free tier: 100 centroid / 50 spatial; test keys: 10 miles. See Auto Radius. |
min | number | Optional | Minimum radius in miles for donut search: nonnegative and less than max. Default: 0 |
include | string | Optional | Comma-separated enrichment flags (see table below) |
mode | string | Optional | centroid (default) — fast haversine on centroids, returns US ZIP + CA Postal. spatial — precise boundary analysis with pct_inside, returns US ZIP + CA FSA. |
limit | number | Optional | Positive integer maximum results; when it cuts the list, summary.total_count is the number returned and summary.total_available the number in the radius. ACS statistics cover the whole radius either way. Centroid mode caps at 10,000 with RESULTS_CAPPED. |
Available Enrichments
| Flag | Adds to Response | Cost | Availability |
|---|---|---|---|
timezone | IANA timezone name, UTC offsets, DST abbreviations for each result | +1 data layer (× base) | US + CA |
acs_demographic | Aggregate: population, sex, age, race, ethnicity (DP05) | +1 data layer (× base) | US only |
acs_social | Aggregate: households, education, language, ancestry (DP02) | +1 data layer (× base) | US only |
acs_economic | Aggregate: employment, income, poverty, commute (DP03) | +1 data layer (× base) | US only |
acs_housing | Aggregate: occupancy, tenure, home value, rent (DP04) | +1 data layer (× base) | US only |
acs_{profile}_{YYYY} | Historical ACS aggregate for any year from 2011–2024. Append _YYYY to any ACS flag (e.g., acs_economic_2019). See ACS Data for year-by-year coverage. | +1 layer each (× base) | US only |
census, cd, state_leg, school_district) are available on /v2/zip and /v2/address. If requested on /v2/radius, these flags are silently ignored — no credit is charged and an ENRICHMENT_UNAVAILABLE notice is included in the response. Use /v2/zip for territory enrichments on individual codes.Every radius search starts with a base cost determined by how far you're searching — bigger search = more work for the server.
Each enrichment you add (timezone, demographics, etc.) multiplies that base cost, because the API has to gather that data for every result in your radius.
Think of it as: “search cost × number of data layers”
Quick worked example
Searching 25 miles with timezone: base cost is 1 (short search) × 2 data layers (the search itself + timezone) = 2 credits.
Credit Cost
Centroid mode (default, faster): Wider bands — the first 100 miles costs 1 base credit, the next 100 costs 2, and so on up to 500 miles.
Spatial mode (precise boundaries): Narrower bands — the first 50 miles costs 1 base credit, the next 50 costs 2, and so on up to 250 miles.
Cost = base × (1 + distinct timezone/ACS flags + auto_radius); auto_radius is 1 for either auto option, otherwise 0.
Auto Radius
Instead of specifying a numeric radius, pass max=auto or max=auto-commute to let the API calculate an appropriate search radius for the center point. This accepts a ZIP, ZIP+4, FSA, Postal code, or lat/lon coordinate center.
| Mode | Based On | Range | Best For |
|---|---|---|---|
auto | Population density: 341 ÷ density0.42 | 3–150 miles | General-purpose searches scaled to urbanity |
auto-commute | ACS commuting patterns: mean travel time × transport-weighted speed × 1.5 | 3–100 miles | Service-area and labor-market analysis |
auto-commute automatically falls back to density-based auto and includes an AUTO_COMMUTE_FALLBACK notice in the response. The resolved radius appears in results[].search.max and the resolution method in results[].search.auto_mode ("density" or "commute").Credit cost: Using auto-radius counts as one additional data layer in the cost calculation. Example: max=auto with include=timezone = base × 3 data layers = 3 credits for a short-range search.
Example Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/radius?code=48226&max=25&include=timezone"
Example Response
{
"success": true,
"request": {
"code": "48226",
"max": "25",
"include": [
"timezone"
],
"mode": "centroid"
},
"results": [
{
"search": {
"center": {
"code": "48226",
"code_type": "ZIP",
"city": "Detroit",
"state": "MI",
"location": {
"lat": 42.331702,
"lon": -83.04962
}
},
"max": 25,
"min": 0,
"mode": "centroid"
},
"matches": [
{
"country": "US",
"code": "48226",
"code_type": "ZIP",
"city": "Detroit",
"state": "MI",
"county": "Wayne",
"county_fips": "26163",
"classification": null,
"classification_name": "Standard",
"location": {
"lat": 42.331654,
"lon": -83.049581
},
"distance_miles": 0,
"area_sq_mi": null,
"timezone": {
"source": "IANA Time Zone Database / timezone-boundary-builder",
"iana_name": "America/Detroit",
"name": "Eastern",
"abbreviation": "EST",
"abbreviation_dst": "EDT",
"utc_offset": -5,
"utc_offset_dst": -4,
"observes_dst": true
}
},
{
"country": "US",
"code": "48243",
"code_type": "ZIP",
"city": "Detroit",
"state": "MI",
"county": "Wayne",
"county_fips": "26163",
"classification": null,
"classification_name": "Standard",
"location": {
"lat": 42.329064,
"lon": -83.039647
},
"distance_miles": 0.54,
"timezone": {
"source": "IANA Time Zone Database / timezone-boundary-builder",
"iana_name": "America/Detroit",
"name": "Eastern",
"abbreviation": "EST",
"abbreviation_dst": "EDT",
"utc_offset": -5,
"utc_offset_dst": -4,
"observes_dst": true
}
}
],
"success": true
}
],
"meta": {
"version": "2.0.0",
"timing_ms": 31,
"credits": {
"used": 2,
"remaining": 856903,
"breakdown": {
"base": 1,
"enrichments": {
"timezone": 1
},
"calculation": "1 x (1 + 1) = 2"
},
"source": "subscription"
},
"request_id": "01M4CRSM8HPQJ7ARP5Z92CMH4S"
}
}
The radius summary contains total_count, us_zip_count, ca_fsa_count and ca_postal_count, which count the matches returned, and total_available, the number of matches in the radius, present when limit cut the list. ACS statistics (stats.acs) always cover every match in the radius, whatever the limit. They carry est and pct only, with no moe or column: Census publishes no margin of error for a combined area. For one ZIP Code’s estimate with its margin of error, use /v2/zip. A coverage_pct under 100 is normal (often 40–60 in cities): PO Box, unique and other point ZIP Codes have no area of their own, so Census publishes no ACS data for them (zips_without_acs_data), and their residents are counted in the surrounding ZIP Codes, so the totals are not short. Examples are trimmed.
Search Fields
| Field | Type | Description |
|---|---|---|
search.center.code | string | Resolved center postal code |
search.center.code_type | string | ZIP, FSA, or Postal |
search.center.city | string | Center city name |
search.center.state | string | Center state/province abbreviation |
search.center.location | object | {"lat": ..., "lon": ...} — center coordinates |
search.max | number | Resolved maximum radius in miles (numeric value, even when auto was requested) |
search.min | number | Minimum radius in miles (donut inner ring) |
search.mode | string | "centroid" or "spatial" |
search.auto_mode | string (optional) | "density" when max=auto, "commute" when max=auto-commute, omitted for numeric radius |
Match Fields
| Field | Type | Description |
|---|---|---|
country | string | "US" or "CA" |
code | string | Postal code |
code_type | string | ZIP, FSA, or Postal |
city | string | City name |
state | string | State/province abbreviation |
county | string (optional) | County name (US only) |
county_fips | string (optional) | County FIPS code (US only) |
classification | string (optional) | USPS ZIP Code type: P (PO Box) or U (unique); null for a standard ZIP Code and for Canada |
classification_name | string (optional) | ZIP type name: Standard, PO Box, Unique. USPS’s label for how mail is delivered: it does not mean the ZIP Code has residents, deliveries or an area of its own. |
location | object | {"lat": ..., "lon": ...} |
distance_miles | number | Distance from center point in miles |
pct_inside | number (optional) | Spatial mode: the percentage of the postal code’s area inside the search radius, to 2 decimals. A code whose overlap rounds to 0.00 is not a match, so every spatial match is at least 0.01. null for a code with no area of its own (area_sq_mi 0) and for every match in centroid mode. |
area_sq_mi | number (optional) | Spatial mode: total area of the postal code in square miles, to 2 decimals; 0 for a code with no area of its own (a point). null in centroid mode. |
Distance Calculation
Calculate the great-circle distance between two points. Supports any combination of ZIP codes, ZIP+4 codes, FSAs, full Canadian postal codes, and lat/lon coordinates. Timezone data is always included at no extra cost. Each endpoint in the response includes an input_type field ("code" or "coordinates") indicating how the point was resolved.
Timezone (October 2026, nothing removed): each point's timezone stays one flat object with the fields it always had, and gains product and attribution; source now names the publisher ("timezone-boundary-builder"), the same three values /v2/zip returns. Field order: source, product, attribution, iana_name, name, abbreviation, abbreviation_dst, utc_offset, utc_offset_dst, observes_dst. It is the zone containing the point, from the time zone boundary file (timezone-boundary-builder, newest release). Canadian points keep their zone with /v2/zip's Canadian source; product and attribution are null.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
from | string | Required | Origin point. Accepts: US ZIP (90210), ZIP+4 (90210-1234), Canadian FSA (M5V), Canadian postal code (M5V2H1), or lat,lon coordinates (34.103,-118.410) |
to | string | Required | Destination point. Accepts: US ZIP (90210), ZIP+4 (90210-1234), Canadian FSA (M5V), Canadian postal code (M5V2H1), or lat,lon coordinates (40.758,-73.986) |
from=90210-1234&to=40.758,-73.9855Credit Cost
Fixed 1 credit per pair. Timezone is always included at no extra cost.
Calculate your costExample Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/distance?from=90210&to=10001"
Example Response
{
"success": true,
"request": {
"from": "90210",
"to": "10001"
},
"results": [
{
"from": {
"country": "US",
"code": "90210",
"code_type": "ZIP",
"city": "Beverly Hills",
"state": "CA",
"county": "Los Angeles",
"location": {
"lat": 34.102776,
"lon": -118.416124
},
"timezone": {
"source": "timezone-boundary-builder",
"product": "Time zone boundaries (with oceans)",
"attribution": "Time zone boundaries © OpenStreetMap contributors, timezone-boundary-builder (ODbL); time zone rules: IANA tz database",
"iana_name": "America/Los_Angeles",
"name": "Pacific",
"abbreviation": "PST",
"abbreviation_dst": "PDT",
"utc_offset": -8,
"utc_offset_dst": -7,
"observes_dst": true
},
"input_type": "code"
},
"to": {
"country": "US",
"code": "10001",
"code_type": "ZIP",
"city": "New York",
"state": "NY",
"county": "New York",
"location": {
"lat": 40.750189,
"lon": -74.000337
},
"timezone": {
"source": "timezone-boundary-builder",
"product": "Time zone boundaries (with oceans)",
"attribution": "Time zone boundaries © OpenStreetMap contributors, timezone-boundary-builder (ODbL); time zone rules: IANA tz database",
"iana_name": "America/New_York",
"name": "Eastern",
"abbreviation": "EST",
"abbreviation_dst": "EDT",
"utc_offset": -5,
"utc_offset_dst": -4,
"observes_dst": true
},
"input_type": "code"
},
"distance": {
"miles": 2453.3,
"km": 3948.15
},
"bearing": 65.9,
"success": true
}
],
"meta": {
"version": "2.0.0",
"timing_ms": 6,
"credits": {
"used": 1,
"remaining": 856902,
"breakdown": {
"base": 1,
"enrichments": {}
},
"source": "subscription"
},
"request_id": "01M4CRSMQEFVH34GD8XJPVPW0J"
}
}
Health Check
Returns API and database health status. No authentication required.
Three-state health: healthy (normal), degraded (DB latency >500ms), unhealthy (DB unreachable, returns HTTP 503).
ready is true once the server has finished loading after a start or restart; until then loading lists what is still loading (radius_centroid, radius_spatial, suggest, acs_cache, address, coordinates) and /v2/radius, /v2/radius/batch and /v2/suggest answer 503 SERVICE_WARMING_UP until what they need has loaded. The status code stays 200 while loading.
{
"success": true,
"data": {
"status": "healthy",
"database": {
"connected": true,
"latencyMs": 3
},
"version": "2.0.0",
"ready": true,
"loading": []
},
"meta": {
"version": "2.0.0",
"timing_ms": 3
}
}
Purchase Credits
Buys one credit pack, paid with an MPP wallet: for anyone with one, an agent or a person. With an API key the pack is added to that key's account, and a prepaid card's key reloads the card; sent without any key, it makes a new prepaid card. How paying works (the 402 challenge, the wallet, the Payment-Authorization header) is under Buying Credits with a Wallet (MPP).
Parameters
A JSON body naming one agent pack in pricing.json (its available_to lists agents; agent packs start at $5). Send at least one of the three fields; sent together they must name the same pack, and a value two packs share is refused with the matches listed.
| Parameter | Type | Required | Description |
|---|---|---|---|
pack | string | One of three | The pack's name as pricing.json lists it, case-insensitive ("mini" = "Mini") |
price_usd | number | One of three | The pack's price in US dollars |
credits | integer | One of three | The pack's credits |
Payment-Authorization | header | Optional | The paid MPP credential (Payment followed by its base64url token, shown below), sent on the retry after a 402 PAYMENT_REQUIRED. Authorization: Payment … (the MPP default) is read the same way; a caller paying with a key then sends the key in X-Api-Key. Without a credential the call answers that 402 with the challenge. |
X-Api-Key | header | Optional | The account (or prepaid card) the pack is for; Authorization: Bearer works too. Without any key, the purchase makes a new prepaid card. |
Credit Cost
0 credits. The payment is the pack's price; a successful answer carries a Payment-Receipt header. Credits are added only after the payment completes (a card clears at once; some methods take longer), and purchased credits never expire.
Response Fields
results[0] is the purchase, in its current state. GET /v2/purchases/{id} answers the same object.
| Field | Type | Description |
|---|---|---|
purchase_id | string | The order number: PP- (bought through the API) or ZC- (bought on the website). An opaque string; its length may grow. |
status | string | processing (still clearing, no credits yet), succeeded, failed (nothing charged), refunded or disputed |
api_key | string (optional) | The new prepaid card's key; present only on a purchase made without a key. The same payment sent again returns it again while the card is unused, for up to 24 hours after the payment settles. Keep it secret: whoever holds it can use the card. |
pack | object | The pack bought, as it was at purchase time: name, credits, price_usd |
charged | object | amount_usd; method is stripe (card or Link; new payment methods add values); reference is the Stripe PaymentIntent id (pi_...), null until the payment reached Stripe; network, asset and transaction are null for a Stripe payment. Whether the money was taken is status. |
credits | object | added (0 until the purchase succeeds); removed (unspent credits taken back after a refund or dispute; absent until then); purchased_remaining (every purchased credit left on the account or card, excluding free and subscription credits) |
receipt_url | string or null | Stripe's hosted receipt; null until the payment succeeds |
created | string | When the purchase was made (UTC) |
settled | string or null | When it succeeded or failed (UTC); null while processing |
terms | string | The Terms of Service the payment accepted |
Answers and Errors
| Status | Meaning |
|---|---|
| 201 | The payment succeeded and the credits were added (a new prepaid card's key in api_key). A Payment-Receipt header comes with it. |
| 202 | The payment is still clearing (status processing, no credits yet; a new card's key comes with it). Keep purchase_id and read GET /v2/purchases/{id}; never buy again. |
| 200 | The same credential again: the purchase as it stands. Nothing is charged twice. |
| 400 | VALIDATION_MISSING_PARAM (no pack, price_usd or credits), PACK_NOT_AVAILABLE (no agent pack matches, more than one does, or the paid challenge is for another pack). Nothing is charged. |
| 402 | PAYMENT_REQUIRED (no Payment-Authorization header), PAYMENT_FAILED (declined, nothing charged; a new challenge comes with it, except for a card declined twice today), PAYMENT_CREDENTIAL_INVALID (not our challenge, changed or expired; a new challenge comes with it), PAYMENT_PENDING (a purchase for the account or card is still clearing; error.payment.purchase_id names it). A data request answers PAYMENT_PENDING too while a purchase is clearing. |
| 403 | PURCHASES_DISABLED (the account owner switched API purchases off), PURCHASES_ON_HOLD (purchases paused for this account, until a time or until reviewed), AUTH_TEST_KEY_RESTRICTED (test and demo keys cannot buy). |
| 429 | RATE_LIMIT_MINUTE: more than 5 payment attempts a minute per key (20 per IP). Retry-After gives the seconds. |
| 503 | SERVICE_UNAVAILABLE: purchases through the API are paused (Retry-After: 300), or the account or card was busy with another payment (Retry-After: 5). Nothing is charged. |
Example Request
First without a Payment-Authorization header, to get the challenge:
curl -X POST -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"pack":"Mini"}' "https://api.zip-codes.com/v2/purchase"
It answers 402 PAYMENT_REQUIRED with a WWW-Authenticate: Payment challenge header and the offer in error.payment:
{
"success": false,
"request": {
"pack": "Mini"
},
"error": {
"code": "PAYMENT_REQUIRED",
"message": "Payment required: $5.00 buys the Mini pack (6,250 credits). Pay the MPP challenge in the WWW-Authenticate header and send this same request again with a Payment-Authorization header.",
"payment": {
"offer": {
"name": "Mini",
"credits": 6250,
"price_usd": 5
},
"methods": [
{
"method": "stripe",
"challenge_id": "...",
"pays_with": [
"card",
"link"
],
"expires": "2026-10-31T18:09:12Z"
}
],
"purchase_url": "https://api.zip-codes.com/v2/purchase",
"packs": "https://api.zip-codes.com/pricing.json",
"terms": "https://www.zip-codes.com/api/tos"
}
},
"meta": {
"version": "2.2.0",
"timing_ms": 3
}
}
Pay the challenge with your wallet, then send the same request again with the credential. The credential is Payment followed by the base64url (no padding) of this JSON: the challenge's parameters exactly as the WWW-Authenticate header gave them, and the wallet's shared payment token (spt_…, limited to the challenge's amount):
{
"challenge": {
"id": "from the challenge",
"realm": "api.zip-codes.com",
"method": "stripe",
"intent": "charge",
"request": "from the challenge, unchanged",
"expires": "from the challenge"
},
"payload": {
"spt": "spt_..."
}
}
Send it in Payment-Authorization (the header the challenge names) or in Authorization. A new prepaid card's key starts pp_card_; account keys start zc_live_.
curl -X POST -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-H "Payment-Authorization: Payment eyJ..." \
-d '{"pack":"Mini"}' "https://api.zip-codes.com/v2/purchase"
Example Response
{
"success": true,
"request": {
"pack": "Mini"
},
"results": [
{
"purchase_id": "PP-G2TH5-EKHBD",
"status": "succeeded",
"pack": {
"name": "Mini",
"credits": 6250,
"price_usd": 5
},
"charged": {
"amount_usd": 5,
"method": "stripe",
"network": null,
"asset": null,
"reference": "pi_...",
"transaction": null
},
"credits": {
"added": 6250,
"purchased_remaining": 6250
},
"receipt_url": "https://...",
"created": "2026-10-31T18:04:12Z",
"settled": "2026-10-31T18:04:13Z",
"terms": "https://www.zip-codes.com/api/tos"
}
],
"meta": {
"version": "2.2.0",
"timing_ms": 912
}
}
Balance
Reads the credit pools of this key's account or prepaid card: what it can spend now. A request draws on free or subscription first, then purchased.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
X-Api-Key | header | Required | The account's or prepaid card's key, in a header (X-Api-Key or Authorization: Bearer), never ?key= |
Credit Cost
0 credits. Limit: 10 a minute per key, shared with GET /v2/purchases/{id}.
Response Fields
results[0] = {free, subscription, purchased, purchase_url, packs}. The pool names are meta.credits.source's words.
| Field | Type | Description |
|---|---|---|
free | object or null | The free daily allowance: remaining, limit (credits per UTC day), resets (the next UTC midnight); null for a subscriber or a prepaid card |
subscription | object or null | The current billing period: plan, remaining (may exceed limit after a plan change), limit, resets (the period's end, UTC; null when not known); null without a subscription (always for a prepaid card) |
purchased | object | Pack credits: remaining, expires (null: purchased credits never expire) |
purchase_url | string | Where to buy a pack through the API (POST /v2/purchase) |
packs | string | The packs on sale, their credits and prices (pricing.json) |
Errors
| Status | Meaning |
|---|---|
| 401 | AUTH_MISSING_KEY or AUTH_INVALID_KEY, or the key sent as ?key=: this call takes the key in a header. |
| 403 | AUTH_TEST_KEY_RESTRICTED: test, demo and admin keys cannot read a balance. |
| 429 | RATE_LIMIT_MINUTE: more than 10 lookups a minute for this key (balance and purchase lookups together). Retry-After gives the seconds. |
Example Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/balance"
Example Response
{
"success": true,
"results": [
{
"free": {
"remaining": 2380,
"limit": 2500,
"resets": "2026-11-01T00:00:00Z"
},
"subscription": null,
"purchased": {
"remaining": 6250,
"expires": null
},
"purchase_url": "https://api.zip-codes.com/v2/purchase",
"packs": "https://api.zip-codes.com/pricing.json"
}
],
"meta": {
"version": "2.2.0",
"timing_ms": 4
}
}
Purchase Lookup
Reads one purchase on this key's account or prepaid card by its order number, in its current state. Use it to follow a 202 from POST /v2/purchase until status reads succeeded.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string (path) | Required | The order number as purchase_id returned it, e.g. PP-G2TH5-EKHBD (PP- through the API, ZC- on the website; dashes and case are forgiven; at most 32 characters) |
X-Api-Key | header | Required | The account's or prepaid card's key, in a header (X-Api-Key or Authorization: Bearer), never ?key= |
Credit Cost
0 credits. Limit: 10 a minute per key, shared with GET /v2/balance; after 20 unknown purchase ids in an hour, purchase lookups answer 429 until the hour ends.
Response Fields
results[0] is the purchase, the same object POST /v2/purchase answers (see its response fields); api_key is never included here.
Errors
| Status | Meaning |
|---|---|
| 401 | AUTH_MISSING_KEY or AUTH_INVALID_KEY, or the key sent as ?key=: this call takes the key in a header. |
| 403 | AUTH_TEST_KEY_RESTRICTED: test, demo and admin keys cannot read a purchase. |
| 404 | PURCHASE_NOT_FOUND: no purchase with this id on this key's account or prepaid card (another account's or card's purchase answers the same). |
| 429 | RATE_LIMIT_MINUTE: more than 10 lookups a minute for this key (purchase lookups and balance together), or 20 unknown purchase ids in an hour (Retry-After until the hour ends). |
Example Request
curl -H "X-Api-Key: YOUR_API_KEY" \ "https://api.zip-codes.com/v2/purchases/PP-G2TH5-EKHBD"
Example Response
{
"success": true,
"results": [
{
"purchase_id": "PP-G2TH5-EKHBD",
"status": "processing",
"pack": {
"name": "Mini",
"credits": 6250,
"price_usd": 5
},
"charged": {
"amount_usd": 5,
"method": "stripe",
"network": null,
"asset": null,
"reference": "pi_...",
"transaction": null
},
"credits": {
"added": 0,
"purchased_remaining": 0
},
"receipt_url": null,
"created": "2026-10-31T18:04:12Z",
"settled": null,
"terms": "https://www.zip-codes.com/api/tos"
}
],
"meta": {
"version": "2.2.0",
"timing_ms": 5
}
}
Batch Requests
Five routes support batch processing via POST: quick-zip, zip, radius, distance, and address. Address accepts 250 items; the other four accept 100 items. There is no suggest batch: POST /v2/suggest is a single call. Individual items can succeed or fail independently (the overall HTTP status is 200 even if some items fail).
AUTH_BATCH_REQUIRES_SUBSCRIPTION error.Each batch request uses its route-specific array (codes, searches, pairs, or addresses); request echoes the corresponding count. Failed items contain {success:false,input:{...},error:{code,message,url}}. Credit totals include source, remaining, and breakdown.
Batch Response Meta
{
"meta": {
"batch": {
"requested": 3,
"succeeded": 2,
"failed": 1
},
"credits": {
"used": 5,
"source": "subscription",
"remaining": 74995,
"breakdown": {
"base": 0,
"enrichments": {},
"note": "2 codes, 5 total credits"
}
},
"request_id": "01M3SX2V9NY9MRRAMWVJG0C09E",
"version": "2.0.0",
"timing_ms": 1
}
}
BATCH_DUPLICATES_DETECTED notice. Credits are reserved up front, unused credits are refunded, and disconnect/timeout refunds all credits.Batch Endpoints
| Endpoint | URL | Body Format |
|---|---|---|
| Quick ZIP | POST /v2/quick-zip/batch |
{"codes": ["90210", "M5V"], "include": "timezone"} |
| ZIP Details | POST /v2/zip/batch |
{"codes": ["90210", "10001"], "include": "timezone,census"} |
| Radius | POST /v2/radius/batch |
{"searches": [{"code": "90210", "max": 25}]} |
| Distance | POST /v2/distance/batch |
{"pairs": [{"from": "90210", "to": "10001"}]} |
| Address (250 max) | POST /v2/address/batch | {"addresses":["1600 Pennsylvania Ave NW, Washington DC 20500"],"include":["coordinates","acs_tract_economic"]} |
All other routes allow 100 items. include accepts a comma-separated string or array of up to 50 strings. Radius searches accept {code,max,min,include,limit}; top-level include, max, and min supply defaults overridden by per-search values. mode is batch-level only; default centroid, while spatial returns HTTP 400 SPATIAL_MODE_DISABLED_BATCH. Radius item input is {index,code,max}. Missing body/fields and oversized batches return HTTP 400; subscription/test-key restrictions 403; insufficient credits 402. Valid batches return HTTP 200.
Example: Quick ZIP Batch
curl -X POST "https://api.zip-codes.com/v2/quick-zip/batch" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"codes": ["90210", "M5V", "99999"], "include": "timezone"}'
Partial Failure
When some items fail, the overall HTTP status is still 200. Failed items appear in the results array with success: false and a per-item error object. The meta.batch summary tells you how many succeeded and failed:
{
"success": true,
"results": [
{
"success": true,
"code": "90210",
"city": "Beverly Hills",
"state": "CA",
"input": {
"code": "90210"
}
},
{
"success": true,
"code": "M5V",
"city": "Toronto",
"state": "ON",
"input": {
"code": "M5V"
}
},
{
"success": false,
"error": {
"code": "NOT_FOUND_ZIP",
"message": "Code 99999 not found.",
"url": "https://www.zip-codes.com/api/"
},
"input": {
"code": "99999"
}
}
],
"meta": {
"batch": {
"requested": 3,
"succeeded": 2,
"failed": 1
},
"credits": {
"used": 5,
"source": "subscription",
"remaining": 74995,
"breakdown": {
"base": 0,
"enrichments": {},
"note": "2 codes, 5 total credits"
}
},
"request_id": "01M3SX2V9NY9MRRAMWVJG0C09E",
"version": "2.0.0",
"timing_ms": 1
},
"request": {
"codes_count": 3,
"include": "timezone"
}
}
Data Enrichments
All enrichments are opt-in. Add them to the include parameter as a comma-separated list. On /zip each billable flag adds 1 credit; quick-zip and radius multiply their base by the number of data layers. Address charges requested coordinates plus delivered boundary flags and tract tags. See Credit Costs.
US ZIP code boundaries come from a commercial boundary provider, updated quarterly. The dataset includes ~33,400 polygon boundaries plus ~7,600 point-only entries (PO Box, Military, and Unique ZIP types that have no geographic area).
Census geographies (counties, tracts, places, etc.) are drawn independently of ZIP codes and almost never align. Where they overlap, each result includes two percentages: pct_of_zip and pct_of_area.
Understanding pct_of_zip vs. pct_of_area
pct_of_zip = intersection area ÷ ZIP area — "What percentage of this ZIP does the geography cover?" It is a share of area, not of population: if a county covers 60% of a ZIP, the share of the ZIP's residents living there can be higher or lower, since people are not spread evenly. Overlaps under a small sliver threshold are left out, so a ZIP's rows can add up to a little under 100.
pct_of_area = intersection area ÷ the part of the geography that ZIP Code polygons cover — "What percentage of the geography falls within this ZIP?" Open water and land outside every ZIP Code are not counted, so a geography's shares across all its ZIP Codes add up to about 100. Use this to gauge significance: a large county may overlap a ZIP but that ZIP is only a tiny sliver of the county. It is null for a geography with no land or less than 1% covered by ZIP Codes (mostly water). School districts use the district's total area instead.
Example: A county covers 40% of a ZIP (pct_of_zip = 40.0), but that overlap is only 2% of the county (pct_of_area = 2.0). The county is meaningful to the ZIP, but the ZIP is insignificant to the county.
shared_area_sq_mi is the overlap itself in square miles. land_area_sq_mi and water_area_sq_mi are the whole geography's Census land and water areas, not the part inside the ZIP. Both percentages and the shared area are null for a point (an address, coordinates, or a ZIP Code answered from its point).
Canadian FSAs (Forward Sortation Areas, ~1,643): The first 3 characters of a postal code. Polygon boundaries from Statistics Canada, updated with each census cycle (every 5 years).
Full Canadian postal codes (~876,000): All 6 characters. Point coordinates only — Canada Post provides centroids but postal code polygon boundaries are not publicly available. Included in /v2/radius centroid mode (default). Not available in spatial mode.
US ZIP+4 (~40M codes): Uses the ZIP+4 centroid when available. ZIP+4 areas change monthly with carrier route reassignment and have no stable polygon boundary.
| Flag | Description | Endpoints | Added cost | Availability |
|---|---|---|---|---|
timezone | IANA timezone with DST info | Quick ZIP, ZIP, Radius, Address | One data layer (× endpoint base) | US + CA |
census | Census boundary overlaps (US Census Bureau; on a Canadian code, answered as statcan) | ZIP, Address | +1 | US only |
cd | Congressional districts (US Census Bureau; on a Canadian code, answered as statcan) | ZIP, Address | +1 | US only |
statcan, statcan_{YYYY} | Statistics Canada geography and federal electoral districts (2021; statcan_2016, statcan_2011) | ZIP | +1 each | CA only |
state_leg | State legislature districts | ZIP, Address | +1 | US only |
census_{YYYY}, cd_{YYYY}, state_leg_{YYYY} | Boundary history: that year’s census, congressional or state legislative boundaries, 2010–2025 (the bare flags return 2026) | ZIP, Address | +1 each | US only |
school_district | School district boundaries (NCES) | ZIP, Address | +1 | US only |
acs_demographic | Population, age, sex, race (DP05) | ZIP, Radius | One data layer (× endpoint base) | US only |
acs_social | Households, education, language (DP02) | ZIP, Radius | One data layer (× endpoint base) | US only |
acs_economic | Employment, income, industry (DP03) | ZIP, Radius | One data layer (× endpoint base) | US only |
acs_housing | Housing units, occupancy, value (DP04) | ZIP, Radius | One data layer (× endpoint base) | US only |
acs_{profile}_{YYYY} | Historical ACS for years 2011–2024. Append year to any ACS flag (e.g., acs_housing_2022). 4 profiles × 14 years = 56 flags. See ACS Data for year-by-year coverage. | ZIP, Radius | One layer per tag (× endpoint base) | US only |
demographics | Population, race, age, households, income, business | ZIP | +1 | US only |
medicare | Medicare CBSA, rating area, SSA code | ZIP | +1 | US only |
coordinates | ZIP+4 point, latitude/longitude (6 decimals) | Address | +1 requested | US |
acs_tract_{demographic|social|economic|housing}[_YYYY] | Containing-tract ACS, 2011–2024; no suffix = 2024 | Address | +1 per delivered tag | US |
ENRICHMENT_UNAVAILABLE notice. No credit is charged for unavailable enrichments. census and cd (and their _YYYY forms) on a Canadian code are answered under statcan with an ENRICHMENT_FLAG_RENAMED notice.Timezone
Returns IANA timezone data including standard and daylight saving abbreviations and UTC offsets. On /v2/zip and /v2/address (and their batches) it is one block: source, product, attribution and a zones array with each zone's share of the ZIP (pct_of_zip; null for a point: an address, coordinates, or a ZIP with only a centroid). /v2/quick-zip and /v2/distance (and their batches) return one flat object: the same source, product and attribution, then one zone's fields (the zone covering most of the ZIP, or containing the point). On /v2/radius, timezone is a flat object per match.
Fields Returned
| Field | Type | Description |
|---|---|---|
source | string | Data source (publisher) |
product | string | The data product (US blocks; see Spatial layers: full provenance) |
attribution | string | The credit line to show with the data (US blocks) |
iana_name | string | IANA timezone identifier (e.g., America/Los_Angeles) |
name | string | Display name (e.g., Pacific Standard Time) |
abbreviation | string | Standard time abbreviation (e.g., PST) |
abbreviation_dst | string | Daylight saving abbreviation (e.g., PDT) |
utc_offset | number | Standard UTC offset in hours (e.g., -8) |
utc_offset_dst | number | DST UTC offset in hours (e.g., -7) |
observes_dst | boolean | Whether this timezone observes daylight saving time |
pct_of_zip | number or null | Percentage of the ZIP's area in this timezone (1 decimal; zones under 1% are left out); null for a point |
/v2/zip and /v2/address, the timezone object contains a zones[] array (sorted by coverage descending); there is no primary shortcut. On /v2/quick-zip, /v2/radius and /v2/distance, a single flat timezone object is returned.{
"source": "timezone-boundary-builder",
"product": "Time zone boundaries (with oceans)",
"attribution": "Time zone boundaries © OpenStreetMap contributors, timezone-boundary-builder (ODbL); time zone rules: IANA tz database",
"zones": [
{
"iana_name": "America/Los_Angeles",
"name": "Pacific",
"abbreviation": "PST",
"abbreviation_dst": "PDT",
"utc_offset": -8,
"utc_offset_dst": -7,
"observes_dst": true,
"pct_of_zip": 100.0
}
]
}
Census Boundaries
Canadian codes return Statistics Canada geography under statcan (flag statcan; census, census_YYYY, cd and cd_YYYY on a Canadian code are legacy aliases answered there with an ENRICHMENT_FLAG_RENAMED notice). statcan.current is Statistics Canada’s latest census (2021; statcan_2016 and statcan_2011 add "2016" / "2011"), opening with source, product, attribution, year and vintage. Postal-code periods list tract, census_division, census_subdivision, dissemination_area, dissemination_block, metro_area, population_centre, economic_region, consolidated_subdivision, designated_place, province and federal_electoral_district (the areas containing the postal code’s location); FSA periods list census_division, census_subdivision, tract, metro_area, economic_region, population_centre, consolidated_subdivision, designated_place, province and federal_electoral_district.
Postal codes and FSAs return the same row, in this order: uid, dguid (null for 2016 and 2011, whose files have none), name, name_en and name_fr (federal electoral districts and provinces: name is Statistics Canada’s bilingual name), type and type_description (census divisions, subdivisions, metro areas, population centres), class and class_description (population centres), area_sq_mi and area_km2 (the area’s total area, water included), land_area_sq_mi and land_area_km2 (the area’s land area, every census year; for 2016 and 2011 summed from Statistics Canada’s dissemination-block land areas in the Geographic Attribute File), then pct_of_zip (postal code: always 100) or pct_of_fsa (FSA: share of the FSA), and pct_of_area (FSA: share of the area; null for a postal code). pct_of_fsa and pct_of_area are null for an FSA without a Statistics Canada polygon.
"statcan": {
"current": {
"source": "Statistics Canada", "product": "...", "attribution": "...",
"year": 2021, "vintage": "2021",
"census_subdivision": [{
"uid": "3506008", "dguid": "2021A00053506008", "name": "Ottawa",
"type": "CV", "type_description": "City / Ville",
"area_sq_mi": 1116.67, "area_km2": 2892.15,
"land_area_sq_mi": 1076.53, "land_area_km2": 2788.2,
"pct_of_zip": 100, "pct_of_area": null
}],
"federal_electoral_district": [{
"uid": "35075", "dguid": "2013A000435075", "name": "Ottawa Centre / Ottawa-Centre",
"name_en": "Ottawa Centre", "name_fr": "Ottawa-Centre",
"area_sq_mi": 14.42, "area_km2": 37.35,
"land_area_sq_mi": 12.52, "land_area_km2": 32.43,
"pct_of_zip": 100, "pct_of_area": null
}]
},
"2016": { ... }
}
US sections are under results[i].census.current: county, county_subdivision, place, tract, block_group, metro_micro_cbsa_statistical_area and metropolitan_cbsa_division are arrays, largest pct_of_zip first. Empty lookups are null.
Boundary families. Each boundary flag returns its own top-level family: census, congressional_district (cd), state_legislative (state_leg), and school_district; Canadian codes return one family, statcan, with federal_electoral_district inside each period. Each family holds its latest year under current; census_YYYY, cd_YYYY and state_leg_YYYY (2010–2025) add that year under its own "YYYY" key beside current, newest first (school_district serves current only). A requested family with nothing found is present with current: null; an unrequested family is absent.
Returns all census geographic boundaries that overlap the ZIP code with percentage overlap calculations. Data sourced from the U.S. Census Bureau’s full-resolution TIGER/Line Shapefiles.
Census boundaries are hierarchical areas defined for statistical reporting — they are not drawn to follow ZIP code boundaries. A single ZIP almost always overlaps multiple tracts, places, and other geographies.
FIPS code structure and geography details
| Geography | FIPS Length | Example |
|---|---|---|
| State | 2 digits | 06 (California) |
| County | 5 digits | 06037 (Los Angeles Co.) |
| County Subdivision | 10 digits | 0603765000 |
| Place | 7 digits | 0644000 (Los Angeles city) |
| Census Tract | 11 digits | 06037264000 |
| Block Group | 12 digits | 060372640001 |
| Congressional District | 4 digits | 0634 |
Counties (~3,200): Primary legal division of every state. Connecticut replaced its 8 counties with 9 Planning Regions in 2022 (Census Bureau adopted these starting with 2024 data). Louisiana has parishes, Alaska has boroughs, and some cities in Virginia and Maryland are independent of any county.
County Subdivisions: Minor Civil Divisions (MCDs) in ~29 states represent legal townships and towns. In other states, Census-defined County Divisions (CCDs) serve as statistical equivalents.
Places: Incorporated places (cities, towns, villages with legal boundaries) and Census Designated Places (CDPs — statistical areas for unincorporated communities).
Census Tracts (~85,000): Designed for 2,500–8,000 people with socioeconomic homogeneity. The tract code has an implied decimal: 046300 = tract 463.00.
Block Groups: The smallest geography with ACS data, containing 600–3,000 people. GEOID = parent tract code + 1 digit.
CBSAs: Metropolitan Statistical Areas (core city 50K+ population) or Micropolitan (10K–50K). Rural areas have no CBSA. Metropolitan Divisions subdivide the largest metros into component areas.
Included Boundary Types
| Boundary | Description |
|---|---|
county | County boundaries with FIPS codes |
county_subdivision | County subdivisions (CCDs, MCDs) |
place | Census-designated places and incorporated cities |
tract | Census tracts with GEOID |
block_group | Census block groups |
metro_micro_cbsa_statistical_area | Every metropolitan or micropolitan statistical area the ZIP overlaps, largest pct_of_zip first |
metropolitan_cbsa_division | Every metro division the ZIP overlaps, largest pct_of_zip first (where applicable) |
Boundary identifiers use fips for counties, cbsa_code for CBSAs, or geoid as applicable; other fields include, name, land_area_sq_mi, water_area_sq_mi, pct_of_zip (% of ZIP this boundary covers), and pct_of_area (% of boundary this ZIP covers).
{
"census": {
"current": {
"county": [
{
"name": "Los Angeles",
"land_area_sq_mi": 4060.29,
"water_area_sq_mi": 689.15,
"pct_of_zip": 100,
"pct_of_area": 0.27,
"fips": "06037"
}
],
"place": [
{
"geoid": "0606308",
"name": "Beverly Hills",
"land_area_sq_mi": 5.71,
"water_area_sq_mi": 0,
"pct_of_zip": 37.16,
"pct_of_area": 71.42
}
],
"tract": [
{
"geoid": "06037261102",
"name": "2611.02",
"land_area_sq_mi": 3.07,
"water_area_sq_mi": 0,
"pct_of_zip": 27.96,
"pct_of_area": 99.98
}
],
"metro_micro_cbsa_statistical_area": [
{
"name": "Los Angeles-Long Beach-Anaheim, CA",
"land_area_sq_mi": 4853.54,
"water_area_sq_mi": 845.63,
"pct_of_zip": 100,
"pct_of_area": 0.22,
"cbsa_code": "31080"
}
],
"source": "U.S. Census Bureau",
"product": "TIGER/Line Shapefiles",
"attribution": "Source: U.S. Census Bureau, TIGER/Line Shapefiles 2025",
"year": 2025,
"vintage": "2025"
}
}
}
Congressional Districts
Returns congressional district overlaps for the current Congress session, with primary district identification and area percentages. Fields sit under congressional_district.current (cd_YYYY adds an earlier year, 2010–2025, under its "YYYY" key), beside the boundary file's source, product, attribution, year, and vintage.
Fields Returned (US)
| Field | Type | Description |
|---|---|---|
congress | number | Congressional session number (e.g., 119) |
primary.geoid | string | GEOID of the primary district (see primary.basis) |
primary.state | string | State abbreviation |
primary.district_number | integer | District number |
primary.basis | string | How the primary district was chosen. On /v2/zip: the district covering the largest share of the ZIP Code’s area (by area, not population), or the district containing the ZIP Code point. On /v2/address: the district containing the matched location, except in the newest year, where it is the district USPS assigns to the ZIP+4 when USPS assigns one |
districts[] | array | All overlapping districts with geoid, name, state, district_number, land_area_sq_mi, water_area_sq_mi, pct_of_zip, pct_of_area |
Fields Returned (CA)
Canadian codes return federal electoral districts inside each statcan period instead, as the list statcan.current.federal_electoral_district (and statcan["2016"].federal_electoral_district for statcan_2016); there is no separate top-level family and no extra charge. cd on a Canadian code is answered as statcan with an ENRICHMENT_FLAG_RENAMED notice. Full postal codes return the district containing their location; FSA boundary overlaps include pct_of_fsa. Each district is the census row above, with name in both languages ("Ottawa Centre / Ottawa-Centre"), name_en and name_fr. Canadian codes never return congressional_district, state_legislative, or school_district.
{
"congressional_district": {
"current": {
"source": "U.S. Census Bureau",
"product": "TIGER/Line Shapefiles",
"attribution": "Source: U.S. Census Bureau, TIGER/Line Shapefiles 2025",
"year": 2025,
"vintage": "2025",
"congress": 119,
"districts": [
{
"geoid": "0632",
"state": "CA",
"district_number": 32,
"name": "Congressional District 32",
"land_area_sq_mi": 296.24,
"water_area_sq_mi": 92.38,
"pct_of_zip": 62.76,
"pct_of_area": 2.32
},
{
"geoid": "0636",
"state": "CA",
"district_number": 36,
"name": "Congressional District 36",
"land_area_sq_mi": 97.11,
"water_area_sq_mi": 97.51,
"pct_of_zip": 37.16,
"pct_of_area": 4.19
}
],
"primary": {
"geoid": "0632",
"state": "CA",
"district_number": 32
}
}
}
}
State Legislature
Returns state senate (upper chamber) and assembly/house (lower chamber) district overlaps with area percentages, under state_legislative.current (state_leg_YYYY adds an earlier year, 2010–2025, under its "YYYY" key) beside source, product, attribution, year, and vintage. US only
Fields Returned
| Field | Type | Description |
|---|---|---|
legislative_year | integer | Legislative year of the district plan (e.g., 2024) |
upper[] | array | State Senate district overlaps |
lower[] | array | State House/Assembly district overlaps |
Each district includes: geoid, name, district_code, state, state_fips, legislative_session_year, land_area_sq_mi, water_area_sq_mi, pct_of_zip, pct_of_area.
{
"state_legislative": {
"current": {
"source": "U.S. Census Bureau",
"product": "TIGER/Line Shapefiles",
"attribution": "Source: U.S. Census Bureau, TIGER/Line Shapefiles 2025",
"year": 2025,
"vintage": "2025",
"legislative_year": 2024,
"upper": [
{
"geoid": "06024",
"name": "24",
"name_full": "State Senate District 24",
"state": "CA",
"state_fips": "06",
"district_code": "024",
"legislative_session_year": "2024",
"land_area_sq_mi": 327.55,
"pct_of_zip": 92.81
}
],
"lower": [
{
"geoid": "06051",
"name": "51",
"name_full": "Assembly District 51",
"state": "CA",
"state_fips": "06",
"district_code": "051",
"legislative_session_year": "2024",
"land_area_sq_mi": 56.4,
"pct_of_zip": 63.49
}
]
}
}
}
School Districts
Returns NCES school district data with boundary overlaps, under school_district.current with source, product, attribution, school_year, and a districts array. US only
District boundaries are from the National Center for Education Statistics (NCES). A single ZIP can overlap multiple school districts because different district types serve the same geographic area at different grade levels.
District type breakdown
Unified: Serves all grades K–12. The most common type nationwide.
Elementary / Secondary: Grade-banded districts that overlap geographically. Elementary districts cover lower grades (K–6 or K–8), while secondary districts cover upper grades (7–12 or 9–12).
Administrative: Supervisory unions (52 records, all in Vermont). The leaid field is a 7-character NCES Local Education Agency ID.
Locale codes: Range from 11 (City—Large) to 43 (Rural—Remote). Returned in the locale object as both a numeric code and description.
Fields Returned per District
| Field | Type | Description |
|---|---|---|
leaid | string | NCES Local Education Agency ID |
name | string | District name |
type | string | District type: unified, elementary, secondary, administrative, other |
grade_low / grade_high | string | Grade range served (e.g., PK to 12) |
office | object | Office address, city, state, ZIP, county, lat/lon |
locale | object | Locale classification code and description (city/suburb/town/rural) |
pct_of_zip / pct_of_area | number | Overlap percentages |
land_area_sq_mi | number | District land area |
{
"school_district": {
"current": {
"source": "National Center for Education Statistics (NCES)",
"product": "EDGE School District Boundaries",
"attribution": "Source: U.S. Department of Education, National Center for Education Statistics (NCES), EDGE School District Boundaries, SY 2024-25 (TIGER 2025)",
"school_year": "2024-2025",
"districts": [
{
"leaid": "0604830",
"name": "Beverly Hills Unified School District",
"type": "unified",
"grade_low": "KG",
"grade_high": "12",
"state_fips": "06",
"land_area_sq_mi": 6.02,
"pct_of_zip": 39.95,
"pct_of_area": 72.86,
"centroid": {
"lat": 34.07923,
"lon": -118.4024374
},
"office": {
"address": "624 N. Rexford Dr.",
"city": "Beverly Hills",
"state": "CA",
"zip": "90210",
"county": "Los Angeles County",
"location": {
"lat": 34.078986,
"lon": -118.40392
}
},
"locale": {
"code": "21",
"description": "Suburb - Large"
},
"nces_codes": {
"mtfcc": "G5420",
"mtfcc_desc": "Unified School District",
"funcstat": "E",
"funcstat_desc": "Active"
}
}
]
}
}
}
ACS Data
American Community Survey 5-year estimates from the Census Bureau. Four separate data profiles available, each activated by its own flag. US only
ACS metrics include an estimate (est) and margin of error (moe). Percentage fields also include pct and pct_moe. Suppressed Census values are returned as null.
pct is what Census prints in the field’s percent column, which is not always a percentage: for most fields it is the share of the field’s total (race.one_race.asian.chinese.pct, a share of the total population); on a total row it is the count itself (households.total.pct equals households.total.est); a rate is in whichever column Census prints it: employment.unemployment_rate and the poverty fields are published only as a percent, so est is null and the rate is in pct, while the housing vacancy rates (occupancy.homeowner_vacancy_rate, occupancy.rental_vacancy_rate) are published in the estimate column, so the rate is in est and pct is null; and it is null where Census gives no percent (income.per_capita, fertility and vacancy rates). The /v2/radius aggregates follow the same convention field by field: a share is recomputed from the aggregated sums of the field and the total Census divides it by, a total row’s pct is its aggregated count, the unemployment rate is recomputed from the aggregated labor force, and the poverty rates are averages weighted by population.
year field and a vintage field indicating the exact dataset. Current ACS flags (e.g., acs_demographic) return the most recent year (2020–2024). Historical flags (e.g., acs_demographic_2022) return the specified year's dataset. You can request current and historical simultaneously — they appear under acs.current and acs["2022"] respectively. 14 years available: 2011–2024.acs_demographic — DP05 (108 fields)
| Section | Key Fields |
|---|---|
| Sex and Age | Total population, male/female counts, age brackets (under 5 through 85+), median age |
| Race | White, Black, American Indian, Asian, Pacific Islander, other, two or more races |
| Hispanic or Latino and Race | Hispanic/Latino totals, breakdown by race within Hispanic/non-Hispanic |
| Citizen, Voting Age Population | Citizens 18+, total voting age population |
| Total Housing Units | Housing unit count |
acs_social — DP02 (154 fields)
| Section | Key Fields |
|---|---|
| Households by Type | Total households, family/nonfamily, married-couple, single-parent, average size |
| Relationship | Householder, spouse, child, other relatives, nonrelatives, unmarried partner |
| Marital Status | Never married, now married, separated, widowed, divorced |
| Fertility | Women 15–50 who had a birth in the past 12 months |
| Grandparents | Grandparents living with grandchildren, responsible for care |
| School Enrollment | Nursery through graduate school, public/private breakdown |
| Educational Attainment | Less than 9th grade through doctorate, high school+, bachelor's+ |
| Veteran Status | Civilian veterans 18+ |
| Disability Status | Civilian noninstitutionalized population with a disability |
| Residence 1 Year Ago | Same house, different house (same county, state, abroad) |
| Place of Birth | Native vs. foreign-born, state of birth, world region |
| Language Spoken at Home | English only, Spanish, Asian/Pacific Island, other, English proficiency |
| Ancestry | Top ancestral origins reported |
| Computers and Internet Use | Households with computer, broadband, smartphone, no internet |
acs_economic — DP03 (137 fields)
| Section | Key Fields |
|---|---|
| Employment Status | Labor force, employed, unemployed, unemployment rate, armed forces |
| Commuting to Work | Drove alone, carpooled, public transit, walked, work from home, mean travel time |
| Occupation | Management, service, sales, construction, production, transportation |
| Industry | Agriculture, manufacturing, retail, healthcare, education, finance, tech |
| Class of Worker | Private wage, government, self-employed, unpaid family |
| Income and Benefits | Median household/family income, per capita, with earnings/Social Security/retirement |
| Health Insurance Coverage | With/without coverage, private, public, by employment status |
| Poverty Level | Families and people below poverty, by age, family type, education |
acs_housing — DP04 (143 fields)
| Section | Key Fields |
|---|---|
| Housing Occupancy | Total units, occupied, vacant, homeowner vacancy rate, rental vacancy rate |
| Units in Structure | Single-family detached/attached, 2–4 units, 5–19, 20+, mobile home, boat/RV |
| Year Structure Built | 2020+, 2010–2019, 2000–2009, through 1939 or earlier |
| Rooms / Bedrooms | 1 room through 9+, median rooms, bedroom count distribution |
| Housing Tenure | Owner-occupied, renter-occupied, average household size by tenure |
| Year Householder Moved In | 2021+, 2018–2020, 2010–2017, through 1989 or earlier |
| Vehicles Available | No vehicle, 1, 2, 3+ vehicles |
| House Heating Fuel | Gas, electric, fuel oil, coal, wood, solar, other, no fuel |
| Selected Characteristics | Lacking plumbing, lacking kitchen, no telephone service |
| Value | Owner-occupied home value distribution, median value |
| Mortgage Status | With/without mortgage, second mortgage, home equity loan |
| Monthly Owner Costs | Cost distribution, median, as percentage of household income |
| Gross Rent | Rent distribution, median rent, as percentage of household income |
Historical ACS Data (2011–2024)
Append _YYYY to any ACS profile flag to request a specific year. Example: include=acs_housing_2022 returns the 2018–2022 5-Year Estimates for housing. You can request multiple years simultaneously (e.g., include=acs_economic,acs_economic_2019). Each flag adds one data layer: +1 credit on /zip, multiplied by the distance-band base on /radius. The current year's suffix is the same request as the unsuffixed flag (acs_economic and acs_economic_2024 return one block under current and are charged once) — on every endpoint, including the address tract flags. When Census publishes the next release, current moves to it and 2024 is requested as _2024. On /v2/zip, a year Census published no data for at that ZCTA comes back with published: false and is not charged.
| Year | ACS 5-Year Estimates | Fields | Coverage |
|---|---|---|---|
| 2024 (current) | 2020–2024 | 528 | 97.4% |
| 2023 | 2019–2023 | 528 | 100% |
| 2022 | 2018–2022 | 521 | 98.7% |
| 2021 | 2017–2021 | 518 | 98.1% |
| 2020 | 2016–2020 | 518 | 98.1% |
| 2019 | 2015–2019 | 517 | 97.9% |
| 2018 | 2014–2018 | 486 | 92.0% |
| 2017 | 2013–2017 | 483 | 91.5% |
| 2016 | 2012–2016 | 463 | 87.7% |
| 2015 | 2011–2015 | 463 | 87.7% |
| 2014 | 2010–2014 | 441 | 83.5% |
| 2013 | 2009–2013 | 441 | 83.5% |
| 2012 | 2008–2012 | 394 | 74.6% |
| 2011 | 2007–2011 | 394 | 74.6% |
null. 394 “universal” fields are available across all 14 years./v2/address, tract profiles use data.acs.tract.current or data.acs.tract["2023"]/["2022"], with the same profile field names. On /v2/zip, ACS returns per-ZIP data under acs.current.{section} (or acs["2022"].{section} for historical), newest year first. Each /v2/zip year block carries published: false means Census published no data for that ZIP's ZCTA in that year (ZCTAs are redrawn each decade), so its profiles are null and a note says so. On /v2/radius, returns aggregates (counts summed, medians and means averaged by each field’s own count, so a radius median is an estimate) under stats.acs.current.{section} — the stats object appears at the top level of each radius result alongside search, matches, and summary. Current dataset: 2020–2024 5-Year Estimates. Historical: 2011–2024 (14 years total).{
"acs": {
"current": {
"geo_id": "860Z200US90210",
"published": true,
"year": 2024,
"product": "ACS 5-Year Estimates Data Profiles",
"vintage": "2020-2024",
"geography_level": "zcta",
"tables": {
"demographic": "DP05"
},
"demographic": {
"sex_and_age": {
"male": {
"est": 9417,
"moe": 800,
"pct": 49.6,
"pct_moe": 2.3
},
"female": {
"est": 9587,
"moe": 585,
"pct": 50.4,
"pct_moe": 2.3
},
"median_age": {
"est": 51.9,
"moe": 2.3
},
"total_population": {
"est": 19004,
"moe": 1101,
"column": "DP05_0001"
}
},
"race": {
"one_race": {
"white": {
"est": 15147,
"moe": 1106,
"pct": 79.7,
"pct_moe": 3.7
},
"black": {
"est": 403,
"moe": 193,
"pct": 2.1,
"pct_moe": 1
},
"asian": {
"total": {
"est": 1628,
"moe": 467,
"pct": 8.6,
"pct_moe": 2.4
}
}
}
}
},
"source": "U.S. Census Bureau"
}
}
}
Demographics
Returns a compact population, race, age, household and business summary for a US ZIP Code. Each figure comes from the latest release of its source: population, race, age and household counts from the latest decennial Census (tabulated for the ZIP Code Tabulation Area); median household income and median home value from the latest ACS 5-year estimates; business counts and payroll from the latest County Business Patterns; and a current population estimate from USPS delivery points.
Fields Returned
| Field | Type | Description |
|---|---|---|
| population | ||
population.total | number | Population from the latest decennial Census; use this for an official count |
population.estimate | number | Current population estimate, not a Census count: the ZIP Code’s residential delivery points (zip_info.delivery_points.residential, latest USPS month) × its persons per household (households.persons_per_household; where the ZIP Code has none, the average of ZIP Codes within 15 miles, else its county’s). Higher than total where the area has grown since the Census, and where many homes are seasonal or vacant. 0 when there is nothing to estimate from: no residential delivery points, or no persons per household (a PO Box, unique or military ZIP Code without its own, and territories such as the U.S. Virgin Islands) |
population.male | number | Male population (latest decennial Census) |
population.female | number | Female population (latest decennial Census) |
| race | ||
race.white | number | White population (latest decennial Census) |
race.black | number | Black or African American population (latest decennial Census) |
race.hispanic | number | Hispanic or Latino population (latest decennial Census); Hispanic origin is an ethnicity, counted alongside race, so the race counts are not meant to add up to population.total |
race.asian | number | Asian population (latest decennial Census) |
race.american_indian | number | American Indian and Alaska Native population (latest decennial Census) |
race.hawaiian_pacific_islander | number | Native Hawaiian and Other Pacific Islander population (latest decennial Census) |
race.other | number | Population of all other races (latest decennial Census) |
| age | ||
age.median | number | Median age (latest decennial Census) |
age.median_male | number | Median age of males (latest decennial Census) |
age.median_female | number | Median age of females (latest decennial Census) |
| households | ||
households.total | number | Households (latest decennial Census) |
households.persons_per_household | number | Average persons per household (latest decennial Census) |
households.average_family_size | number | Average family size |
households.median_income | number | Median household income (USD), from the latest ACS 5-year estimates |
households.average_home_value | number | Median value of owner-occupied homes (USD), from the latest ACS 5-year estimates: a median, despite the field name. Census’s top category ($2,000,000 or more) shows as 2000001 |
| business | ||
business.establishments | number | Business establishments, from the latest County Business Patterns |
business.employees | number | Paid employees, from the latest County Business Patterns |
business.payroll_q1 | number | First-quarter payroll in full USD, from the latest County Business Patterns (published in thousands) |
business.payroll_annual | number | Annual payroll in full USD, from the latest County Business Patterns (published in thousands) |
business.employment_flag | string | Set when Census withholds the employee count for confidentiality; null when the count is published |
{
"demographics": {
"population": {
"total": 21134,
"estimate": 22973,
"male": 10099,
"female": 11035
},
"race": {
"white": 17130,
"black": 366,
"hispanic": 1296,
"asian": 1406,
"american_indian": 25,
"hawaiian_pacific_islander": 12,
"other": 481
},
"age": {
"median": 49.3,
"median_male": 49.3,
"median_female": 49.2
},
"households": {
"total": 8553,
"persons_per_household": 2.47,
"average_family_size": 3.13,
"median_income": 187801,
"average_home_value": 2000001
},
"business": {
"establishments": 2536,
"employees": 32353,
"payroll_q1": 648013000,
"payroll_annual": 2653591000
}
}
}
Medicare
Returns Medicare geographic identifiers used in healthcare pricing, insurance rating areas, and CMS administrative boundaries.
Fields Returned
| Field | Type | Description |
|---|---|---|
cbsa_code | string | Core Based Statistical Area code used by CMS for Medicare payment |
cbsa_name | string | CBSA name (e.g., “Los Angeles-Long Beach-Anaheim, CA”) |
cbsa_type | string | Metropolitan or Micropolitan (omitted if rural) |
rating_area_id | string | ACA marketplace rating area identifier; null when the ZIP Code has none on file. Every Connecticut ZIP Code is null: CMS has not assigned rating areas to the planning regions that replaced Connecticut’s counties in 2022 |
ssa_state_county_code | string | SSA state/county code used in Medicare administrative systems |
multi_county_notice | string | Present only when a ZIP spans multiple counties with different Medicare assignments |
{
"medicare": {
"cbsa_code": "31084",
"cbsa_name": "Los Angeles-Long Beach-Glendale, CA",
"cbsa_type": "Metro",
"rating_area_id": "16",
"ssa_state_county_code": "05200"
}
}
Data sources
Every dataset the API answers from: the authority behind it, the edition we serve where the source publishes editions, how often we take a new release, and a ready-made citation. Cite the edition shown; a dataset updated on a cadence (the USPS files, for example) has no edition. The same list is served free, without a key, by the MCP server's about_data tool.
| Source | Authority | Provides | Cadence | Edition | Citation |
|---|---|---|---|---|---|
| USPS ZIP Codes | USPS | ZIP Codes, preferred and acceptable city names, counties, classifications | Monthly | — | USPS, ZIP Codes, via zip-codes.com - https://www.zip-codes.com/ |
| USPS City State file | USPS | preferred city name per ZIP Code, acceptable alternates, mailability | Monthly | — | USPS, City State file, via zip-codes.com - https://www.zip-codes.com/ |
| USPS ZIP+4 | USPS | ZIP+4 records: street and unit ranges, record types, carrier routes | Monthly | — | USPS, ZIP+4, via zip-codes.com - https://www.zip-codes.com/ |
| USPS Delivery Statistics | USPS | active and possible delivery points and carrier routes, by ZIP Code and route | Monthly | — | USPS, Delivery Statistics, via zip-codes.com - https://www.zip-codes.com/ |
| Postal Codes | Canada Post | Canadian postal codes and FSAs, cities, provinces, locations | Monthly | — | Canada Post, Postal Codes, via zip-codes.com - https://www.zip-codes.com/ |
| US ZIP Code Boundaries | zip-codes.com | ZIP Code polygons and centroids | Quarterly | — | zip-codes.com, US ZIP Code Boundaries - https://www.zip-codes.com/ |
| US Carrier Route Boundaries | zip-codes.com | USPS carrier route polygons | Quarterly | — | zip-codes.com, US Carrier Route Boundaries - https://www.zip-codes.com/ |
| TIGER/Line Shapefiles | U.S. Census Bureau | counties, metro areas, places, tracts, block groups, congressional and state legislative districts: names, codes, land and water area, and their overlap with each ZIP Code | Yearly | 2026 | U.S. Census Bureau, TIGER/Line Shapefiles 2026, via zip-codes.com - https://www.zip-codes.com/ |
| ZIP Code Tabulation Areas | U.S. Census Bureau | the official land and water area behind zip_info.land_area_sq_mi | Every ten years, with each US census | 2020 | U.S. Census Bureau, ZIP Code Tabulation Areas 2020, via zip-codes.com - https://www.zip-codes.com/ |
| Census Demographic and Housing Characteristics (DHC) | U.S. Census Bureau | population, sex, race and Hispanic origin, median age, households and persons per household (base /zip fields and the demographics include) | Every ten years, with each US census | 2020 | U.S. Census Bureau, Census Demographic and Housing Characteristics (DHC) 2020, via zip-codes.com - https://www.zip-codes.com/ |
| American Community Survey 5-Year Estimates | U.S. Census Bureau | demographic, social, economic and housing estimates for ZIP Code areas and tracts, and the median household income and average home value in the demographics include | Yearly, usually in January | 2020-2024 | U.S. Census Bureau, American Community Survey 5-Year Estimates 2020-2024, via zip-codes.com - https://www.zip-codes.com/ |
| County Business Patterns | U.S. Census Bureau | business establishments, employees and payroll by ZIP Code (demographics.business) | Yearly | — | U.S. Census Bureau, County Business Patterns, via zip-codes.com - https://www.zip-codes.com/ |
| ANSI codes | U.S. Census Bureau | state, county and place codes | When the source publishes | — | U.S. Census Bureau, ANSI codes, via zip-codes.com - https://www.zip-codes.com/ |
| Metropolitan and Micropolitan Statistical Area Delineations | U.S. Office of Management and Budget | which metro and micro areas exist, their type and their counties | When the source publishes | Bulletin 23-01 | U.S. Office of Management and Budget, Metropolitan and Micropolitan Statistical Area Delineations Bulletin 23-01, via zip-codes.com - https://www.zip-codes.com/ |
| Medicare Labor Market Areas | Centers for Medicare & Medicaid Services | Medicare CBSA codes (a different geography from the OMB metro areas), ACA rating areas and SSA state and county codes | Yearly | — | Centers for Medicare & Medicaid Services, Medicare Labor Market Areas, via zip-codes.com - https://www.zip-codes.com/ |
| Elevation | U.S. Geological Survey | ZIP Code centroid elevation | When the source publishes | — | U.S. Geological Survey, Elevation, via zip-codes.com - https://www.zip-codes.com/ |
| Area codes | North American Numbering Plan Administration (NANPA) | telephone area codes per ZIP Code | Quarterly | — | North American Numbering Plan Administration (NANPA), Area codes, via zip-codes.com - https://www.zip-codes.com/ |
| EDGE School District Boundaries | National Center for Education Statistics (NCES) | unified, elementary and secondary school districts | Yearly | SY 2024-25 | National Center for Education Statistics (NCES), EDGE School District Boundaries SY 2024-25, via zip-codes.com - https://www.zip-codes.com/ |
| Time zone boundaries | timezone-boundary-builder | time zone polygons, with the IANA tz database's rules and names | When the source publishes | — | timezone-boundary-builder, Time zone boundaries, via zip-codes.com - https://www.zip-codes.com/ |
| Boundary Files | Statistics Canada | statistical areas: Canadian census divisions, subdivisions, tracts, metropolitan areas, federal electoral districts and FSA polygons | Every five years, with each Canadian census | 2021 Census | Statistics Canada, Boundary Files 2021 Census, via zip-codes.com - https://www.zip-codes.com/ |
| Census of Population | Statistics Canada | FSA population and dwelling counts (fsa_population, fsa_dwellings, fsa_dwellings_occupied) | Every five years, with each Canadian census | 2021 | Statistics Canada, Census of Population 2021, via zip-codes.com - https://www.zip-codes.com/ |
Spatial layers: full provenance
Every spatial block in a response (each census, congressional_district and state_legislative period, school_district, timezone) states source, product and attribution. Show attribution wherever you display the data. The full provenance of each source is below; it is generated from the data files themselves.
| Source | License | Attribution | Refresh |
|---|---|---|---|
| U.S. Census Bureau TIGER/Line Shapefiles | Public domain (U.S. Government work) | Source: U.S. Census Bureau, TIGER/Line Shapefiles 2026 (newest; each vintage names its own edition) | yearly (new TIGER/Line each autumn) |
| Vintages held: 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 | |||
| National Center for Education Statistics (NCES) EDGE School District Boundaries | Public domain (U.S. Government work) | Source: U.S. Department of Education, National Center for Education Statistics (NCES), EDGE School District Boundaries, SY 2024-25 (TIGER 2025) (newest; each vintage names its own edition) | yearly (NCES releases each school year's districts on the matching TIGER/Line year) |
| Vintages held: SY1516-TL16, SY1516-TL17, SY1718-TL18, SY1819-TL19, SY1920-TL20, SY2021-TL21, SY2122-TL22, SY2223-TL23, SY2324-TL24, SY2425-TL25 | |||
| Statistics Canada Boundary Files (catalogue 92-160-X): every census geographic level, digital (DBF) and cartographic (CBF) boundary files, English Shapefile editions | Statistics Canada Open Licence (https://www.statcan.gc.ca/en/terms-conditions/open-licence) | Source: Statistics Canada, Boundary Files, 2021 Census. Reproduced and distributed on an "as is" basis with the permission of Statistics Canada. (newest; each vintage names its own edition) | every 5 years with the census (boundary files ~November of the census year, more levels in the following months); never revised in place except by notice |
| Vintages held: 2011, 2016, 2021 | |||
| timezone-boundary-builder Time zone boundaries (with oceans) | Open Data Commons Open Database License (ODbL) 1.0; IANA tz database: public domain | Time zone boundaries © OpenStreetMap contributors, timezone-boundary-builder (ODbL); time zone rules: IANA tz database | with each timezone-boundary-builder release (it follows the IANA tzdata releases) |
| Vintages held: 2016j, 2017c, 2018g, 2019b, 2020d, 2022g, 2023d, 2024b, 2025b, 2026d | |||
| ZIP-Codes.com Carrier Route Boundary Data | Licensed data — ZIP-Codes.com | Source: ZIP-Codes.com | quarterly |
| Vintages held: 2026-08 | |||
| ZIP-Codes.com ZIP Code Boundary Data | Licensed data — ZIP-Codes.com | Source: ZIP-Codes.com | quarterly |
| Vintages held: 2026-08 | |||
Credit Costs
Lookup calls use credits from your plan; health and demo calls are not billed. Simple lookups cost 1. Adding enrichments (extra data like demographics or timezone) adds to the cost. The radius endpoint scales with search distance. This section breaks down exactly what each call costs — no surprises.
| Endpoint | Base Cost | + Enrichments | Plain English |
|---|---|---|---|
| Address | 1 per request | + delivered coordinates and boundary/tract tags | 1 base; no match costs 1 |
| Quick ZIP | 1 (ZIP/FSA/Postal), 2 (ZIP+4) | × (1 + timezone + reverse_geocode) | ZIP+4 + timezone = 4; coordinates to ZIP+4 + timezone = 6 |
| ZIP Details | 1 (all code types) | + billable flags + reverse_geocode | ZIP+4 + timezone = 2 |
| Distance | 1 per pair | timezone free | Always 1 credit |
| Suggest | 1–5 (by limit) | N/A | 1 credit for ≤15 results |
| Radius | 1–5 (by distance) | × (1 + flags + auto_radius) | Distinct timezone/ACS requests, including historical years (the current year's suffix and the unsuffixed flag count once) |
| Batch | Per-item cost | Same as single | Sum of individual items |
| Health | 0 | N/A | Always free |
Address Matching
1 base + delivered coordinates + each delivered boundary flag + each delivered tract tag. No match costs 1; coordinates and boundary/tract flags that are not returned are not charged.
Quick ZIP & ZIP Details
Quick ZIP credits = base × (1 + timezone + reverse_geocode). Base is 2 for ZIP+4 and 1 for ZIP, FSA, or Postal. Coordinates use the resolved type and add a reverse-geocode layer. ZIP+4 with timezone costs 4; coordinates resolving to ZIP+4 cost 4, or 6 with timezone. Unknown flags have no effect. ZIP Details credits = 1 + billable flags + reverse_geocode. Base is 1 for every code type, including ZIP+4 and Postal. Canadian unavailable flags cost 0 with a notice.
Examples: quick-zip ZIP+4 with timezone = 4. Full /zip ZIP+4 with timezone + demographics = 3.
Radius Search
Base cost scales with distance (banded pricing). Each enrichment multiplies the base rather than adding to it. Auto-radius counts as one additional data layer.
Examples: 25mi with no flags = 1. 25mi with timezone = 2. 200mi centroid with 4 ACS flags = 10.
Distance
Fixed 1 credit per pair. Timezone data is always included at no extra cost. Mix any input types freely.
Suggest
Cost scales with the requested limit: 1–15 results = 1 credit, 16–50 = 2, 51–150 = 3, 151–500 = 5. Billed on requested limit, not results returned.
Radius Credit Costs
Formula: base × (1 + enrichment_count + auto_radius). Base bands differ by mode.
Centroid Mode (default) — ceil(radius / 100), max 500mi
| Band | Radius Range | Base | No Flags | + 1 flag | + 2 flags |
|---|---|---|---|---|---|
| 1 | 1–100 mi | 1 | 1 | 2 | 3 |
| 2 | 101–200 mi | 2 | 2 | 4 | 6 |
| 3 | 201–300 mi | 3 | 3 | 6 | 9 |
| 4 | 301–400 mi | 4 | 4 | 8 | 12 |
| 5 | 401–500 mi | 5 | 5 | 10 | 15 |
Spatial Mode — ceil(radius / 50), max 250mi
| Band | Radius Range | Base | No Flags | + 1 flag | + 2 flags |
|---|---|---|---|---|---|
| 1 | 1–50 mi | 1 | 1 | 2 | 3 |
| 2 | 51–100 mi | 2 | 2 | 4 | 6 |
| 3 | 101–150 mi | 3 | 3 | 6 | 9 |
| 4 | 151–200 mi | 4 | 4 | 8 | 12 |
| 5 | 201–250 mi | 5 | 5 | 10 | 15 |
Band boundaries are inclusive at the top: max=50 = band 1, max=51 = band 2. Decimal values are accepted: max=50.5 = band 2.
Formula Examples
25mi centroid, no flags→ ceil(25/100) × (1 + 0) = 1 credit25mi centroid, timezone→ 1 × (1 + 1) = 2 credits100mi centroid, acs_demographic + timezone→ 1 × (1 + 2) = 3 credits200mi centroid, 4 ACS flags→ 2 × (1 + 4) = 10 credits500mi centroid, 4 ACS + timezone→ 5 × (1 + 5) = 30 credits50mi spatial, no flags→ ceil(50/50) × (1 + 0) = 1 credit100mi spatial, 2 ACS flags→ 2 × (1 + 2) = 6 credits250mi spatial, 4 ACS + timezone→ 5 × (1 + 5) = 30 credits
Credit Estimator
Select an endpoint and configure options to estimate credits assuming the selected enrichments are delivered. Actual usage appears in meta.credits.
Free accounts receive 2,500 daily credits. Subscribers receive their monthly allowance and no daily free credits.
Subscription Plans
| Plan | Monthly Credits | Price |
|---|---|---|
| Free | 2,500 / day | Free |
| Developer | 100,000 | $49/mo |
| Professional | 350,000 | $149/mo |
| Business | 1,500,000 | $499/mo |
Credit Packs
Pre-purchase credits that never expire. Works alongside any plan — free or paid. If you exceed your daily free allowance or monthly subscription, purchased credits kick in automatically.
| Pack | Credits | Price | Per 1,000 |
|---|---|---|---|
| Starter | 25,000 | $19 | $0.76 |
| Standard | 65,000 | $49 | $0.75 |
| Growth | 150,000 | $99 | $0.66 |
| Pro (best value) | 300,000 | $179 | $0.60 |
| Enterprise | 2,000,000 | $799 | $0.40 |
Purchased credits are used only after your free or subscription allowance is exhausted. Need a custom volume? Contact us.
Error Codes
Every error, including per-item failures, includes a public url for next steps. AUTH_MISSING_KEY, AUTH_INVALID_KEY, AUTH_INACTIVE_KEY, AUTH_INACTIVE_ACCOUNT, AUTH_TEST_KEY_RESTRICTED, AUTH_TEST_KEY_RADIUS_EXCEEDED, AUTH_IP_RESTRICTED, AUTH_ORIGIN_RESTRICTED, and CREDIT_INSUFFICIENT link to your account. AUTH_BATCH_REQUIRES_SUBSCRIPTION, AUTH_FREE_TIER_RADIUS_EXCEEDED, and RATE_LIMIT_MINUTE link to pricing. All VALIDATION_* errors, MISSING_VERSION, UNKNOWN_VERSION, ROUTE_NOT_FOUND, BAD_REQUEST, SPATIAL_MODE_DISABLED_BATCH, and NOT_FOUND_COORDS_NO_BOUNDARY link to documentation. Other errors link to the general API page. The missing-key response for a key sent in the Authorization header without the Bearer scheme links to authentication documentation.
Whole-request failures use {success:false,request?,error:{code,message,retryable?,url,get_started?},meta}; results is omitted. Middleware echoes request:{method,path} and marks 429/503/504 as retryable. Braces in messages are placeholders; validation can return additional parameter-specific messages.
| HTTP | Code | Message / variants |
|---|---|---|
| 401 | AUTH_MISSING_KEY | "API key is required. Send it in the X-Api-Key header or the ?key= query parameter. Try the API now with the demo key in error.get_started, see prices and credit packs at https://api.zip-codes.com/pricing.json, or get a key at https://www.zip-codes.com/api/account." |
| 401 | AUTH_MISSING_KEY (key in Authorization without Bearer) | "API key detected in the Authorization header without the Bearer scheme. Send it as Authorization: Bearer zc_... or in the X-Api-Key header." |
| 401 | AUTH_INVALID_KEY | "Invalid API key format. Keys are 32 characters. Manage your keys at https://www.zip-codes.com/api/account." (<16 chars) / "Invalid API key. Manage your keys at https://www.zip-codes.com/api/account." |
| 403 | AUTH_INACTIVE_KEY | "API key has been deactivated." / "API key has expired." |
| 403 | AUTH_INACTIVE_ACCOUNT | "Account has been suspended. Visit https://www.zip-codes.com/api/account for assistance." |
| 403 | AUTH_IP_RESTRICTED / AUTH_ORIGIN_RESTRICTED | "Access denied: IP address not in allowlist." / "Access denied: origin domain not in allowlist." |
| 403 | AUTH_IP_BLOCKED | "Too many invalid authentication attempts. Try again later." |
| 403 | AUTH_BATCH_REQUIRES_SUBSCRIPTION | "Batch endpoints need a subscription or purchased credits. Buy a pack with POST https://api.zip-codes.com/v2/purchase (see https://api.zip-codes.com/pricing.json) or subscribe at https://www.zip-codes.com/api/pricing." |
| 403 | AUTH_FREE_TIER_RADIUS_EXCEEDED | "Free tier is limited to {n}-mile radius in {mode} mode. Upgrade at https://www.zip-codes.com/api/pricing." |
| 402 | CREDIT_INSUFFICIENT | "Insufficient credits. This request costs {n} credits but only {m} available. Buy credits at https://www.zip-codes.com/api/account." (batch: "Insufficient credits. This batch costs ~{n} credits but only {m} available. Buy credits at https://www.zip-codes.com/api/account.") |
| 429 | RATE_LIMIT_MINUTE | "Rate limit exceeded. Please retry after 60 seconds." + meta.rate_limit {limit, window:"minute", retry_after:60} |
| 429 | RATE_LIMIT_MINUTE (unauthenticated flood) | "Too many unauthenticated requests. Retry after 60 seconds." |
| 400 | VALIDATION_MISSING_PARAM | "Missing required parameter: {p}" (alias variant adds ". Found '{alias}' in query — use '{p}' instead.") |
| 400 | VALIDATION_INVALID_PARAM | "Parameter '{p}' cannot be empty." |
| 400 | VALIDATION_INVALID_ZIP / VALIDATION_INVALID_FSA / VALIDATION_INVALID_POSTAL | "Invalid postal code format: '{raw}'." |
| 400 | VALIDATION_INVALID_COORDINATES | "Coordinates out of range: lat must be -90..90, lon must be -180..180." |
| 400 | VALIDATION_INVALID_REQUEST | "Malformed or unreadable request. Check that the request body is valid JSON and all fields have the correct types." |
| 400 | MISSING_VERSION | "API version required. Use /v2/ prefix." (a path outside /v2) |
| 400 | UNKNOWN_VERSION | "Unknown API version. Use /v2/ prefix." (/v3 and later) |
| 404 | ROUTE_NOT_FOUND | No /v2 route matches this path. Then the call it most likely meant, when the path can mean only one: Use GET /v2/zip?code=10977&include=census (code takes a ZIP, ZIP+4, FSA, postal code or lat,lon coordinates; values go in the query string, not the path). Every route: https://api.zip-codes.com/llms.txt. Otherwise the public routes, each with its query (GET|POST /v2/zip?code={code}) or its batch body key (POST /v2/zip/batch {"codes": [...]}). A /v2 path no route matches (/v2/zip/10977, /v2/zipcode/90210). Only a value that parses as a code or coordinates is repeated back; a number whose meaning the path does not settle, an address or search text stay placeholders ({code}, {miles}, {address}, {text}). Never run, never charged. |
| 500 | INTERNAL_ERROR | "An unexpected error occurred. Please try again later." |
| 403 | AUTH_TEST_KEY_RESTRICTED | Test API keys are restricted to demo codes only. Code '{code}' is not a demo code. (Demo help suffix appended.) |
| 403 | AUTH_TEST_KEY_RADIUS_EXCEEDED | Test API keys are limited to a {n}-mile radius. Requested: {r} miles. (Auto variant: Auto-resolved: {r} miles.) |
| 400 | VALIDATION_BATCH_TOO_LARGE | Batch size {n} exceeds maximum of {max} |
| 400 | VALIDATION_INPUT_TOO_LONG | Address input exceeds maximum length of 500 characters. / Address input exceeds maximum length of 300 characters after cleanup. |
| 400 | VALIDATION_INPUT_TOO_SHORT | Address input is too short to parse (minimum 9 characters after cleanup). |
| 400 | NOT_FOUND_COORDS_NO_BOUNDARY (auto-radius) | No ZIP or FSA boundary found for these coordinates. Auto radius requires coordinates within a known boundary. |
| 400 | SPATIAL_MODE_DISABLED_BATCH | Spatial mode is not available for batch radius searches. Use mode=centroid (default) or make individual /v2/radius requests with mode=spatial. |
| 503 | SERVICE_POOL_EXHAUSTED | Service temporarily at capacity. Please retry in a few seconds. (Retry-After: 5) |
| 503 | SERVICE_WARMING_UP | Radius search is starting up: the {centroid|spatial} engine is loading after a restart. Retry after {n} seconds. / Suggest is starting up: the suggest index is loading after a restart. Retry after {n} seconds. (/v2/radius, /v2/radius/batch and /v2/suggest only, right after a server start or restart, usually under a minute; Retry-After: the expected seconds until ready, default 30; retryable; no credits charged; no partial or fallback answer.) |
| 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable (default configurable maintenance message; Retry-After: 300). Address variant: Address matching service became unavailable during request. |
| 504 | SERVICE_TIMEOUT | The request timed out. Please try again or reduce the search radius. |
| 405 | METHOD_NOT_ALLOWED | {method} is not supported for {route}. It takes {methods}. (A batch route adds: with a JSON body {"codes": [...]}. For one item, use GET /v2/zip?code={code}.) Every route: https://api.zip-codes.com/llms.txt. Allow header: the methods. |
| 413 | REQUEST_TOO_LARGE | Request body exceeds the maximum allowed size. |
| 415 | UNSUPPORTED_MEDIA_TYPE | Content-Type must be application/json for this endpoint. (A body that is not valid JSON returns 400.) |
Test-key restriction messages append this suffix: Demo US ZIPs: 90210, 10001, 10118, 32504, 00601, 96950, 33139, 60601, 98101, 30301, 02872, 06103. Demo CA: M5V, K1A, V6B, T2P, H3A, M1R0E9, V5K0A1, K1A0A1, T1X0L3, H1A0A1. Address restrictions read Test API keys are restricted to demo addresses only. Use a real API key to look up any address. followed by a suffix naming demo addresses. The full demo address list (matched ignoring case, commas, periods and extra spaces) is in llms-full.txt; every address example on this page is on it.
Address service-not-ready messages are Address matching service is not ready. Binary files are still loading, please retry shortly. or Address matching service is not ready. Address matching is not enabled on this server.
401 responses include WWW-Authenticate: ApiKey realm="zip-codes-api". Missing-key, invalid-key, and insufficient-credit messages also include the account link in their text. VALIDATION_UNKNOWN_ENRICHMENT is a notice on HTTP 200, not an error.
Per-Item Not Found (HTTP 200)
| Code | Message |
|---|---|
NOT_FOUND_ZIP | ZIP code {zip} not found. |
NOT_FOUND_FSA | FSA {fsa} not found. |
NOT_FOUND_POSTAL | Postal code {postal} not found. |
NOT_FOUND_COORDS_NO_BOUNDARY | No ZIP or postal code boundary found for coordinates {lat},{lon}. |
NOT_FOUND_FROM / NOT_FOUND_TO | Code '{input}' not found in any boundary table |
INSUFFICIENT_INPUT | Not enough information to locate the address. Include a city and state, or a ZIP code. |
AMBIGUOUS | More than one address matches. Add detail such as a unit number, directional or ZIP code. |
NOT_FOUND | The address could not be matched. |
quick-zip coordinate variant: No ZIP or FSA boundary found for coordinates {coordinates}. /zip can also report Coordinates {lat},{lon} landed in FSA {fsa} but no postal code could be resolved. Use /quick-zip for FSA-level lookups.
Postal lookup envelopes keep success:true with {success:false,input:{code},error:{code,message,url}} in results. Codes: NOT_FOUND_ZIP, NOT_FOUND_FSA, NOT_FOUND_POSTAL, NOT_FOUND_COORDS_NO_BOUNDARY, and distance NOT_FOUND_FROM/NOT_FOUND_TO. ZIP: ZIP code {zip} not found.; quick-zip batch: Code {code} not found.; FSA: FSA {fsa} not found.; radius: Center code '{code}' not found.; distance: Code '{input}' not found in any boundary table. Unknown ZIP+4 with a known parent ZIP succeeds with detail:{zip4,valid:false}.
See Address Matching for INSUFFICIENT_INPUT, AMBIGUOUS, and NOT_FOUND, also HTTP 200. Address batch validation messages: Address exceeds maximum length of 500 characters., Address exceeds maximum length of 300 characters after cleanup., Address too short to parse (minimum 9 characters).; item INTERNAL_ERROR: Address matching failed for this item.
Error Response Format
{
"success": false,
"request": {
"method": "GET",
"path": "/v2/quick-zip"
},
"error": {
"code": "AUTH_MISSING_KEY",
"message": "API key is required. Send it in the X-Api-Key header or the ?key= query parameter. Try the API now with the demo key in error.get_started, see prices and credit packs at https://api.zip-codes.com/pricing.json, or get a key at https://www.zip-codes.com/api/account.",
"retryable": false,
"url": "https://www.zip-codes.com/api/account",
"get_started": {
"demo_key": "zc_test_DEMOAPIKEY000000000000",
"demo_scope": "the demo ZIPs, postal codes and addresses listed in https://api.zip-codes.com/llms-full.txt",
"pricing": "https://api.zip-codes.com/pricing.json",
"llms_txt": "https://api.zip-codes.com/llms.txt",
"openapi": "https://api.zip-codes.com/v2/openapi.json",
"signup": "https://www.zip-codes.com/api/account"
}
},
"meta": {
"request_id": "01M3SX2VHP0M9TXH8WFHW7ZM3G",
"version": "2.0.0",
"timing_ms": 0
}
}Rate Limiting
Limits use a one-minute sliding window (six 10-second segments). There are no hourly or daily request limits; daily free credits are a separate allowance.
Allowed authenticated API requests carry X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp, now + 60 seconds), and X-RateLimit-Window: minute. These headers are absent on health, docs, swagger, OPTIONS and auth-rejected requests. A 429 adds Retry-After: 60. X-Request-Id is a ULID; X-Api-Version is 2.0.
Limits by Rate Tier
| Tier | Requests per minute |
|---|---|
| Free (default) | 60 |
| Restricted | 20 |
| Developer | 300 |
| Professional | 300 |
| Business | 600 |
| Enterprise | 1800 |
| Demo (per IP) | 5 |
| Prepaid (a free account holding purchased credits, while they last; a plan keeps its own limit), and a prepaid card holding credits | 300 |
| Prepaid card with no credits left (until a reload credits it) | 10 |
{
"success": false,
"request": {
"method": "GET",
"path": "/v2/quick-zip"
},
"error": {
"code": "RATE_LIMIT_MINUTE",
"message": "Rate limit exceeded. Please retry after 60 seconds.",
"retryable": true,
"url": "https://www.zip-codes.com/api/pricing"
},
"meta": {
"request_id": "01M3SX2VHP0M9TXH8WFHW7ZM3G",
"version": "2.0.0",
"timing_ms": 0,
"rate_limit": {
"limit": 60,
"window": "minute",
"retry_after": 60
}
}
}Buying Credits with a Wallet (MPP)
This API accepts payment programmatically. Anyone with an MPP wallet (a shared payment token paid through Stripe — card or Link), an agent or a person, can buy credits and keep working with no signup, no account and no person on our side. The wallet authorizes each payment (Link, for example, asks its human to approve every payment in the Link app). The scheme is the Machine Payments Protocol (MPP, draft-httpauth-payment): the API answers 402 with a WWW-Authenticate: Payment challenge, you pay it with your wallet, and you send the same request again with the credential in a Payment-Authorization header. A successful answer carries a Payment-Receipt header. Credits are added only after the payment completes (a card clears at once; some methods take longer).
Two moments raise a challenge:
- Out of credits. A data request that runs out of credits answers
402 CREDIT_INSUFFICIENT; when one pack would make that request succeed, it carries the offer (error.payment: the smallest such pack, its price and the payment methods) and the challenge. Pay it and send the request again: the pack is credited and the request answered. - Buying on purpose.
POST /v2/purchasesent without aPayment-Authorizationheader answers402 PAYMENT_REQUIREDwith the challenge.
Three endpoints, each with its parameters, response fields, examples and errors in the endpoint reference:
POST /v2/purchasebuys one pack (with a key, for that key's account or prepaid card; without one, a new prepaid card).GET /v2/balancereads the credit pools of this key's account or prepaid card.GET /v2/purchases/{id}reads one purchase by its order number; use it to follow a202untilstatusreadssucceeded.
Prepaid cards. Sent without any API key, POST /v2/purchase makes a prepaid card: an API key with no account that holds the pack's credits and works on every endpoint until they run out (it has no free daily credits, and runs at 10 requests a minute while empty). Its key is in results[0].api_key. A purchase sent with that key reloads the card. Keep the key: the same payment sent again returns it again only while the card is unused, and for at most 24 hours after the payment settles. From zero, go straight to POST /v2/purchase: a data request without a key answers 401 AUTH_MISSING_KEY, not a purchase offer.
Limits and keys
GET /v2/purchases/{id}andGET /v2/balancetake the key in a header (X-Api-KeyorAuthorization: Bearer), never?key=(401). They share a limit of 10 a minute per key; after 20 unknown purchase ids in an hour, purchase lookups answer429until the hour ends.POST /v2/purchase: 5 payment attempts a minute per key, 20 per IP.- Test, demo and admin keys cannot read a purchase or a balance (
403 AUTH_TEST_KEY_RESTRICTED); test and demo keys cannot buy and get no payment offers.
A pack bought with a key is added to that key's account or prepaid card. The names, credits and prices of the agent packs are live at pricing.json. Paying accepts the Terms of Service.
Supported Countries
| Country | Code Types | Census Boundaries | ACS Data | Political Districts |
|---|---|---|---|---|
| United States | ZIP (5-digit), ZIP+4 (9-digit) | Full (county, tract, block group, place, CBSA) | Full (DP02–DP05) | Congressional, State Senate, State House |
| Canada | FSA (3-char), Postal (6-char) | Statistics Canada census geographies (FSA overlaps or the areas containing a postal code) | Not available | Federal electoral districts inside statcan |
HTTP Status Codes
Summary of all HTTP status codes the API can return:
| Code | Meaning | When |
|---|---|---|
| 200 | OK | Processed lookup, including not-found. Check result errors; single address no-match has success:false. Batch item failures also return 200. |
| 400 | Bad Request | Missing or invalid parameters, malformed JSON body, empty batch |
| 401 | Unauthorized | Missing or invalid API key |
| 402 | Payment Required | Insufficient credits or daily limit reached |
| 403 | Forbidden | Inactive key/account, allowlist restriction, or plan/demo restriction |
| 405 | Method Not Allowed | Wrong HTTP method (e.g., PUT on a GET/POST endpoint) |
| 413 | Payload Too Large | Request body exceeds the maximum allowed size |
| 415 | Unsupported Media Type | Unsupported media type; /v2 POST requests are normalized to JSON |
| 429 | Too Many Requests | Rate limit exceeded. Check Retry-After header. |
| 500 | Internal Server Error | Unexpected server error. Include X-Request-Id when reporting. |
| 504 | Gateway Timeout | Request timed out |
| 503 | Service Unavailable | Server maintenance or temporary overload; on /v2/radius, /v2/radius/batch and /v2/suggest, also while the radius engine or suggest index loads after a restart (SERVICE_WARMING_UP, not charged). Retry after the Retry-After header's seconds. |
Versioning
The API uses path-based versioning. All current endpoints are under /v2/. The version is also returned in API response metadata as meta.version.
- Backward-compatible changes (new fields, new enrichments, new endpoints) are added without a version bump.
- Breaking changes (removed fields, changed response structure) will only occur in a new version (e.g.,
/v3/). - Nothing is removed from
v2. An include flag, year, field or endpoint that is retired keeps working: it is still accepted and answered with anENRICHMENT_FLAG_RETIREDnotice (with its data while it exists, otherwise not charged). - The current version (
v2) is stable and actively maintained. There are no plans to deprecate it. - If a future version is released,
v2will continue to operate for a minimum of 12 months after deprecation notice. - See our Service Level Agreement and Terms of Service for uptime and usage commitments.
OpenAPI Specification
A machine-readable OpenAPI 3.0 specification is auto-generated from the API and always reflects the current endpoints, parameters, and response schemas.
| Resource | URL | Use For |
|---|---|---|
| Postman Workspace | /postman |
One-click fork in Postman — live, versioned collection |
| Swagger UI | /swagger |
Interactive exploration and testing |
| OpenAPI JSON | /swagger/v2/swagger.json |
Generate SDKs, import into Insomnia/Bruno/AI coding tools |
| Download Spec | /v2/openapi.json |
Download the OpenAPI spec file directly |
Changelog
Notable API changes, data updates, and new features.
November 3, 2026
- Tract ACS for every year, 2011–2024:
/v2/addressand its batch acceptacs_tract_{profile}_{YYYY}for every ACS release from 2011 (was 2022–2024), in the same per-year block underdata.acs.tractand at the same price, one credit per profile and year delivered. Each year’s tract comes from that year’s Census tracts, so an address’sgeo_idcan differ between years. - Private mailboxes printed as
PMB n: a unit at a commercial mail receiving agency (a mail drop such as a UPS Store) that does not confirm is read as the renter’s private mailbox, as USPS reads it, and printed asPMB n, with a newUNIT_READ_AS_PMBnotice. The ZIP+4, street and house number are unchanged. - Address notices:
MILITARY_ADDRESSnow says where USPS puts the parts of an APO/FPO/DPO address (streetholds the unit,numberthe box);HOUSE_NUMBER_CORRECTEDis now sent when a house number is read differently from how it was written;ZIP4_WITHHELDon a phantom carrier route says why. - A wrong
/v2route names the right call: a/v2path that matches no route answers404 ROUTE_NOT_FOUND(was400 MISSING_VERSION) with the call it most likely meant, such asGET /v2/zip?code=10977&include=censusfor/v2/zip/10977?include=census. A route asked with the wrong method answers405 METHOD_NOT_ALLOWEDwith anAllowheader. Neither is charged. See Error codes. - Faster ACS: tract ACS on
/v2/addressand ACS on/v2/zipare built faster and with less memory. Responses are unchanged.
November 1, 2026
- Buying credits through the API:
POST /v2/purchasebuys a credit pack with an MPP wallet (a shared payment token paid through Stripe), and an out-of-credits402 CREDIT_INSUFFICIENTcan carry a payment offer to pay and retry. Sent without an API key, a purchase makes a prepaid API key holding the pack’s credits.GET /v2/balancereads your credit pools andGET /v2/purchases/{id}reads one purchase. Purchased credits never expire. The account owner can switch API purchases off on the dashboard’s Settings page. Purchases through the API may answer503“paused” withRetry-Afterwhile they are switched off on our side. - Batch with purchased credits: the batch routes now answer for an account holding purchased credits, not only for a subscriber. A free account holding purchased credits gets 300 requests a minute (was 60) while they last.
- API key as
Authorization: Bearer: every route accepts the key as a Bearer token as well asX-Api-Key(recommended). - The payment credential in
Authorization: Payment(2.3.1): the paid credential is read fromAuthorization: Payment …, the MPP default, as well as fromPayment-Authorization. The credential's format is shown under POST /v2/purchase. - TIGER/Line 2026 is current: bare
census,cdandstate_leganswer from the 2026 Census boundaries undercurrent; 2025 is an earlier year, under its"2025"key (census_2025), and every year from 2010 stays available. - MCP: pages on
www.zip-codes.comandapi.zip-codes.comcan call the MCP server from a browser (CORS); other sites cannot. MCP clients are unaffected. - Documentation: the Data sources section lists every dataset the API answers from, and the address notice lists name
NO_HOUSE_NUMBERandZIP4_WITHHELD.
October 19, 2026
- Metro areas are lists (response shape change): in every
censusperiod on/v2/zip,/v2/addressand their batches,metro_micro_cbsa_statistical_areaandmetropolitan_cbsa_divisionare now arrays of every area the ZIP Code overlaps, largestpct_of_zipfirst (each was a single object, so a ZIP Code in two metro areas lost the second). Read[0]where you read the object. - Address coordinates charged only when returned: on
/v2/address,coordinatescosts a credit only when alocationis returned. - ZIP+4 street detail: on
/v2/quick-zipand/v2/zip,detail.street[]now sends what USPS publishes. A blank house or unit number isnull(it was"0", giving"350 5TH AVE, 0"and"0 GENERAL DELIVERY");nameis uppercase like the rest of the address (5TH, not5Th); unit numbers such asK01keep their zeros.record_typeHreadsHighrise(it read “Highway Contract”) andFreadsFirm. Where several USPS records cover one ZIP+4, the same one is always returned. - Land and water area:
/v2/zipzip_info.land_area_sq_miandwater_area_sq_minow come from the Census ZIP Code Tabulation Area (land area was the boundary polygon’s, water included), and arenullfor a ZIP Code with no ZCTA of its own. - No placeholder values: ZIP Codes stored without a location (military and a few PO Box and unique ones) return
location,area_codesandelevationasnullinstead of 0,0,["000"]and0;medicare.rating_area_id"0"(none) isnull. - Congressional districts carry their own state on
/v2/zipand/v2/address: a ZIP crossing a state line no longer shows the other state’s district under its own state. - Radius:
/v2/radiusACS statistics cover every match in the radius whatever thelimit, and each field’spctfollows what Census prints in its percent column (shares use Census’s own totals). Spatial mode no longer lists codes that only graze the circle (pct_insideunder 0.01). County names are spelled as on/v2/zip(DeKalb, notDekalb), on/v2/distancetoo. - Suggest:
/v2/suggestfinds every ZIP Code, PO Box and military ones included; metro areas carry their two-letter state; places, counties and metro areas follow the 2025 Census boundaries, withlocationat Census’s internal point. - Documentation: the OpenAPI spec describes nearly every response field, among them the
/v2/addressfields, confidence levels and codes; the address input guidance asks for one line in mailing order, with any unit before the city.
October 17, 2026
- MCP server: the API’s data as Model Context Protocol tools for AI agents at
https://mcp.zip-codes.com/mcp(Claude, Cursor, VS Code and other MCP clients). Without a key every tool answers in demo mode (real values, deeper fields withheld, not charged); with your key inX-Api-Keyanswers are complete and a tool call costs what its endpoint costs. Connect instructions and the tool list: mcp.zip-codes.com. - Boundary history: on
/v2/zip,/v2/addressand their batches, add a year tocensus,cdorstate_leg(for examplecensus_2018,cd_2012,state_leg_2020; 2010–2024) to get that year’s boundaries under its own"YYYY"key besidecurrent. The bare flags return each family’s latest year; asking for the latest year both ways (censusandcensus_2025) returns one block, charged once. A year we do not hold is ignored withVALIDATION_UNKNOWN_ENRICHMENTat no charge. - Full-resolution Census boundaries: census, congressional and state legislative boundaries now come from the U.S. Census Bureau’s full-resolution TIGER/Line files instead of the simplified cartographic files, and
currentmoves from 2024 to 2025. Overlap percentages shift, most visibly along coasts, and tiny slivers no longer appear.pct_of_areanow measures land only: the share of the area’s land, as covered by ZIP boundaries.sourcereads"U.S. Census Bureau"(was"Census Bureau TIGER/Line Cartographic Boundaries"). - Shared area:
/v2/zipboundary rows gainshared_area_sq_mi./v2/addressboundary rows now carrypct_of_zip,pct_of_areaandshared_area_sq_miasnull(an address is a point), so both endpoints return the same shape. - ZIPs without a boundary polygon: about 7,600 ZIPs that have a location but no boundary polygon (for example
06101) now return boundaries on/v2/zip, answered from their point, with the overlap fieldsnull. They previously returned none. - 2022 metro areas: Census published no CBSA or metropolitan division boundaries for 2022, so
census_2022returns the 2021 TIGER/Line delineation (OMB Bulletin 20-01, the one Census’s 2022 data products use) for both. - Missing boundary data: a requested family or year whose data is not available returns
ENRICHMENT_UNAVAILABLEand is not charged. If a single layer within a delivered year is missing, its keys arenulland aDATA_UNAVAILABLEnotice names it. - Time zone for an address:
/v2/addressand its batch accepttimezoneand return the same block as/v2/zip, withpct_of_zipnull. One credit, charged only when returned. - Quick ZIP and Distance timezone gain attribution:
/v2/quick-zip,/v2/distanceand their batches keeptimezoneas one flat object with every field it had, and addproductandattribution;sourcenow names the publisher ("timezone-boundary-builder"), as on/v2/zip. The zone comes from the time zone boundary file: for a ZIP crossing a zone line, the zone covering most of it. Credits are unchanged. - Data sources: spatial blocks now carry
source,productandattribution; each source's license, product page, vintages and refresh cadence are listed under Spatial layers: full provenance. - Radius:
/v2/radiusspatial mode now returnspct_insidefor about 100 ZIPs that returnednull, and ACS aggregates weight them by their real share.county_fipsnow follows the ZIP data vendor, which changes it for about 100 ZIPs, mostly Connecticut planning regions. - Fixed: overlap percentages far above 100%, from overlaps that failed to compute, no longer occur.
- Canadian boundaries from Statistics Canada’s boundary files: on
/v2/zipand its batch, a Canadian postal code’s or FSA’scensusandcd(federal_electoral_district) now come from Statistics Canada’s Boundary Files (full extent) and census FSA file, with the same row fields. A postal code’s areas are those containing its location. An FSA’s polygon includes its coastal and Great Lakes water, sopct_of_fsais a share of the whole FSA (shares move in about 470 coastal FSAs). FSAs Statistics Canada has no polygon for (for exampleM5K) are answered from their location, withpct_of_fsaandpct_of_areanull; large rural FSAs that timed out (for exampleX0A) now return. Rows with shares far above 100% no longer occur. - Canadian census rows unified: on
/v2/zip, a Canadian postal code’s and an FSA’scensusandfederal_electoral_districtrows now have the same fields:uid,dguid, areas in square miles and km² (area_sq_mi,area_km2,land_area_sq_mi,land_area_km2),pct_of_area, and type descriptions on FSAs. Federal electoral district and province names are in both languages (namebilingual, plusname_enandname_fr; an FSA’snamewas English only). Postal codes also returnprovince. No field was removed. - Canadian census years:
censusandcdon a Canadian code return the latest Statistics Canada census (2021);census_2016/cd_2016andcensus_2011/cd_2011return that census under its"YYYY"key, one credit each, charged only when returned. A year without a Canadian census (for examplecensus_2024) returnsENRICHMENT_UNAVAILABLEat no charge. Each Canadian period opens withsource,productandattribution, thenyearandvintage(was"source": "Statistics Canada 2021 Census"). - Canadian geography is now
statcan(response shape change): on/v2/zipand its batch, a Canadian postal code’s or FSA’s Statistics Canada geography moves fromcensustostatcan, requested withstatcan(latest census, 2021;statcan_2021is the same),statcan_2016orstatcan_2011, one credit each. Federal electoral districts are now a list inside each period,statcan.current.federal_electoral_district, at no extra charge; the top-levelfederal_electoral_districtfamily is gone. Periods and rows are unchanged.census,census_YYYY,cdandcd_YYYYon a Canadian code still work: they are answered understatcanwith anENRICHMENT_FLAG_RENAMEDnotice, and asking for an alias andstatcantogether returns one block, charged once (socensuspluscdis now one credit).statcanon a US code returnsENRICHMENT_UNAVAILABLEat no charge. UScensusandcdare unchanged. - Canadian time zones: come from the same time zone boundaries as US ZIPs, with
source,productandattribution. A postal code’s zone haspct_of_zipnull(it is a point); an FSA crossing a zone line lists each zone covering at least 1% of it. About 1% of postal codes had a wrong zone (for example Saskatoon returnedAmerica/Adak); they are corrected. - ACS fields are the same in every year: every ACS field now appears in every year’s response on
/v2/zip,/v2/radiusand/v2/address. When Census published no item with that meaning in a year, the field is present withcolumnnulland every valuenull, instead of being left out (a 2011 response carried 394 of the 542 fields). Fields that held another item’s value are corrected, among themyear_moved_inandyear_builtbins,households.male_householder.living_alonebefore 2019, and repeated DP05 rows whose percent could be a count; 74 fields were added for items Census published under no name. - Server start-up (503 with
Retry-After): right after a server start or restart, while their data is still loading (usually under a minute),/v2/radius, its batch and/v2/suggestanswer HTTP 503SERVICE_WARMING_UPwith aRetry-Afterheader, not charged. They previously answered meanwhile from a slower fallback whose matches could differ. Once loaded, answers are unchanged./v2/healthreportsreadyand lists what is stillloading. - Credits: under heavy load a funded account no longer gets a false
402 CREDIT_INSUFFICIENT; when the service is genuinely at capacity the answer is HTTP 503SERVICE_POOL_EXHAUSTEDwithRetry-After, not charged. A not-found answer on/v2/zip,/v2/quick-zip,/v2/distanceor/v2/radiusthat cannot be paid now returns402 CREDIT_INSUFFICIENTinstead of a 200 saying a credit was charged, and an uncharged one reads"Not found (no credit charged).". The unused part of a batch reservation is now also returned to your free daily credits, on the day it was charged. - OpenAPI specification: response schemas are open (a response may gain fields, so clients should ignore fields they do not know) and have permanent names (
ZipResponse,ZipBatchItem,AddressResponse, …, replacing generated names such asZipResultBatchItemApiResponse; the old names stay as deprecated aliases, so SDKs built from them keep working). Fields sent in every response are markedrequired. Nothing is removed fromv2: see Versioning. No response changed. - Faster: Canadian
/v2/radiusspatial searches over large areas (worst cases from 6–8 s to under 0.4 s), repeat/v2/zipcalls on Canadian FSAs withstatcan, and batches, which now run several items at a time while keeping input order and credits. No response changed. - Address matching:
/v2/addressnow matches city-only addresses (no state or ZIP, for example123 Main St, Boise) when the street identifies one town, with the notice “State 'XX' inferred from the city.”, and handles more edge cases (recipient lines, unit prefixes, wrong ZIPs, alternate street names).
October 4, 2026
- Boundary families (response shape change): on
/v2/zipand/v2/address, congressional and state legislative districts moved out ofcensus.currentinto their own top-level objects,congressional_district.currentandstate_legislative.current(withupperandlower), and school districts are nowschool_district.current. Canadiancdreturnsfederal_electoral_district.current. Each family is keyed so historical periods can be added besidecurrent. A requested family with nothing found returns{"current": null}. Flags and prices are unchanged. - Congressional primary basis:
congressional_district.current.primarynow includesbasis. On/v2/zipthe primary is the district with the largest share of the ZIP’s area; on/v2/addressit is the USPS ZIP+4 congressional district. - Autocomplete notices:
/v2/suggestnow says “Showing results for” only when the results come from a spelling correction, not on correctly spelled words. - Agent documentation: a redesigned llms.txt and llms-full.txt, an AGENTS.md with credit-safety notes for AI agents, plus
robots.txtandsitemap.xml. - Faster ACS enrichments: ACS profiles on
/v2/zip,/v2/addressand/v2/radiusare now served from in-memory Census data files instead of database queries, so requests with many ACS years and profiles return significantly faster. Every historical year on/v2/radiusnow aggregates as quickly as the current year. - ACS values match Census exactly: every ACS value is now taken directly from the Census Bureau’s published files. Open-ended medians return the Census threshold (for example
250000for “250,000+”), and values Census could not compute returnnullrather than0. - ACS year availability on
/v2/zip: each ACS year block now includespublished. When Census published no data for the ZIP’s ZCTA in a requested year (ZCTAs are redrawn every decade),publishedisfalse, the profiles arenull, and anotesays so. Year blocks are returned newest first.
September 2026
- New address engine:
/v2/addressnow runs on a new matching engine that is faster and more accurate, with the same request and response format. - Address enrichments: coordinates, census, congressional and state legislative boundaries, school districts, and tract ACS profiles for 2011–2024, charged only for the boundary and tract data actually delivered.
- Error next steps: every error includes a public
url;FREE_TIER_LIMIT_CAPPEDnotices link to pricing, and key and credit messages include account links. - Reference corrections: batch formats and limits, HTTP 200 not-found behavior, emitted errors, per-minute rate limits, and credit formulas in this reference now match the API.
August 2026
- Improved address matching:
/v2/addressnow runs on a rebuilt matching engine that resolves more real-world input to a deliverable ZIP+4. Notably better on addresses that were previously hard to place: misspelled or heavily abbreviated street names, missing or incorrect ZIP codes, alternate and historical street names, and apartment or suite designators. - Relocated addresses: an address whose delivery point has been moved by the USPS now returns its current ZIP+4 instead of the retired one.
- No charge for undelivered responses: if a request is cancelled or times out before a response reaches you, no credits are charged.
March 2026
- New endpoint:
/v2/address— Address matching. Parse and validate raw US street addresses against USPS ZIP+4 delivery-point data. Returns standardized address, ZIP+4, parsed components, and delivery metadata. Launched with a 1-credit base; see September 2026 for enrichments.
March 12, 2026 — ZIP Codes API v2 launched
- Launch: ZIP Codes API v2 went live with ZIP, radius, distance and autocomplete lookups for US and Canadian postal codes, opt-in enrichments, and credit-based pricing.
Frequently Asked Questions
Do you use ZCTA boundaries?
How often is the data updated?
Can I try the API without signing up?
Do you support Canadian postal codes?
M5V) and full postal codes (6 characters like M5V2H1) on postal lookup, radius, distance, and suggest routes; address matching is US-only. Canadian codes work with timezone enrichment, and radius searches automatically include cross-border results. Some enrichments (ACS demographic data, state legislative districts, school districts) are US-only and unavailable for Canadian codes, with a notice and no credit charged; statcan returns Statistics Canada geography and federal electoral districts (census and cd on a Canadian code are answered as statcan).What is the difference between Quick ZIP and ZIP Details?
/v2/quick-zip) is a lightweight lookup returning core postal code info (city, state, coordinates, code type) with only timezone as an optional enrichment. It is optimized for fast, high-volume lookups like form validation or address autocomplete. ZIP Details (/v2/zip) returns comprehensive data including demographics, business statistics, delivery points, and multiple enrichments (census boundaries, ACS data, congressional districts, school districts, and more).How are credit costs calculated?
base × (1 + timezone + reverse_geocode). Base is 2 for ZIP+4 and 1 for ZIP, FSA, or Postal. Coordinates use the resolved type and add a reverse-geocode layer. ZIP+4 with timezone costs 4; coordinates resolving to ZIP+4 cost 4, or 6 with timezone. Unknown flags have no effect. ZIP Details credits = 1 + billable flags + reverse_geocode. Base is 1 for every code type, including ZIP+4 and Postal. Canadian unavailable flags cost 0 with a notice. Address: 1 + coordinates + delivered boundary/tract tags. Radius: distance-band base × (1 + flags + auto_radius). Distance: 1 per pair. Suggest limit bands: 1/2/3/5 credits. See Credit Costs.What happens if I exceed my rate limit?
Retry-After header indicating how many seconds to wait. Rate limits are enforced on a per-minute window and vary by subscription tier. Rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) are present on allowed authenticated API responses; health, docs, swagger, OPTIONS and auth-rejected requests do not carry them.Can I do reverse geocoding with coordinates?
lat,lon in the code parameter (e.g., code=34.1030,-118.4105) on any lookup endpoint. The API uses a two-step spatial search: first, it identifies which ZIP or FSA boundary polygon contains the coordinate (not just the nearest centroid — a point in rural Montana will correctly resolve to the ZIP that actually covers that location, not a distant one). Then, it finds the closest ZIP+4 or full postal code centroid within that parent boundary for precise sub-code resolution. Coordinates add one data layer on quick-zip (× its resolved base), and one credit on /zip. This works in both the US and Canada.What ACS data years are available?
acs_* flags return the most recent year (2024, covering 2020–2024 estimates). To request a specific historical year, append _YYYY to any ACS flag (e.g., include=acs_demographic_2022). You can request multiple years in the same call. Address tract ACS supports the same 2011–2024 with acs_tract_* flags. Field availability varies by year; see the ACS coverage table, as Census added new questions over time. New ACS releases are added annually, typically within weeks of the Census Bureau publication.Is there a batch/bulk endpoint?
Why does one ZIP return multiple counties, tracts, or school districts?
pct_of_zip and pct_of_area fields on each overlap quantify how much of each area is shared.What is the difference between centroid and spatial mode on /radius?
pct_inside is omitted; area_sq_mi is present when available. Spatial uses precise polygon intersection (STBuffer/STIntersects) — slower but includes pct_inside and area_sq_mi, supports up to 250mi, returns US ZIP codes + Canadian FSAs (no full postal codes). Use mode=spatial when you need overlap precision.