# ZIP Codes API - Complete Public Reference Base URL: https://api.zip-codes.com Human docs: https://api.zip-codes.com/docs OpenAPI: https://api.zip-codes.com/v2/openapi.json Playground: https://api.zip-codes.com/docs/playground Get a key and pricing: https://www.zip-codes.com/api/pricing Pricing as JSON: https://api.zip-codes.com/pricing.json API catalog (RFC 9727): https://api.zip-codes.com/.well-known/api-catalog Agent credit-safety notes: https://api.zip-codes.com/AGENTS.md Changelog: https://api.zip-codes.com/docs#changelog Updated: 2026-10-03 Location intelligence for applications and agents: postal lookup, every geographic overlap a ZIP Code has, a normalized demographic time series, spatial search and US address matching. Free accounts receive 2,500 credits/day and 60 requests/minute: enough to build and run a real application, not only a demo. ## For AI agents - The public demo key (below) works on the listed demo codes and addresses at 5 requests/minute; it is the way to confirm response shapes before using a user's key. - Every enrichment flag costs credits (formulas below). Batch routes charge every item, duplicates included, and need a paid plan: dedupe first and state the expected credits before a large run. - Unknown flags, unavailable enrichments and unpublished ACS years are not charged; they come back as notices, not errors, and retrying will not change them. - Every error carries a code, a message and a next-step url, plus retryable where it is known. - Bugs, feature requests, apps built on the API and research credits go to jharris@zip-codes.com (see Getting started and programs). Including the request_id helps. ## Use it for / not for Use it for: what a US ZIP or Canadian postal code really covers (every county, place, tract, block group, district and school district it crosses, with shares); ZIP-level and tract-level ACS over time; who lives within a radius; matching a US address to its ZIP+4 and that point's districts; Medicare CBSA assignments; autocomplete and distance. Not for: countries other than the US and Canada; routing, directions or map tiles; rooftop geocoding (an address resolves to its ZIP+4 point); historical boundaries (planned, not served yet). ## ZIP Codes versus Census ZCTAs This API's ZIP boundaries Census ZCTAs Built from USPS carrier routes, 2020 Census tabulation refreshed quarterly blocks, once a decade Coverage every ZIP; PO Box, unique and not every ZIP has one military ZIPs as points Overlaps every county, tract, district one area per code with pct_of_zip, pct_of_area ZIP+4 / addresses yes no Census: ZCTAs are "generalized areal representations of the geographic extent and distribution of the point-based ZIP Codes built using 2020 Census tabulation blocks", and "Not all valid ZIP Codes are represented by a 2020 ZCTA." (https://www.census.gov/programs-surveys/geography/guidance/geo-areas/zctas.html) ACS is published only for ZCTAs, so /zip ACS is the same-code ZCTA's and says so (geography_level "zcta"). ## What makes this data different US ZIP boundaries are carrier-route derived from USPS delivery routes and updated quarterly. /zip intersects ZIP polygons with Census, congressional, state legislative and school-district polygons, returning every overlap that passes the per-layer sliver thresholds, with pct_of_zip and pct_of_area on each: the intersection as a share of the ZIP and of the other polygon. A ZIP can reach further than expected; small slivers are real overlaps too. For example, 90210 overlaps CA-32 (62.83%), CA-36 (37.09%) and CA-30 (0.08%) of its ZIP area; /zip selects CA-32 as congressional primary by area share. That is not a measure of where most residents or addresses are. The base county / county_fips is the USPS assignment, usually the county with the most mail; census.current.county shows where the ZIP actually lies and flags the assigned county with is_usps_primary. For 02872, Prudence Island, RI, the base county is Bristol (44001); its Census county overlap is Newport (44005), with pct_of_zip:98.74 and is_usps_primary:false. The Census 2024 county layer uses Connecticut's nine planning regions as county-equivalents: 06103 returns Capitol Planning Region, FIPS 09110, and an address in Hartford resolves to that region. Boundary families census, congressional_district, state_legislative and school_district serve current only today; historical periods are planned. ACS 5-Year Data Profiles cover 14 years (2011-2024) on /zip and /radius, and census tracts for 2022-2024 on /address. The four profiles are demographic (DP05), social (DP02), economic (DP03) and housing (DP04): 528 normalized fields, 423 present in all 14 years. Several years can be requested together; unsuffixed and _2024 flags select the same current release (the 2020-2024 estimates) and are charged once. Census renumbers columns: 339 of these 528 fields change column code somewhere in 2011-2024. Field names stay the same across years, ZCTAs, tracts and radius aggregates, so a comparison over time is like for like. Each /zip and /address profile value names its source column, and each year block names its table, vintage and GEO_ID, so any number traces back to the Census year, table and column. For example, social.citizenship.foreign_born_total is DP02_0093 in 2015 and DP02_0095 in 2024. On /zip, ACS belongs to the same-code ZCTA, not the carrier-route ZIP polygon. PO Box, unique and military ZIPs are point-only and normally have no ACS. A requested /zip ZCTA/year without published ACS returns published:false with null profiles, and that year's flags are not charged. /address tract ACS has no published field. /radius mode=spatial intersects a geodesic circle with ZIP/FSA boundaries. pct_inside is the percentage of each ZIP/FSA inside the circle; area_sq_mi gives its area, and stats.acs holds US ACS aggregates weighted by the share of each ZIP inside the circle. Point-only ZIPs have pct_inside:null; centroid mode leaves it null. Use max for miles and min for an inner radius; centroid is the default; spatial mode needs an individual /v2/radius call. The code input on /zip, /quick-zip and /radius accepts a 5-digit ZIP, ZIP+4 (with or without its hyphen), Canadian FSA, full Canadian postal code (with or without its space), or latitude,longitude. For example, 34.0901,-118.4065 resolves to ZIP+4 90210-2940 on /zip and /quick-zip (reverse_geocode is billed under each endpoint's credit formula). On /radius a coordinate is the search center directly; a ZIP+4 searches from its ZIP. /address matches a US address to a ZIP+4 record and enriches at that record's point: the containing Census geographies (in Connecticut, the planning region), congressional and state legislative districts, school district, and ACS for the containing tract under acs.tract. Its congressional primary is the USPS ZIP+4 district, falling back to the district containing the point; primary.basis identifies the rule. US /zip base fields include delivery-point counts, ZIP+4 counts, city aliases, metro areas and elevation. include=medicare adds the ZIP's CMS assignment: cbsa_code, cbsa_name, cbsa_type, rating_area_id and ssa_state_county_code, with multi_county_notice when relevant. Canadian FSA Census overlaps use Statistics Canada 2021 boundaries with pct_of_fsa/pct_of_area; full postal codes use assigned Census geographies, including dissemination areas and blocks. Canada has no ACS, state_leg or school_district enrichment. ## Sources and refresh US ZIP boundaries: carrier-route derived, quarterly. US ZIP base data and ZIP+4: monthly. Census boundaries: TIGER/Line cartographic 2024 (119th Congress; 2024 state legislative districts). School districts: NCES 2024-2025. ACS: 5-Year Data Profiles 2011-2024, annual. Canada: Canada Post codes monthly; Statistics Canada 2021 geographies. ## What you can build - Location assistants: accept postal codes, coordinates or US addresses and explain postal identity, geographic overlaps and address-level geography. - Site and service-area research: combine spatial radius shares, delivery-point counts and US ACS aggregates to compare candidate areas. - Community dashboards: compare income, housing, education and demographic measures across the ACS years, with source-column provenance. - Civic and school-district tools: show a ZIP's overlaps, or the districts containing a matched US ZIP+4 point. - Medicare geography tools: attach CMS CBSA, rating area and SSA county codes to ZIP records, including multi-county notices. - US/Canada location interfaces: combine autocomplete, postal lookup, distance and bearing, and radius search. ## Getting started and programs Free accounts receive 2,500 credits/day and 60 requests/minute. Get a key and pricing: https://www.zip-codes.com/api/pricing Authenticate with X-Api-Key: YOUR_API_KEY or ?key=YOUR_API_KEY. Start with /v2/zip?code=90210&include=census,cd to see geographic overlaps. Add acs_economic,acs_economic_2015 to compare income profiles across years. /v2/radius?code=90210&max=5&mode=spatial&include=acs_demographic gives an area-weighted demographic summary. For US addresses, use /v2/address with include=census,cd,acs_tract_demographic. Request costs depend on the endpoint and enrichments; formulas follow below. Bugs and feature requests: jharris@zip-codes.com. Every message is read, and feature requests from agents and developers shape what gets built. Include the request_id and your use case. Built something with the API? Send it to jharris@zip-codes.com; apps and tools we feature get 100,000 credits. College and university researchers: email jharris@zip-codes.com with a line about your project; we grant free credits for genuine research. ## Coming soon (planned, not available) - Historical administrative boundaries keyed by period, the way ACS is keyed by year: Connecticut's former counties, congressional districts per Congress, state legislative districts per legislative year, for comparison with the ACS series. - ACS time series at higher geographies (tract, county, place) for modeling. - An autonomous agent purchase path for pay-as-you-go credits, through Stripe. - An MCP server with extra tools. ## Authentication and requests Use X-Api-Key: YOUR_API_KEY or ?key=YOUR_API_KEY. Authorization: Bearer is not accepted. Health, documentation, OpenAPI, Swagger and Postman require no key. Per-key IP and Origin allowlists are enforced when configured. CORS allows any origin. The six single lookup routes accept GET query parameters or POST JSON using the same names. Body values win; missing body fields fall back to the query. Send Content-Type: application/json; POST bodies under /v2 are treated as JSON even if this header is omitted. Batch routes are POST JSON only; health is GET only. Optional enrichments default to none. Include flags are split on commas, trimmed, lowercased, hyphens normalized to underscores, and de-duplicated. ### Public demo key (for evaluation only) A shared demo key works without signup, restricted to demo codes and rate-limited to 5 requests/min per IP: X-Api-Key: zc_test_DEMOAPIKEY000000000000 Demo codes — US ZIP: 90210, 10001, 10118, 32504, 00601, 96950, 33139, 60601, 98101, 30301. CA FSA: M5V, K1A, V6B, T2P, H3A. CA Postal: M1R0E9, V5K0A1, K1A0A1, T1X0L3, H1A0A1. Use it to verify integration shape, then switch to a free signup key. Demo addresses (/v2/address and /v2/address/batch; matched ignoring case, commas, periods and extra spaces): 1600 Pennsylvania Ave NW, Washington DC 1 Arizona Memorial Pl, Honolulu HI 96818 300 Alamo Plaza, San Antonio TX 78205 3764 Elvis Presley Blvd, Memphis TN 38116 400 Broad St, Seattle WA 98109 11 N 4th St, St Louis MO 63102 4 Jersey St, Boston MA 02215 150 Calle de la Fortaleza, San Juan PR 00901 ATTN Admissions, 77 Massachusetts Ave, Cambridge MA 02139 200 E Colfax Ave, Denver 80203 1 Infinite Loop, Cupertino, CA ATTN Leasing Office, 233 S Wacker Dr Ste 4700, Chicago IL 60606 350 Fifth Avenue, New York NY 10118 20 W 34th St, New York NY 10001 1060 W Addison St, Chicago IL 60613 3700 SW 328th St, Homestead FL 33033 8039 Beach Blvd, Buena Park CA 90620 1 Rocket Rd, Hawthorne CA 90250 PO Box 1142, Beverly Hills CA 90213 750 N Glebe Rd, Arlington VA 22203 1600 Pennsylvania Ave NW, Washington DC 20500 9641 Sunset Blvd, Beverly Hills, CA 90210 350 5th Ave, New York, NY 10118 4034 Elmcrest Dr, Pensacola, FL 32504 Cleanup and no-match examples, also accepted: 123 Nowhere Lane Faketown ZZ 999 Nonexistent St, Beverly Hills, CA 90210 1600 pennsylvania ave nw washingtn dc 1600 pensylvania ave nw washingtn dc 350 fifth avenue apt 101 new york ny 350 5th ave apt 101 new york ny po box 1234 beverly hills ca 90210 p.o. box 1234 beverly hills ca 90210 400 BROAD st seattle WA Demo keys are not billed. Free accounts receive 2,500 credits/day; subscribers receive no daily free credits. Monthly credits: Developer 100,000 ($49); Professional 350,000 ($149); Business 1,500,000 ($499). Credit packs (one-time, never expire, stack on top of the free daily allowance): Starter 25,000 ($19); Standard 65,000 ($49); Growth 150,000 ($99); Pro 300,000 ($179); Enterprise 2,000,000 ($799). Spending order: free daily credits (free accounts), then subscription credits, then purchased packs. Worked example: 50,000 ZIP lookups with timezone a month = 100,000 credits; about 75,000 come from the free allowance and one $19 Starter pack covers the rest, cheaper than a $49 subscription. A subscription replaces the free daily credits and adds batch access and higher rate limits. Prices as of 2026-10-03; https://api.zip-codes.com/pricing.json is generated from the live billing configuration. ## Response contract Normal lookup envelope (shape notation; ? means optional): {success:true, request:{...}, results:[...], meta:{request_id,version,timing_ms, credits:{used,source,remaining,breakdown:{base,enrichments:{},note?}},notices?}} request_id is a ULID. Version is "2.0.0". Headers include X-Request-Id and X-Api-Version: 2.0. Credits include a breakdown. Items for quick-zip, zip, radius, distance and suggest are FLATTENED: {success,input?,error?,notices?,...endpoint_fields}. There is no data wrapper. Address retains data, including in its flattened batch item. Health has a top-level data object rather than results. Null model properties are omitted, except explicitly preserved nulls such as census sections and ACS metric values. Whole-request error shape: {success:false,request?,error:{code,message,retryable?,url},meta:{request_id,version, timing_ms,credits?,notices?,rate_limit?}}; results is omitted. Middleware echoes request:{method,path} and marks only 429/503/504 as retryable. Well-formed unknown postal codes return HTTP 200 per-item failures, not HTTP 404. The envelope stays success:true and results contains {success:false,input:{code}, error:{code,message,url}}. Single address no-match instead has top-level success:false and an error inside results[0], with no top-level error. Inspect both levels. Validation failures are HTTP 400 and cost 0 credits. Responses use JSON, support Brotli/Gzip, and carry Cache-Control: no-store. ## Code input formats code (and distance from/to) is a string of at most 50 characters: - US ZIP: 5 digits, e.g. 90210. - ZIP+4: 12345-6789 or 123456789. - Canadian FSA: A1A, e.g. M5V. - Canadian postal: A1A1A1, optional space, e.g. M5V 2H1. - Coordinates: latitude,longitude in decimal degrees, latitude -90..90, longitude -180..180. On /zip and /quick-zip, a coordinate can resolve to a ZIP+4; reverse_geocode follows the endpoint's credit formula. On /radius, the coordinate directly centers the search. ## Endpoints ### GET or POST /v2/quick-zip code: required. include: optional string; only timezone has an effect. Flattened results[0] includes success, country, code, code_type, parent_code?, city, state, state_name, state_fips?, county?, county_fips?, multi_county?, classification?, classification_name?, municipality?, location:{lat,lon}, detail?, timezone?. ZIP+4 detail includes zip4, record_type, carrier_route, street:{addr_low,addr_high,prefix,name,suffix,postfix,odd_even,sec_addr_abbr, sec_addr_low,sec_addr_high,sec_addr_odd_even,building_name,address} when available. timezone is a single object with source, iana_name, name, abbreviation, abbreviation_dst, utc_offset, utc_offset_dst (numeric hours), observes_dst. Canadian postal fallback to FSA applies on quick-zip and distance only, with FALLBACK_USED. ZIP+4 coordinates use the ZIP+4 centroid when available. Cost = base * (1 + timezone + reverse_geocode), each indicator 0 or 1. Base is 2 for ZIP+4, 1 for ZIP/FSA/Postal. Coordinates use the resolved type. ZIP+4 + timezone = 4; coordinates resolving to ZIP+4 = 4, or 6 with timezone. Not found costs 1, or 2 for coordinate input. Single unknown ZIP message: "ZIP code {zip} not found." Batch: "Code {code} not found." curl -H "X-Api-Key: YOUR_API_KEY" "https://api.zip-codes.com/v2/quick-zip?code=90210&include=timezone" POST body: {"code":"90210","include":"timezone"} ### GET or POST /v2/zip code: required. include: optional comma-separated string of the flags below. Flattened results[0] includes postal identity/location fields plus area_codes, elevation:{ft,m}, metro, zip_info (US), postal_info (Canada), city_aliases, detail, and requested enrichments when available. metro: region, division, csa/csa_name, msa/msa_name, pmsa/pmsa_name. zip_info: classification, classification_name, facility_code, city_delivery_indicator, intro_date, land_area_sq_mi, water_area_sq_mi, zip4_count, delivery_points:{total_active,business,residential,po_box,single_family,multi_family}. postal_info: record_type, address_type, fsa, delivery_postal_code, fsa_population, fsa_dwellings, fsa_dwellings_occupied, fsa_postal_code_count. | Include flag | Response path below results[i] | Content / coverage | |---|---|---| | timezone | timezone | US/CA; source, zones array; numeric UTC offsets, pct_of_zip; no primary shortcut | | census | census.current | US/CA census geography | | cd | congressional_district.current (US); federal_electoral_district.current (CA) | US congressional / CA federal electoral districts | | state_leg | state_legislative.current.upper / lower | US senate/house | | school_district | school_district.current | US NCES districts | | demographics | demographics | Legacy US population, race, age, households and business summary (one vintage); use acs_* for the dated 2011-2024 profiles. payroll_q1/payroll_annual are in full USD | | medicare | medicare | US cbsa_code, cbsa_name, cbsa_type, rating_area_id, ssa_state_county_code, multi_county_notice? | | acs_demographic | acs.current.demographic | US DP05 | | acs_social | acs.current.social | US DP02 | | acs_economic | acs.current.economic | US DP03 | | acs_housing | acs.current.housing | US DP04 | | acs_{profile}_YYYY | acs["YYYY"].{profile} | Each of the four profiles, every year 2011 through the current year (2024). The current year's suffix (_2024) is the same request as the unsuffixed flag: it appears under current and is charged once | Each boundary flag returns its own top-level family with a current key: census, congressional_district (cd), state_legislative (state_leg), school_district; on Canadian codes cd returns federal_electoral_district. Only current is available today. Historical boundaries keyed by period are planned, each family on its own cycle: boundary vintage for census, Congress number for congressional_district (e.g. "118"), legislative year for state_legislative, school year for school_district. A requested family with nothing found is present with current: null; an unrequested family is absent. US census.current contains source/year/vintage, county, county_subdivision, place, tract and block_group arrays; metro_micro_cbsa_statistical_area and metropolitan_cbsa_division are single objects. congressional_district.current = {source,year,vintage,congress,districts,primary} with integer district_number. state_legislative.current = {source,year,vintage,legislative_year,upper,lower}. Empty lookups are null. ZIP overlaps have pct_of_zip, pct_of_area, land/water areas. School district structure matches address below, plus ZIP area-overlap percentages. Cost = 1 + billable flags + reverse_geocode, for EVERY code type including ZIP+4 and Postal. Canada: ACS, demographics, medicare, school_district, state_leg are unavailable, cost 0, and produce ENRICHMENT_UNAVAILABLE. Unknown flags are ignored free with VALIDATION_UNKNOWN_ENRICHMENT on single /zip; /zip/batch emits no unknown-flag notice. ZIP with timezone,census,cd costs 4. Unknown ZIP costs 1: NOT_FOUND_ZIP "ZIP code {zip} not found."; FSA: NOT_FOUND_FSA "FSA {fsa} not found."; postal: NOT_FOUND_POSTAL "Postal code {postal} not found.". Coordinates with no boundary: NOT_FOUND_COORDS_NO_BOUNDARY. If the parent ZIP exists but the +4 does not, the item succeeds with detail:{zip4,valid:false}. Bad code format returns HTTP 400. curl -H "X-Api-Key: YOUR_API_KEY" "https://api.zip-codes.com/v2/zip?code=90210&include=timezone,acs_demographic" POST body: {"code":"90210","include":"timezone,acs_demographic"} Canadian census.current uses source Statistics Canada 2021 Census, year 2021. Sections include census_division, census_subdivision, tract, metro_area, economic_region, population_centre, consolidated_subdivision, and designated_place. FSA lookups also include province and overlap percentages (pct_of_fsa); postal lookups use pre-assigned areas and add dissemination_area/dissemination_block. cd on a Canadian code returns federal_electoral_district.current = {source:"Statistics Canada 2021 Census",year:2021,districts:[...]}; FSA items carry pct_of_fsa. Canada never returns congressional_district, state_legislative or school_district. Canadian federal electoral districts use the 2013 Representation Order boundaries distributed with the 2021 Census, not the 2023 redistribution. ### ACS field reference (ZIP and address profiles) /zip uses acs.current (2024) or acs["YYYY"] for earlier years 2011-2023, geography_level:"zcta", geo_id such as "860Z200US90210". /address uses acs.tract.current / ["2023"] / ["2022"], geography_level:"tract", geo_id beginning "1400000US". Year blocks contain source, product, year, vintage, geo_id, geography_level, tables and requested profiles. /zip year blocks also carry published (false = Census published no data for that ZCTA in that year; the profiles are null, a note says so, and that year is not charged), and come newest year first. Profile/table mapping: demographic DP05, social DP02, economic DP03, housing DP04. The current year is the newest ACS release. When Census publishes the next one (2025), current moves to it and 2024 becomes acs["2024"], requested as _2024; on every endpoint the current year's suffix and the unsuffixed flag are one request, charged once. Metrics contain column (Census column ID), est (estimate), moe (margin of error), and where applicable pct and pct_moe. Suppressed/unavailable values can be null. Profile field names are identical for tract and ZIP, and in every year. The four tables serve 528 fields; 423 are present in all 14 years (Census adds and drops rows, so the rest cover part of the range). Sections, as the JSON keys under each profile: - demographic (DP05, 94 fields): sex_and_age, race, race_combination, hispanic_or_latino, total_housing_units, citizen_voting_age. e.g. demographic.sex_and_age.total_population, median_age; race.one_race.white. - social (DP02, 154 fields): households, relationship, marital_status, fertility, grandparents, school_enrollment, education, veterans, disability, residence (1 year ago), place_of_birth, citizenship, year_of_entry, world_region, language, ancestry, computers_internet. e.g. social.citizenship.foreign_born_total; households.avg_household_size. - economic (DP03, 137 fields): employment, commuting, occupation, industry, class_of_worker, income, health_insurance, poverty. e.g. economic.income.households.median; commuting.mean_travel_time. - housing (DP04, 143 fields): occupancy, structure, year_built, rooms, bedrooms, tenure, year_moved_in, vehicles, heating_fuel, characteristics (plumbing, kitchen, telephone), occupants_per_room, value, mortgage, owner_costs, owner_costs_pct, rent, rent_pct. e.g. housing.occupancy.vacant; value.median. Human field reference: https://api.zip-codes.com/docs#enrich-acs ### GET or POST /v2/radius | Parameter | Allowed values / default / limits | |---|---| | code | Required center, any supported code or lat,lon; <=50 characters | | max | Required miles >0; <=500 centroid / <=250 spatial; or "auto" / "auto-commute" | | min | Optional miles >=0 and < max; default 0 | | mode | centroid (default) or spatial | | include | Optional timezone and acs_{profile}[_YYYY], years 2011-2024; unsuffixed = current (2024), the same request as _2024 | | limit | Optional positive integer; truncates results and sets summary.total_available | Free tier: <=100 miles centroid / <=50 spatial. Test keys: <=10 miles. Centroid uses code centroids (US ZIP and CA postal); spatial intersects ZIP/FSA boundaries and returns pct_inside. Searches can cross the US/Canada border. Centroid caps at 10,000 with RESULTS_CAPPED. Auto radius accepts coordinates too: auto uses density (3-150 miles); auto-commute uses commute data (3-100 miles), falling back to density with AUTO_COMMUTE_FALLBACK if necessary. Boundary flags census, cd, state_leg, school_district and geography are dropped with ENRICHMENT_UNAVAILABLE and no charge. Use /zip or /address for boundaries. Flattened result: {success,input?,search:{center,max,min,mode,auto_mode?}, matches:[{country,code,code_type,city,state,county?,county_fips?,classification?, classification_name?,location:{lat,lon},distance_miles,pct_inside?,area_sq_mi?, timezone?}],summary,stats?}. stats.acs.current / ["YYYY"] holds ACS aggregates, newest year first. Each year block carries source, product, year, vintage, zips_with_acs_data, zips_without_acs_data, coverage_pct and aggregation_note, then the profiles; summaries carry est and pct (no moe, no column). Counts are summed; spatial counts use value * (pct_inside / 100). Point-only ZIPs have pct_inside:null and count fully when ACS is available; in centroid mode every ZIP counts fully. Medians and means are population-weighted; rates and percentages are recomputed from the aggregated sums. Unrequested profiles are present as null. summary contains total_count, us_zip_count, ca_fsa_count, ca_postal_count, and optional total_available. Request echo includes min and limit only if supplied. Unknown center: HTTP 200 per-item NOT_FOUND_ZIP / NOT_FOUND_FSA / NOT_FOUND_POSTAL, "Center code '{code}' not found.", costs 1. Invalid mode: "Invalid mode: '{mode}'. Valid values: centroid, spatial". Cost = ceil(radius/100) * (1 + distinct timezone/ACS requests + auto_radius) in centroid mode; replace /100 with /50 for spatial. auto_radius is 1 for either auto option, otherwise 0. No per-returned-code charge. curl -H "X-Api-Key: YOUR_API_KEY" "https://api.zip-codes.com/v2/radius?code=48226&max=25&include=timezone" POST body: {"code":"48226","max":25,"include":"timezone"} ### GET or POST /v2/distance from and to: required strings, any supported code or lat,lon, <=50 chars each. No include parameter. Cost: 1 credit per pair; timezone included free. Flattened result: {success,input?,from:{country,code,code_type,city,state,county?, location:{lat,lon},timezone,input_type},to:{...},distance:{miles,km},bearing}. input_type: code or coordinates. Bearing is degrees. Not-found: HTTP 200 per-item NOT_FOUND_FROM / NOT_FOUND_TO, "Code '{input}' not found in any boundary table", charged 1. Missing fields cost 0. curl -H "X-Api-Key: YOUR_API_KEY" "https://api.zip-codes.com/v2/distance?from=90210&to=10001" POST body: {"from":"90210","to":"10001"} ### GET or POST /v2/suggest | Parameter | Allowed values / default / limits | |---|---| | q | Required nonempty query, <=200 characters | | limit | Integer 1-500, default 10; free/test capped at 15 with FREE_TIER_LIMIT_CAPPED | | include | Optional comma-separated types; default all nine: zip, place, county, cbsa, fsa, state, school_district, ca_postal, uspscity | | state | Optional US state / Canadian province abbreviation | | country | Optional US or CA; default both | | proximity | Optional auto, lat,lon, IP, FSA, 5-digit ZIP, 2-letter state/province; ranking bias | Flattened results[0] = {success:true,total_matched,matches:[...]}. Matches have type, name, state/province, country and available location/population, identifiers (fips/geoid/cbsa_code), bbox, and type-specific fields. No data wrapper. Cost by effective requested limit: <=15 = 1; <=50 = 2; <=150 = 3; 151-500 = 5. There is NO suggest batch; POST /v2/suggest is a single call. curl -H "X-Api-Key: YOUR_API_KEY" "https://api.zip-codes.com/v2/suggest?q=spring&limit=3" POST body: {"q":"spring","limit":3,"include":"place,zip","state":"MO","country":"US","proximity":"auto"} ### GET or POST /v2/address US address matching with ZIP+4. Parameters: address (required string), include (optional string; default none). With a ZIP, city and state are optional; without a ZIP, provide city and state. Input <=500 raw characters, 9-300 after cleanup. GET include is comma-separated. POST /v2/address include MUST be a string; an array returns HTTP 400. POST /v2/address/batch accepts a string or array. Single matched result: {input:string,success:true,data,notices?}. Data contains confidence (high/medium/low), formatted_address, address_line1, address_line2, address_components, details, pbsa_indicator only when true, and optional enrichments. Components include number, pre_directional, street, street_pre_type, suffix, post_directional, secondary_type, secondary_number, city, state, zip, plus4, country. Nullable model fields are omitted. Details contain record_type, carrier_route, facility, county, congressional_district, location_block_group, finance_number, building_name, government_building, base_alternate_code, lacs_status, municipality_key, urbanization_key, preferred_last_line_key when available. Match notices include class and codes MATCHED, PARTIAL_MATCH, ZIP_CORRECTED, CITY_CORRECTED, STATE_CORRECTED, STREET_CORRECTED, SECONDARY_NEEDED, SECONDARY_NOT_VERIFIED, INPUT_NOT_USED. Paths below are relative to results[0].data for single /address or results[i].data for /address/batch: | Flag | Adds | Path below data | Credits | |---|---|---|---| | coordinates | ZIP+4 point, 6 decimals | location:{latitude,longitude} | +1 whenever requested, even no match | | census | County, subdivision, place, tract, block group, metro/division | census.current | +1 when delivered | | cd | Congressional districts | congressional_district.current | +1 when delivered | | state_leg | Upper/lower legislative districts | state_legislative.current.upper / lower | +1 when delivered | | school_district | NCES school districts | school_district.current | +1 when delivered | | acs_tract_demographic | DP05 | acs.tract.{current/2023/2022}.demographic | +1 per delivered year | | acs_tract_social | DP02 | acs.tract.{current/2023/2022}.social | +1 per delivered year | | acs_tract_economic | DP03 | acs.tract.{current/2023/2022}.economic | +1 per delivered year | | acs_tract_housing | DP04 | acs.tract.{current/2023/2022}.housing | +1 per delivered year | Each tract flag accepts no suffix, _2024, _2023, or _2022. No suffix selects current 2024; _2024 is the same tag and counts once with the unsuffixed alias. There are 12 distinct tract profile/year tags. Other years and /zip-only flags such as timezone and acs_demographic are unknown on /address: ignored free with VALIDATION_UNKNOWN_ENRICHMENT, "Unknown enrichment flag(s) ignored: {flags}." Enrichments use the matched ZIP+4 record's centroid. Returned location is rounded to 6 decimals and omitted unless coordinates was requested. The tract containing the point is the same for every requested year. census, congressional_district, state_legislative and school_district are separate families with the /zip names, order and structure; each holds its data under current. A requested family with nothing found is present with current: null; an unrequested family is absent. census.current, congressional_district.current and state_legislative.current carry source "Census Bureau TIGER/Line Cartographic Boundaries", year 2024, vintage "2024". census.current arrays: county (with is_usps_primary), county_subdivision, place, tract, block_group. Single objects: metro_micro_cbsa_statistical_area, metropolitan_cbsa_division. congressional_district.current adds {congress,districts:[...],primary:{geoid,state,district_number,basis}}; district_number is an integer. On /address, primary uses the USPS ZIP+4 district, falling back to the district containing the matched point. On /zip, primary uses the largest ZIP-area share. Read primary.basis to distinguish these rules. state_legislative.current adds {legislative_year,upper:[...], lower:[...]}. Empty lookups are null. A completed empty boundary lookup still counts as delivered and is charged. Point rows have no pct_of_zip, pct_of_area or intersection-area fields. details.congressional_district is a different field: the USPS congressional district code string from the match details, independent of the cd flag. school_district.current = {source,school_year,districts:[{leaid,name,type,grade_low, grade_high,state_fips,land_area_sq_mi,water_area_sq_mi,centroid?,office?,locale?, nces_codes}]}. Fixture source: "National Center for Education Statistics (NCES)"; school_year: "2024-2025". acs.tract.current / ["2023"] / ["2022"] blocks contain {source:"U.S. Census Bureau", product:"ACS 5-Year Estimates Data Profiles",year,vintage:"{year-4}-{year}", geo_id:"1400000US{11-digit tract GEOID}",geography_level:"tract",tables:{profile:table}, :{...}|null}. See the ACS field reference above and https://api.zip-codes.com/docs#enrich-acs for the same field names as /zip. A missing profile may be null; acs is omitted if nothing was delivered. Cost = 1 + requested coordinates + each delivered boundary flag + each delivered tract tag. Available credits are checked against requested enrichments first. Unavailable boundary/tract data is not charged and produces ENRICHMENT_UNAVAILABLE with "matched location or requested enrichment data is unavailable; not charged." No match costs 1 (+1 if coordinates requested), performs no enrichments, and requested boundary/tract flags produce one ENRICHMENT_UNAVAILABLE notice ending "address was not matched; not charged." Single no-match: HTTP 200, top-level success:false, no top-level error; results[0] = {input,success:false,error:{code,message,url}}: - 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 candidate list is returned for ambiguity. GET example: 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" Captured response excerpt (fields trimmed, original credits 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.0, "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.0, "pct_moe": null }, "occupied": { "column": "DP04_0002", "est": 1170, "moe": 149, "pct": 89.8, "pct_moe": 7.9 } } } } } }, "census": { "current": { "source": "Census Bureau TIGER/Line Cartographic Boundaries", "year": 2024, "vintage": "2024", "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": "Census Bureau TIGER/Line Cartographic Boundaries", "year": 2024, "vintage": "2024", "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": "Census Bureau TIGER/Line Cartographic Boundaries", "year": 2024, "vintage": "2024", "legislative_year": 2024, "upper": [ { "geoid": "06024", "name": "24" } ], "lower": [ { "geoid": "06051", "name": "51" } ] } }, "school_district": { "current": { "source": "National Center for Education Statistics (NCES)", "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": "01M3SX2V9NY9MRRAMWVJG0C09E", "version": "2.0.0", "timing_ms": 77, "credits": { "used": 8, "source": "subscription", "remaining": 1485728, "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 } } } } } POST /v2/address/batch body: { "addresses": [ "1600 Pennsylvania Ave NW, Washington DC 20500", "not a real address 99999" ], "include": [ "coordinates", "acs_tract_economic" ] } Captured batch response excerpt: { "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 } } } Captured single no-match response: { "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." } ] } } ### GET /v2/health No key, no credits, no parameters. Returns {success,data:{status,database:{connected, latencyMs},version},meta:{version,timing_ms,...}}. Status: healthy; degraded (connected but DB latency >500 ms); unhealthy (DB unreachable, HTTP 503). Otherwise HTTP 200. ## Five batch endpoints Paid subscription required. POST JSON only. Whole-request errors: HTTP 400 missing body/field or VALIDATION_BATCH_TOO_LARGE; HTTP 403 AUTH_BATCH_REQUIRES_SUBSCRIPTION or AUTH_TEST_KEY_RESTRICTED; HTTP 402 CREDIT_INSUFFICIENT. Valid batches return HTTP 200 even when items fail. | Route | Body (shape notation) | Maximum | |---|---|---| | /v2/quick-zip/batch | {codes:[strings],include?} | 100 | | /v2/zip/batch | {codes:[strings],include?} | 100 | | /v2/radius/batch | {searches:[{code,max?,min?,include?,limit?}],include?,max?,min?,mode?} | 100 | | /v2/distance/batch | {pairs:[{from,to}]} | 100 | | /v2/address/batch | {addresses:[strings],include?} | 250; 120-second timeout | include accepts a comma-separated string or array (up to 50 string entries), including per-search radius include. Radius max/min/include at the top provide per-search defaults; item values override. max must be supplied per item or as a default. mode is batch-level only, defaults to centroid; spatial returns HTTP 400 SPATIAL_MODE_DISABLED_BATCH. Radius item input echo: {index,code,max}. Single-call formats, limits and include flags apply unless stated here. Example bodies: quick-zip: {"codes":["90210","M5V"],"include":"timezone"} zip: {"codes":["90210","10001"],"include":["timezone","census"]} radius: {"searches":[{"code":"90210"},{"code":"10001","max":10,"min":2,"include":"timezone","limit":20}],"max":25,"min":0,"include":["acs_demographic"],"mode":"centroid"} distance: {"pairs":[{"from":"90210","to":"10001"}]} address: {"addresses":["1600 Pennsylvania Ave NW, Washington DC 20500"],"include":["coordinates","acs_tract_economic"]} Envelope: {success:true,request:{codes_count|searches_count|pairs_count|addresses_count,...}, results:[items],meta:{request_id,version,timing_ms,credits:{used,source,remaining, breakdown:{base,enrichments,note?}},batch:{requested,succeeded,failed},notices?}}. Successful postal/radius/distance items are flattened with input alongside their fields. Failed item: {success:false,input:{...},error:{code,message,url}}. Address item: {success,input:{address},matched,data?,notices?}. Unmatched: {success:true,input:{address},matched:false}; no engine reason code, counted in meta.batch.succeeded. Rejected/failed items have success:false and error; cost 0. Codes: VALIDATION_MISSING_PARAM, VALIDATION_INPUT_TOO_LONG, VALIDATION_INPUT_TOO_SHORT, SERVICE_UNAVAILABLE, INTERNAL_ERROR. Item messages include "Address exceeds maximum length of 500 characters.", "Address exceeds maximum length of 300 characters after cleanup.", "Address too short to parse (minimum 9 characters).", "Address matching service became unavailable during request.", "Address matching failed for this item." Credits are summed per item. Duplicates are processed and billed individually, with BATCH_DUPLICATES_DETECTED. Credits are reserved up front; unused refunded; disconnect/timeout refunds all. Address envelope notices aggregate unavailable boundary/tract enrichments and report unknown flags. Processed unmatched address: 1 credit (+1 if coordinates requested). Postal not-found: 1, except quick-zip coordinate not-found: 2. Invalid-format/missing-field items: 0. No suggest batch exists; POST /v2/suggest is a single request. ## Credit formulas | Endpoint | Credits | |---|---| | quick-zip | base * (1 + timezone + reverse_geocode); base ZIP+4=2, ZIP/FSA/Postal=1 | | zip | 1 + billable flags + reverse_geocode; all code types base 1 | | radius | ceil(r/100) centroid or ceil(r/50) spatial, multiplied by (1 + distinct timezone/ACS flags + auto_radius) | | distance | 1 per pair | | suggest | Effective requested limit <=15:1, <=50:2, <=150:3, else:5 | | address | 1 + requested coordinates + delivered boundary flags + delivered tract tags | | batch | Sum of items, including duplicates | | health | 0 | Address unavailable boundary/tract flags are free; a completed empty boundary lookup remains billable. No-match costs and Canadian unavailable flags are specified under each endpoint. Validation costs 0. ## Rate limits | Rate tier | Requests/minute | |---|---| | Free (default) | 60 | | Restricted | 20 | | Developer | 300 | | Professional | 300 | | Business | 600 | | Enterprise | 1800 | | Demo (per IP) | 5 | One-minute sliding window (six 10-second segments). No hourly or daily request limits; daily free credits are separate. Allowed authenticated API requests carry X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp, now + 60 seconds), 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. Free-tier 429 example: { "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 } } } ## Errors and notices Every error, including per-item failures, includes a public url for next steps: - https://www.zip-codes.com/api/account: 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, CREDIT_INSUFFICIENT. - https://www.zip-codes.com/api/pricing: AUTH_BATCH_REQUIRES_SUBSCRIPTION, AUTH_FREE_TIER_RADIUS_EXCEEDED, RATE_LIMIT_MINUTE. - https://api.zip-codes.com/docs: all VALIDATION_* errors, MISSING_VERSION, UNKNOWN_VERSION, BAD_REQUEST, SPATIAL_MODE_DISABLED_BATCH, NOT_FOUND_COORDS_NO_BOUNDARY. - https://www.zip-codes.com/api/: all other errors. - https://api.zip-codes.com/docs#authentication: overrides AUTH_MISSING_KEY only when the key was sent in Authorization: Bearer. Only FREE_TIER_LIMIT_CAPPED notices include the optional url, pointing to https://www.zip-codes.com/api/pricing. All other notices omit url. | HTTP | Code | Message / variants | |---|---|---| | 401 | AUTH_MISSING_KEY | "API key is required. Provide via X-Api-Key header or ?key= query parameter. Get a key at https://www.zip-codes.com/api/account." | | 401 | AUTH_MISSING_KEY (key in Authorization) | "API key detected in Authorization header. This API uses the X-Api-Key header, not Bearer authentication. Move your key to: X-Api-Key: zc_..." | | 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 require a paid subscription. Upgrade 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." | | 400 | UNKNOWN_VERSION | "Unknown API version. Use /v2/ prefix." (/v3 and later) | | 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_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 {path}. Check the API documentation for allowed 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." or "POST requests require Content-Type: application/json. Send parameters as a JSON body, not form-encoded data." A body that is not valid JSON returns 400 instead. | Braces in messages are placeholders. Parameter validation can also return parameter-specific messages under VALIDATION_INVALID_PARAM. Test restriction messages append this exact suffix (including its leading space): " Demo US ZIPs: 90210, 10001, 10118, 32504, 00601, 96950, 33139, 60601, 98101, 30301. Demo CA: M5V, K1A, V6B, T2P, H3A, M1R0E9, V5K0A1, K1A0A1, T1X0L3, H1A0A1." Address restrictions use a different suffix, naming demo addresses instead: " Demo addresses include: 1600 Pennsylvania Ave NW, Washington DC 20500; 9641 Sunset Blvd, Beverly Hills, CA 90210; 350 Fifth Avenue, New York NY 10118; PO Box 1142, Beverly Hills CA 90213. Full list: https://api.zip-codes.com/llms-full.txt" Single address restriction before that suffix: "Test API keys are restricted to demo addresses only. Use a real API key to look up any address." Service-not-ready: "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 carry 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 non-fatal notice on HTTP 200, not an error. 401 example: { "success": false, "request": { "method": "GET", "path": "/v2/quick-zip" }, "error": { "code": "AUTH_MISSING_KEY", "message": "API key is required. Provide via X-Api-Key header or ?key= query parameter. Get a key at https://www.zip-codes.com/api/account.", "retryable": false, "url": "https://www.zip-codes.com/api/account" }, "meta": { "request_id": "01M3SX2VHP0M9TXH8WFHW7ZM3G", "version": "2.0.0", "timing_ms": 0 } } Lookup failures use HTTP 200 as described under each endpoint. NOT_FOUND_COORDS_NO_BOUNDARY can report "No ZIP or postal code boundary found for coordinates {lat},{lon}." Address no-match reasons are INSUFFICIENT_INPUT, AMBIGUOUS, NOT_FOUND; exact messages appear in the address reference. Non-fatal notices can appear in meta.notices or item notices: INPUT_NORMALIZED, REVERSE_GEOCODE, FALLBACK_USED (quick-zip/distance only), ENRICHMENT_UNAVAILABLE, VALIDATION_UNKNOWN_ENRICHMENT, FREE_TIER_LIMIT_CAPPED, RESULTS_CAPPED, AUTO_COMMUTE_FALLBACK, BATCH_DUPLICATES_DETECTED, and address match/correction codes. ## Links - Human docs: https://api.zip-codes.com/docs - OpenAPI: https://api.zip-codes.com/v2/openapi.json - Playground: https://api.zip-codes.com/docs/playground - Short index: https://api.zip-codes.com/llms.txt - Key signup and pricing: https://www.zip-codes.com/api/pricing - Postman: https://api.zip-codes.com/postman