> ## Documentation Index
> Fetch the complete documentation index at: https://docs.countrystatecity.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Postcodes by Country

> Page through or search a country's postcodes with cursor pagination

Page through a country's postcodes / ZIP codes, or narrow the list with a code/locality search, state, city, or type filter. Use this to build postcode discovery instead of downloading the full dataset.

<Note>
  **Requires a Starter+ plan** — including a bare list request with no filters at all. This is intentional: it stops a Community or Legacy key from paginating through and scraping the full hosted dataset. [Get Postcode by Code](/api/endpoints/get-postcode-by-code) (exact lookup) has no such gate and stays free on every plan.
</Note>

<Warning>
  This is informational postcode data, not an address verification or mail-deliverability service. Results confirm codes exist in the dataset — they do not confirm delivery points or standardize addresses.
</Warning>

<Tip>
  **Trim results:** Add `?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](/api/field-filtering-and-sorting) guide for `?fields=` syntax.
</Tip>

<Info>
  There is no total-count field. Counting every match up front would mean a second expensive query on top of a 844k-row table, so check `has_more` and keep paging instead of expecting a total.
</Info>

```json 200 - Search with q, Basic tier (Starter) theme={null}
{
  "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
  }
}
```

```json 200 - Search with q, Full tier (Supporter, Professional, Business) theme={null}
{
  "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 the `searchEndpoint` 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` |

See [Pricing](https://countrystatecity.in/pricing?source=docs\&campaign=postcode_search) for plan details.

## Caching and conditional requests

Every response sets `ETag`, `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:

```javascript theme={null}
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');
}
```

<RequestExample>
  ```bash cURL theme={null}
  curl 'https://api.countrystatecity.in/v1/countries/IN/postcodes?limit=20' \
    -H 'X-CSCAPI-KEY: YOUR_API_KEY'
  ```

  ```bash cURL (search) theme={null}
  curl 'https://api.countrystatecity.in/v1/countries/IN/postcodes?q=110001&type=full' \
    -H 'X-CSCAPI-KEY: YOUR_API_KEY'
  ```

  ```python Python theme={null}
  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')
  ```

  ```javascript JavaScript theme={null}
  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' });
  ```

  ```javascript JavaScript (page through every result) theme={null}
  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);
  }
  ```
</RequestExample>

## Errors

```json 400 - Invalid country code theme={null}
{
  "status": "error",
  "message": "countryCode must be a 2-letter ISO code"
}
```

```json 400 - Cursor filters don't match this request theme={null}
{
  "status": "error",
  "message": "cursor filters do not match this request."
}
```

A cursor also `400`s 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.

```json 403 - Requires Starter+ theme={null}
{
  "status": "error",
  "message": "This feature is not available on your current plan.",
  "details": {
    "feature": "searchEndpoint",
    "currentTier": "community",
    "requiredTier": "starter",
    "upgradeUrl": "https://countrystatecity.in/pricing"
  }
}
```

This `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](/api/endpoints/get-postcode-by-code)) is free on those plans.

```json 200 - Country with no postcode coverage theme={null}
{
  "data": [],
  "pagination": {
    "limit": 50,
    "next_cursor": null,
    "has_more": false
  }
}
```

