curl 'https://api.countrystatecity.in/v1/countries/IN/postcodes?limit=20' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
curl 'https://api.countrystatecity.in/v1/countries/IN/postcodes?q=110001&type=full' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
import requests
def search_postcodes(country_code, **params):
response = requests.get(
f'https://api.countrystatecity.in/v1/countries/{country_code}/postcodes',
params=params,
headers={'X-CSCAPI-KEY': 'YOUR_API_KEY'}
)
response.raise_for_status()
return response.json()
page = search_postcodes('IN', q='110001', type='full')
const searchPostcodes = async (countryCode, params = {}) => {
const url = new URL(`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes`);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
const response = await fetch(url, { headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } });
if (!response.ok) throw new Error(await response.text());
return response.json();
};
const page = await searchPostcodes('IN', { q: '110001', type: 'full' });
async function* allPostcodes(countryCode, params = {}) {
let cursor;
do {
const url = new URL(`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes`);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, { headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } });
if (!response.ok) throw new Error(await response.text());
const page = await response.json();
yield* page.data;
cursor = page.pagination.has_more ? page.pagination.next_cursor : null;
} while (cursor);
}
for await (const postcode of allPostcodes('IN', { state_code: 'DL' })) {
console.log(postcode.code, postcode.locality_name);
}
{
"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>"
}
],
"pagination": {
"limit": 50,
"next_cursor": "<string>",
"has_more": true
}
}{
"status": "error",
"message": "<string>",
"details": {
"reason": "<string>"
}
}{
"error": "Unauthorized. You shouldn't be here."
}{
"status": "error",
"message": "This feature is not available on your current plan.",
"details": {
"feature": "searchEndpoint",
"currentTier": "community",
"requiredTier": "starter",
"upgradeUrl": "https://countrystatecity.in/pricing"
}
}{
"status": "error",
"message": "Country not found.",
"details": {
"reason": "country_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"
}Get Postcodes by Country
Page through or search a country’s postcodes with cursor pagination
curl 'https://api.countrystatecity.in/v1/countries/IN/postcodes?limit=20' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
curl 'https://api.countrystatecity.in/v1/countries/IN/postcodes?q=110001&type=full' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
import requests
def search_postcodes(country_code, **params):
response = requests.get(
f'https://api.countrystatecity.in/v1/countries/{country_code}/postcodes',
params=params,
headers={'X-CSCAPI-KEY': 'YOUR_API_KEY'}
)
response.raise_for_status()
return response.json()
page = search_postcodes('IN', q='110001', type='full')
const searchPostcodes = async (countryCode, params = {}) => {
const url = new URL(`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes`);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
const response = await fetch(url, { headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } });
if (!response.ok) throw new Error(await response.text());
return response.json();
};
const page = await searchPostcodes('IN', { q: '110001', type: 'full' });
async function* allPostcodes(countryCode, params = {}) {
let cursor;
do {
const url = new URL(`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes`);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, { headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } });
if (!response.ok) throw new Error(await response.text());
const page = await response.json();
yield* page.data;
cursor = page.pagination.has_more ? page.pagination.next_cursor : null;
} while (cursor);
}
for await (const postcode of allPostcodes('IN', { state_code: 'DL' })) {
console.log(postcode.code, postcode.locality_name);
}
{
"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>"
}
],
"pagination": {
"limit": 50,
"next_cursor": "<string>",
"has_more": true
}
}{
"status": "error",
"message": "<string>",
"details": {
"reason": "<string>"
}
}{
"error": "Unauthorized. You shouldn't be here."
}{
"status": "error",
"message": "This feature is not available on your current plan.",
"details": {
"feature": "searchEndpoint",
"currentTier": "community",
"requiredTier": "starter",
"upgradeUrl": "https://countrystatecity.in/pricing"
}
}{
"status": "error",
"message": "Country not found.",
"details": {
"reason": "country_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"
}?fields= to limit the columns returned. Requires Starter+. There is no ?sort= on this endpoint — results are always ordered by code, then id, so cursor pagination stays stable. See the Field Filtering & Sorting guide for ?fields= syntax.has_more and keep paging instead of expecting a total.{
"data": [
{
"id": "131878",
"code": "110001",
"country_code": "IN",
"state_code": "DL",
"locality_name": "Sansad Marg",
"type": "full"
}
],
"pagination": {
"limit": 50,
"next_cursor": null,
"has_more": false
}
}
{
"data": [
{
"id": "131878",
"code": "110001",
"country_code": "IN",
"state_code": "DL",
"locality_name": "Sansad Marg",
"type": "full",
"country_id": "101",
"state_id": "4021",
"city_id": null,
"latitude": null,
"longitude": null,
"source": "geonames",
"wikiDataId": null
}
],
"pagination": {
"limit": 50,
"next_cursor": null,
"has_more": false
}
}
Tier-based field availability
Community and Legacy lack thesearchEndpoint feature this endpoint requires, so the Basic row below is only reachable on Starter — Community and Legacy get a 403 on this endpoint before field access matters.
| Tier | Plans | Fields |
|---|---|---|
| Basic | Starter | 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 (public, max-age=3600), and X-Cache (HIT/MISS), cached for 1 hour regardless of whether q is present. Send the last ETag back as If-None-Match to get a bodyless 304 when nothing changed instead of re-downloading the page:
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/IN/postcodes?limit=20' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
curl 'https://api.countrystatecity.in/v1/countries/IN/postcodes?q=110001&type=full' \
-H 'X-CSCAPI-KEY: YOUR_API_KEY'
import requests
def search_postcodes(country_code, **params):
response = requests.get(
f'https://api.countrystatecity.in/v1/countries/{country_code}/postcodes',
params=params,
headers={'X-CSCAPI-KEY': 'YOUR_API_KEY'}
)
response.raise_for_status()
return response.json()
page = search_postcodes('IN', q='110001', type='full')
const searchPostcodes = async (countryCode, params = {}) => {
const url = new URL(`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes`);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
const response = await fetch(url, { headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } });
if (!response.ok) throw new Error(await response.text());
return response.json();
};
const page = await searchPostcodes('IN', { q: '110001', type: 'full' });
async function* allPostcodes(countryCode, params = {}) {
let cursor;
do {
const url = new URL(`https://api.countrystatecity.in/v1/countries/${countryCode}/postcodes`);
for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, { headers: { 'X-CSCAPI-KEY': 'YOUR_API_KEY' } });
if (!response.ok) throw new Error(await response.text());
const page = await response.json();
yield* page.data;
cursor = page.pagination.has_more ? page.pagination.next_cursor : null;
} while (cursor);
}
for await (const postcode of allPostcodes('IN', { state_code: 'DL' })) {
console.log(postcode.code, postcode.locality_name);
}
Errors
{
"status": "error",
"message": "countryCode must be a 2-letter ISO code"
}
{
"status": "error",
"message": "cursor filters do not match this request."
}
400s as "cursor belongs to a different country." when reused across countries, "cursor is invalid or has been tampered with." when it’s malformed or its signature doesn’t verify, and "cursor has expired. Start again without a cursor." after 24 hours.
{
"status": "error",
"message": "This feature is not available on your current plan.",
"details": {
"feature": "searchEndpoint",
"currentTier": "community",
"requiredTier": "starter",
"upgradeUrl": "https://countrystatecity.in/pricing"
}
}
403 fires on every request to this endpoint from a Community or Legacy key — including a bare list with no q, state_code, city_id, or type at all. Only exact lookup (Get Postcode by Code) is free on those plans.
{
"data": [],
"pagination": {
"limit": 50,
"next_cursor": null,
"has_more": false
}
}
200 with an empty data array — not a 404. The 404 cases (country_not_found) match Get Postcode by Code for an unknown country.
See Errors & Rate Limits for the 401/429 shapes and usage headers common to every endpoint.
Related
Get Postcode by Code
Postcodes Reference
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.
2"LK"
Query Parameters
Case-insensitive search: codes starting with q, or locality names containing it. 2 to 100 characters.
2 - 100"1100"
Exact, case-insensitive state/province code filter.
"DL"
Exact city ID filter (positive integer).
x >= 1129881
Exact, case-insensitive postcode type filter. Not a fixed enum; unrecognised values match zero rows.
32"full"
Comma-separated fields to include. id is always included. There is no sort on this endpoint.
"code,locality_name"
Records per page, 1 to 100.
1 <= x <= 100Opaque pagination.next_cursor from the previous page. Resend the same filters, or omit them and send only the cursor.