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": [
{
"id": "670115",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Wattala",
"type": "full",
"country_id": "208",
"state_id": "2804",
"city_id": "<string>",
"latitude": 6.9906677,
"longitude": 79.8931709,
"source": "<string>",
"wikiDataId": "<string>"
}
],
"meta": {
"country_code": "LK",
"query": "11104",
"match_count": 3
}
}{
"status": "error",
"message": "postcode must be at most 20 characters"
}{
"error": "Unauthorized. You shouldn't be here."
}{
"status": "error",
"message": "No postcode matches the given code.",
"details": {
"reason": "postcode_not_found"
}
}{
"error": "Daily usage limit exceeded. Please try again tomorrow or contact support for higher limits.",
"limit": 100,
"period": "daily",
"tier": "community",
"upgradeUrl": "https://countrystatecity.in/pricing"
}Postcodes
Get Postcode by Code
Look up every postcode record that matches an exact code within a country
GET
/
countries
/
{ciso}
/
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": [
{
"id": "670115",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Wattala",
"type": "full",
"country_id": "208",
"state_id": "2804",
"city_id": "<string>",
"latitude": 6.9906677,
"longitude": 79.8931709,
"source": "<string>",
"wikiDataId": "<string>"
}
],
"meta": {
"country_code": "LK",
"query": "11104",
"match_count": 3
}
}{
"status": "error",
"message": "postcode must be at most 20 characters"
}{
"error": "Unauthorized. You shouldn't be here."
}{
"status": "error",
"message": "No postcode matches the given code.",
"details": {
"reason": "postcode_not_found"
}
}{
"error": "Daily usage limit exceeded. Please try again tomorrow or contact support for higher limits.",
"limit": 100,
"period": "daily",
"tier": "community",
"upgradeUrl": "https://countrystatecity.in/pricing"
}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 Starter+ 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.
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.
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, Basic tier (Community, Starter, Legacy)
{
"data": [
{
"id": "670115",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Mabola",
"type": "full"
},
{
"id": "670116",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Watinapaha",
"type": "full"
},
{
"id": "670117",
"code": "11104",
"country_code": "LK",
"state_code": "12",
"locality_name": "Wattala",
"type": "full"
}
],
"meta": {
"country_code": "LK",
"query": "11104",
"match_count": 3
}
}
200 - Duplicate exact match, Full tier (Supporter, Professional, Business)
{
"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,
"source": "geonames",
"wikiDataId": null
},
{
"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,
"source": "geonames",
"wikiDataId": null
},
{
"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,
"source": "geonames",
"wikiDataId": null
}
],
"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 |
| Full | Supporter, Professional, Business | All Basic + country_id, state_id, city_id, latitude, longitude, 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 Starter+.
Postcodes Reference
Coverage stats, postcode types, and self-hosted query examples.
Authorizations
API key for authentication. Get your free key at app.countrystatecity.in.
Path Parameters
ISO 3166-1 alpha-2 country code, case-insensitive (e.g. LK). Numeric IDs and ISO3 codes are not accepted on postcode endpoints.
Required string length:
2Example:
"LK"
Postcode to look up, 1 to 20 characters. Case-insensitive and tolerant of repeated internal spaces (gy1 1aa matches GY1 1AA). URL-encode spaces.
Required string length:
1 - 20Example:
"11104"