A country that exists but has no postcodes, or a filter that matches nothing, returns `200` with an empty `data` array — not a `404`. The `404` cases (`country_not_found`) match [Get Postcode by Code](/api/endpoints/get-postcode-by-code#errors) for an unknown country.

See [Errors & Rate Limits](/api/errors) for the `401`/`429` shapes and usage headers common to every endpoint.

<Note>
  This endpoint requires a Starter+ plan, for listing as well as searching. [Compare plans](https://countrystatecity.in/pricing?source=docs\&campaign=postcode_search) to add it to your API key.
</Note>

## Related

<CardGroup cols={2}>
  <Card title="Get Postcode by Code" icon="location-dot" href="/api/endpoints/get-postcode-by-code">
    Exact lookup, free on every plan — start here before adding search.
  </Card>

  <Card title="Postcodes Reference" icon="mailbox" href="/database/postcodes">
    Coverage stats, postcode types, and self-hosted query examples.
  </Card>
</CardGroup>


## OpenAPI

````yaml GET /countries/{ciso}/postcodes
openapi: 3.0.3
info:
  title: Country State City API
  description: >-
    Geographic data API providing countries, states, cities, timezones,
    currencies, and phone dial codes with tiered access levels.


    ## Authentication

    All endpoints require an API key passed via the `X-CSCAPI-KEY` header. Get
    your free key at [app.countrystatecity.in](https://app.countrystatecity.in).


    ## Pricing & Limits

    The fields returned, endpoints available, and request quotas depend on your
    plan. See current tiers and limits at:


    **[countrystatecity.in/pricing](https://countrystatecity.in/pricing)**


    ## Data Access Levels

    Each plan includes a data access level that controls which fields are
    returned:


    - **Basic** — Core fields: id, name, iso2, iso3, phonecode, capital,
    currency, coordinates, region, timezones, emoji

    - **Coordinates** — Basic + extended metadata (population, tld, nationality,
    currency details, postal codes, etc.)

    - **Full** — All fields including translations (18+ languages) and
    wikiDataId


    Fields restricted to higher tiers are marked in each schema below.


    ## Feature Gating

    Some endpoints require a minimum tier. Restricted endpoints return `403`
    with an `upgradeUrl` when accessed on a lower plan. Check the [pricing
    page](https://countrystatecity.in/pricing) for current feature availability.


    ## Rate Limiting

    Requests exceeding your daily or monthly quota receive a `429` response with
    your current limit, period, tier, and an upgrade URL.


    ## Caching

    Cacheable geographic responses include `Cache-Control`, `ETag`, and
    `X-Cache` (HIT/MISS) headers; send `If-None-Match` on them to receive `304
    Not Modified`. Phone number parsing is never cached (`Cache-Control:
    no-store`, no `ETag`); see each endpoint for exceptions.


    ## Usage Headers

    All `/v1/*` responses include usage and plan headers:

    - `X-CSC-Plan` — your current tier

    - `X-CSC-Daily-Used` / `X-CSC-Daily-Limit` — today's usage vs limit

    - `X-CSC-Monthly-Used` / `X-CSC-Monthly-Limit` — this month's usage vs limit


    Use these to monitor consumption without a separate API call.
  contact:
    name: Darshan Gada (dr5hn)
    email: gadadarshan@gmail.com
    url: https://countrystatecity.in
  license:
    name: Creative Commons Attribution 4.0
    url: https://creativecommons.org/licenses/by/4.0/
  version: 2.4.1
servers:
  - url: https://api.countrystatecity.in/v1
    description: Production API
security: []
tags:
  - name: Countries
    description: Country data — 250+ countries with metadata, coordinates, and translations
  - name: States
    description: State/province/region data — 5,000+ subdivisions
  - name: Cities
    description: City data — 150,000+ cities worldwide
  - name: Postcodes
    description: >-
      Postcode and ZIP code lookup and search. Informational data, not address
      verification.
  - name: Regions
    description: Geographic regions and subregions — continental groupings of countries
  - name: Timezones
    description: >-
      IANA timezone lookup by country, state, or city — includes UTC offset and
      DST status
  - name: Currencies
    description: ISO 4217 currency data — list all currencies or look up by country
  - name: Phone
    description: >-
      International dial codes — list, lookup by country, and E.164 phone number
      parsing
  - name: ISO
    description: >-
      ISO 3166 code lookup and conversion — country (alpha-2/3/numeric) and
      state (ISO 3166-2)
  - name: Search
    description: Typo-tolerant fuzzy search across cities, states, and countries
  - name: Changes
    description: >-
      Data change feed — track additions, removals, and updates to place records
      over time. **Professional and Business plans only.**
  - name: Meta
    description: Information about the dataset release currently served
paths:
  /countries/{ciso}/postcodes:
    get:
      tags:
        - Postcodes
      summary: List or search postcodes in a country
      description: >-
        Page through a country's postcodes, or narrow them with a code/locality
        search, state, city or type filter. Cursor-paginated; there is no total
        count.


        Informational data only: this is not an address verification or
        mail-deliverability service.


        **Availability:** Starter+ plan required, including a bare list with no
        filters.
      operationId: listPostcodesByCountry
      parameters:
        - name: ciso
          in: path
          required: true
          description: >-
            ISO 3166-1 alpha-2 country code, case-insensitive (e.g. `LK`).
            Numeric IDs and ISO3 codes are not accepted on postcode endpoints.
          schema:
            type: string
            minLength: 2
            maxLength: 2
            example: LK
        - name: q
          in: query
          required: false
          description: >-
            Case-insensitive search: codes starting with `q`, or locality names
            containing it. 2 to 100 characters.
          schema:
            type: string
            minLength: 2
            maxLength: 100
            example: '1100'
        - name: state_code
          in: query
          required: false
          description: Exact, case-insensitive state/province code filter.
          schema:
            type: string
            example: DL
        - name: city_id
          in: query
          required: false
          description: Exact city ID filter (positive integer).
          schema:
            type: integer
            minimum: 1
            example: 129881
        - name: type
          in: query
          required: false
          description: >-
            Exact, case-insensitive postcode type filter. Not a fixed enum;
            unrecognised values match zero rows.
          schema:
            type: string
            maxLength: 32
            example: full
        - name: fields
          in: query
          required: false
          description: >-
            Comma-separated fields to include. `id` is always included. There is
            no `sort` on this endpoint.
          schema:
            type: string
            example: code,locality_name
        - name: limit
          in: query
          required: false
          description: Records per page, 1 to 100.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          required: false
          description: >-
            Opaque `pagination.next_cursor` from the previous page. Resend the
            same filters, or omit them and send only the cursor.
          schema:
            type: string
      responses:
        '200':
          description: A page of postcodes
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
            ETag:
              $ref: '#/components/headers/ETag'
            X-Cache:
              $ref: '#/components/headers/XCache'
            X-CSC-Plan:
              $ref: '#/components/headers/XCSCPlan'
            X-CSC-Daily-Used:
              $ref: '#/components/headers/XCSCDailyUsed'
            X-CSC-Daily-Limit:
              $ref: '#/components/headers/XCSCDailyLimit'
            X-CSC-Monthly-Used:
              $ref: '#/components/headers/XCSCMonthlyUsed'
            X-CSC-Monthly-Limit:
              $ref: '#/components/headers/XCSCMonthlyLimit'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostcodeListResponse'
        '304':
          description: Not modified (sent `If-None-Match` matched the current `ETag`)
        '400':
          description: >-
            Invalid country code or parameter, or a cursor that is expired,
            tampered with, from another country, or used with different filters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            Plan below Starter. Returned for every request to this endpoint,
            including a bare list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusError'
              example:
                status: error
                message: This feature is not available on your current plan.
                details:
                  feature: searchEndpoint
                  currentTier: community
                  requiredTier: starter
                  upgradeUrl: https://countrystatecity.in/pricing
        '404':
          description: >-
            Unknown country (`details.reason`: `country_not_found`). A known
            country with no postcodes, or a filter with no matches, returns
            `200` with an empty `data` array.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusError'
              example:
                status: error
                message: Country not found.
                details:
                  reason: country_not_found
        '429':
          $ref: '#/components/responses/RateLimited'
      security:
        - ApiKeyAuth: []
components:
  headers:
    CacheControl:
      description: Cache directive
      schema:
        type: string
        example: public, max-age=3600
    ETag:
      description: Entity tag for conditional requests (use with `If-None-Match`)
      schema:
        type: string
        example: '"a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5"'
    XCache:
      description: Whether the response was served from cache
      schema:
        type: string
        enum:
          - HIT
          - MISS
        example: HIT
    XCSCPlan:
      description: Your current pricing tier
      schema:
        type: string
        enum:
          - community
          - starter
          - supporter
          - professional
          - business
          - legacy
          - custom
        example: supporter
    XCSCDailyUsed:
      description: Number of requests used today
      schema:
        type: integer
        example: 42
    XCSCDailyLimit:
      description: Your daily request limit (or 'unlimited' for custom plans)
      schema:
        type: string
        example: '1000'
    XCSCMonthlyUsed:
      description: Number of requests used this month
      schema:
        type: integer
        example: 850
    XCSCMonthlyLimit:
      description: Your monthly request limit (or 'unlimited' for custom plans)
      schema:
        type: string
        example: '30000'
  schemas:
    PostcodeListResponse:
      type: object
      properties:
        data:
          type: array
          description: Matching records for this page, ordered by code then `id`
          items:
            $ref: '#/components/schemas/Postcode'
        pagination:
          type: object
          properties:
            limit:
              type: integer
              example: 50
            next_cursor:
              type: string
              nullable: true
              description: Opaque token for the next page, `null` on the last page
            has_more:
              type: boolean
      required:
        - data
        - pagination
    StatusError:
      type: object
      description: Error envelope used by the postcode and meta endpoints.
      properties:
        status:
          type: string
          example: error
        message:
          type: string
        details:
          type: object
          nullable: true
          properties:
            reason:
              type: string
              description: >-
                Machine-readable reason. Check this instead of parsing
                `message`.
          additionalProperties: true
      required:
        - status
        - message
    Postcode:
      type: object
      description: >-
        A postcode record. `id` and foreign keys are numeric strings for 64-bit
        safety. Fields above the Basic tier are stripped, not nulled, for lower
        plans.
      properties:
        id:
          type: string
          pattern: ^[0-9]+$
          description: Unique identifier, as a numeric string
          example: '670115'
        code:
          type: string
          description: Postcode as stored, original spacing and casing preserved
          example: '11104'
        country_code:
          type: string
          description: ISO2 country code
          example: LK
        state_code:
          type: string
          nullable: true
          description: State/province code, when linked
          example: '12'
        locality_name:
          type: string
          nullable: true
          description: Place name associated with the postcode
          example: Wattala
        type:
          type: string
          nullable: true
          description: >-
            Source-provided classification (not a fixed enum), e.g. `full`,
            `area`, `fsa`, `po_box`, `street`
          example: full
        country_id:
          type: string
          pattern: ^[0-9]+$
          description: Country ID. **Coordinates tier and above.**
          example: '208'
        state_id:
          type: string
          pattern: ^[0-9]+$
          description: State ID. **Coordinates tier and above.**
          nullable: true
          example: '2804'
        city_id:
          type: string
          pattern: ^[0-9]+$
          description: City ID, when linked to a city. **Coordinates tier and above.**
          nullable: true
        latitude:
          type: number
          nullable: true
          description: Latitude. **Coordinates tier and above.**
          example: 6.9906677
        longitude:
          type: number
          nullable: true
          description: Longitude. **Coordinates tier and above.**
          example: 79.8931709
        source:
          type: string
          nullable: true
          description: Originating data source. **Full tier only.**
        wikiDataId:
          type: string
          nullable: true
          description: Wikidata Q-ID. **Full tier only.**
      required:
        - id
        - code
        - country_code
    Error:
      type: object
      properties:
        error:
          type: string
          description: Error message
      required:
        - error
    RateLimitError:
      type: object
      properties:
        error:
          type: string
          example: >-
            Daily usage limit exceeded. Please try again tomorrow or contact
            support for higher limits.
        limit:
          type: integer
          description: Your plan's limit for the exceeded period
          example: 100
        period:
          type: string
          enum:
            - daily
            - monthly
          example: daily
        tier:
          type: string
          description: Your current tier
          example: community
        upgradeUrl:
          type: string
          description: URL to upgrade your plan
          example: https://countrystatecity.in/pricing
      required:
        - error
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Unauthorized. You shouldn't be here.
    RateLimited:
      description: Daily or monthly usage limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RateLimitError'
          example:
            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
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      description: >-
        API key for authentication. Get your free key at
        [app.countrystatecity.in](https://app.countrystatecity.in).
      name: X-CSCAPI-KEY
      in: header

````

## Related topics

- [Postcodes](/database/postcodes.md)
- [Get Postcode by Code](/api/endpoints/get-postcode-by-code.md)
- [Database Schema](/database/schema.md)
- [Get All Countries](/api/endpoints/get-all-countries.md)
- [Get Timezone by Country](/api/endpoints/get-timezone-by-country.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.