curl 'https://api.countrystatecity.in/v1/countries/LK/postcodes/11104' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
import requests
def get_postcode(country_code, code):
response = requests.get(
f'https://api.countrystatecity.in/v1/countries/{country_code}/postcodes/{code}',
headers={'X-CSCAPI-KEY': 'YOUR_API_KEY'}
)
response.raise_for_status()
return response.json()
result = get_postcode('LK', '11104')
print(f"{result['meta']['match_count']} match(es) for {result['meta']['query']}")
const getPostcode = async (countryCode, code) => {
const response = await fetch(
`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes/${encodeURIComponent(code)}`,
{ headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } }
);
if (!response.ok) throw new Error(await response.text());
return response.json();
};
const result = await getPostcode('LK', '11104');
console.log(`${result.meta.match_count} match(es) for ${result.meta.query}`);
{
"data": [
{}
],
"data[].id": "<string>",
"data[].code": "<string>",
"data[].country_code": "<string>",
"data[].state_code": {},
"data[].locality_name": {},
"data[].type": {},
"data[].country_id": "<string>",
"data[].state_id": {},
"data[].city_id": {},
"data[].latitude": {},
"data[].longitude": {},
"data[].source": {},
"data[].wikiDataId": {},
"meta.country_code": "<string>",
"meta.query": "<string>",
"meta.match_count": 123,
"details.reason": "<string>"
}Postcode Endpoints
Get Postcode by Code
Look up every postcode record that matches an exact code within a country
GET
/
v1
/
countries
/
{countryCode}
/
postcodes
/
{code}
curl 'https://api.countrystatecity.in/v1/countries/LK/postcodes/11104' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
import requests
def get_postcode(country_code, code):
response = requests.get(
f'https://api.countrystatecity.in/v1/countries/{country_code}/postcodes/{code}',
headers={'X-CSCAPI-KEY': 'YOUR_API_KEY'}
)
response.raise_for_status()
return response.json()
result = get_postcode('LK', '11104')
print(f"{result['meta']['match_count']} match(es) for {result['meta']['query']}")
const getPostcode = async (countryCode, code) => {
const response = await fetch(
`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes/${encodeURIComponent(code)}`,
{ headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } }
);
if (!response.ok) throw new Error(await response.text());
return response.json();
};
const result = await getPostcode('LK', '11104');
console.log(`${result.meta.match_count} match(es) for ${result.meta.query}`);
{
"data": [
{}
],
"data[].id": "<string>",
"data[].code": "<string>",
"data[].country_code": "<string>",
"data[].state_code": {},
"data[].locality_name": {},
"data[].type": {},
"data[].country_id": "<string>",
"data[].state_id": {},
"data[].city_id": {},
"data[].latitude": {},
"data[].longitude": {},
"data[].source": {},
"data[].wikiDataId": {},
"meta.country_code": "<string>",
"meta.query": "<string>",
"meta.match_count": 123,
"details.reason": "<string>"
}Look up a postcode / ZIP code within a country and get back every matching record. Use this to confirm that a user-supplied postcode appears in the dataset.
See Pricing for plan details.
Free on every plan, including Community. Exact lookup counts against your normal daily/monthly quota like any other request. Paginated listing and search use Get Postcodes by Country and require a Supporter+ plan — exact lookup has no such gate.
This is informational postcode data, not an address verification or mail-deliverability service. A match confirms the code exists in the dataset — it does not confirm a delivery point, standardize an address, or guarantee mail will arrive.
Path parameters
string
required
ISO 3166-1 alpha-2 country code, case-insensitive (e.g.,
US, GB, IN). Numeric country IDs and ISO3 codes are not accepted on this endpoint.string
required
The postcode to look up (e.g.,
110001, GY1 1AA, 11104). 1-20 characters after trimming outer whitespace. Matching is case-insensitive and tolerant of repeated internal spaces — gy1 1aa matches GY1 1AA — but the code returned in the response preserves its original spacing and casing.Authentication
string
required
Your API key.
Why the response is an array
A postcode is not always unique to one place. The same code can legitimately cover more than one locality, so this endpoint returns every matching record instead of picking one — an empty result is a404, never an empty success.
array
Every postcode record matching the code, in ascending
id order.string
Unique identifier for the postcode record, returned as a numeric string (not a JSON number) for 64-bit safety.
string
The postcode as stored, with original spacing and casing preserved.
string
ISO2 country code.
string | null
State/province code, when the postcode is linked to one.
string | null
Human-readable place name associated with the postcode.
string | null
A source-provided classification of the postcode, not a fixed enum — see Postcode types for the values currently in the dataset. Filtering on an unrecognized value just matches zero rows rather than erroring.
string
Country ID, as a numeric string. Requires Supporter+.
string | null
State ID, as a numeric string. Requires Supporter+.
string | null
City ID, as a numeric string, when the postcode is linked to a specific city. Requires Supporter+.
number | null
Latitude of the postcode area. Requires Supporter+.
number | null
Longitude of the postcode area. Requires Supporter+.
string | null
Originating data source, for attribution and license tracking. Requires Professional+.
string | null
Wikidata Q-ID for cross-referencing. Requires Professional+.
string
The country code you queried, uppercased.
string
The normalized
code used for matching (trimmed, uppercased, with repeated spaces collapsed).integer
Number of records in
data.Fields above Basic are stripped for lower plans, not just hidden — a Community or Starter key never receives
country_id, state_id, city_id, latitude, or longitude in the response body. See Tier-based field availability below. id and every foreign-key field are returned as numeric strings rather than JSON numbers — parse them as strings in your client.200 - Duplicate exact match (Supporter+)
{
"data": [
{
"id": "670115",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Mabola",
"type": "full",
"country_id": "208",
"state_id": "2804",
"city_id": null,
"latitude": 7.0062833,
"longitude": 79.8924828
},
{
"id": "670116",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Watinapaha",
"type": "full",
"country_id": "208",
"state_id": "2804",
"city_id": null,
"latitude": 7.1853118,
"longitude": 80.0003875
},
{
"id": "670117",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Wattala",
"type": "full",
"country_id": "208",
"state_id": "2804",
"city_id": null,
"latitude": 6.9906677,
"longitude": 79.8931709
}
],
"meta": {
"country_code": "LK",
"query": "11104",
"match_count": 3
}
}
Tier-based field availability
| Tier | Plans | Fields |
|---|---|---|
| Basic | Community, Starter, Legacy | id, code, country_code, state_code, locality_name, type |
| Coordinates | Supporter | All Basic + country_id, state_id, city_id, latitude, longitude |
| Full | Professional, Business | All Coordinates + source, wikiDataId |
Caching and conditional requests
Every response setsETag, Cache-Control, and X-Cache (HIT/MISS), cached for 24 hours. Send the last ETag back as If-None-Match to get a bodyless 304 when nothing changed instead of re-downloading the result:
const response = await fetch(url, {
headers: {
'X-CSCAPI-KEY': 'YOUR_API_KEY',
'If-None-Match': savedEtag
}
});
if (response.status === 304) {
// Nothing changed — reuse your cached copy.
} else {
savedEtag = response.headers.get('ETag');
}
curl 'https://api.countrystatecity.in/v1/countries/LK/postcodes/11104' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
import requests
def get_postcode(country_code, code):
response = requests.get(
f'https://api.countrystatecity.in/v1/countries/{country_code}/postcodes/{code}',
headers={'X-CSCAPI-KEY': 'YOUR_API_KEY'}
)
response.raise_for_status()
return response.json()
result = get_postcode('LK', '11104')
print(f"{result['meta']['match_count']} match(es) for {result['meta']['query']}")
const getPostcode = async (countryCode, code) => {
const response = await fetch(
`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes/${encodeURIComponent(code)}`,
{ headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } }
);
if (!response.ok) throw new Error(await response.text());
return response.json();
};
const result = await getPostcode('LK', '11104');
console.log(`${result.meta.match_count} match(es) for ${result.meta.query}`);
Errors
string
Distinguishes the three
404 cases below. Check this instead of parsing message.404 - Unknown country
{
"status": "error",
"message": "Country not found.",
"details": { "reason": "country_not_found" }
}
404 - No coverage for this country
{
"status": "error",
"message": "No postcode coverage is available for this country.",
"details": { "reason": "postcode_coverage_unavailable" }
}
404 - Code has no match
{
"status": "error",
"message": "No postcode matches the given code.",
"details": { "reason": "postcode_not_found" }
}
400 - Invalid code
{
"status": "error",
"message": "postcode must be at most 20 characters"
}
code is also rejected as empty, or for containing a /, \, or control character.
See Errors & Rate Limits for the 401/429 shapes and usage headers common to every endpoint.
Related
Get Postcodes by Country
Page through or search a country’s postcodes. Requires Supporter+.
Postcodes Reference
Coverage stats, postcode types, and self-hosted query examples.
Was this page helpful?