Skip to main content
POST
GraphQL API
The GraphQL endpoint covers the same geographic data as the REST API — countries, states, cities, regions, and subregions — through a single endpoint. Ask for exactly the fields you need, and resolve nested relationships (a country’s states, a state’s cities) in one request instead of chaining several REST calls yourself.
Available on the Professional and Business plans. Compare plans to add GraphQL to your API key.

When to use it

Reach for GraphQL when a screen needs a nested shape — for example a country with its states and each state’s cities — in one round trip. For a single flat list, the REST endpoints work just as well: they return the complete list in one response and are simpler to cache by URL.

Authentication

string
required
Your API key, the same header every REST endpoint uses.

Sending a query

Send a POST request with a JSON body containing a query string (and optional variables):
cURL
JavaScript
Python
200 - Response

Exploring the schema

A GET request to the same endpoint with an Accept header that includes text/html (what a browser sends) returns GraphiQL, an interactive schema explorer with autocomplete and inline docs for every type and field. The page is protected like any query: the request that loads it must carry your X-CSCAPI-KEY header, and your plan must include GraphQL. Typing the URL into the address bar sends no key, so it returns 401 instead of the explorer. To open it:
  1. Install a browser extension that adds custom request headers, and set X-CSCAPI-KEY: YOUR_API_KEY for api.countrystatecity.in.
  2. Open https://api.countrystatecity.in/v1/graphql.
  3. In GraphiQL’s Headers panel, add {"X-CSCAPI-KEY": "YOUR_API_KEY"}. GraphiQL sends its queries with the headers from this panel.
Alternatively, use an API client with GraphQL support, such as Postman or Insomnia, with the header set. It can load the schema by introspection over an ordinary POST. Loading the page and each request GraphiQL sends count toward your daily and monthly usage like any other request. If your key restricts allowed domains or IPs, GraphiQL’s requests are checked against those restrictions too.

Nested queries

Fields that return related entities — Country.states, State.cities, Region.subregions, Subregion.countries — resolve in the same HTTP request, so one round trip replaces a chain of REST calls. Each nested list returns a page (20 rows per parent unless you pass limit), and every parent row adds a lookup to the query budget, so keep the outer lists small:
This returns the first 2 matching countries (sorted by name), up to 10 states for each, and up to 10 cities for each of those states: a page at every level, not every match. It is estimated at 23 lookups (1 for countries, 1 per country for states, 1 per state for cities) and 222 rows, well inside the budget. Nested lookups are not merged into a single database query. The server still fetches each parent’s children separately; it runs those lookups a limited number at a time in parallel and reuses a result when the same parent and page appear twice in one request. So the cities of 20 different states still cost 20 lookups.

Fields by plan

Two plan settings apply, separately:
  • The GraphQL feature decides whether you can call /v1/graphql at all. Professional and Business include it, and a custom plan can too. Supporter, for example, has full field access over REST but no GraphQL.
  • Your data access level decides which fields come back, using the same per-field filtering as REST. Professional and Business have full access, so every field in the schema is returned. A custom plan can have a lower level.
A field above your access level returns null rather than an error, so one query can safely ask for fields you might not have: A custom plan can also be set to an intermediate level that returns the Full fields except translations (and, for countries, states and cities, wikiDataId). Only fields defined in the GraphQL schema can be requested. REST’s localized_name and matched_locale are not in it, and there are no locale or include_translations arguments: asking for localized_name is a validation error, not null. To show a name in another language, select translations (a JSON-encoded string keyed by language code) and pick the language in your app, or use the REST locale parameter.

Available queries

country(ciso) takes a numeric ID passed as a string, or an ISO2 code. An ISO3 code such as "USA" passes validation but matches nothing, so the query returns null; convert it first with Lookup Country by ISO Code. The country argument of states, state, and cities takes an ISO2 code, and state(iso) and cities(state) take the state’s code (for example CA). searchQuery (2 to 100 characters) keeps rows whose name (or, for countries, states, and cities, native name) contains it, ignoring case. A single-item query that matches nothing returns null with no error.
city(id) has no REST equivalent — it’s the one query only available through GraphQL, since REST only ever exposed cities scoped to a country or state.

Limits

Each HTTP request to /v1/graphql counts as one request against your plan’s daily and monthly limits, however many lookups it runs, including a request rejected for exceeding the query budget.

Pagination

Every list field, at the root or nested, takes limit and offset. They are arguments on the field inside your query, not URL query parameters. offset defaults to 0. Lists are sorted by name, then ID, so stepping offset by limit pages through the full list. A limit above 1,000, or a negative limit or offset, is rejected rather than capped: it fails that field with a BAD_REQUEST error (see Errors). limit: 0 returns an empty list. Unlike the REST list endpoints, a GraphQL list never returns more than one page. Watch the nested default in particular: { country(ciso: "US") { states { name } } } returns only the first 20 states. Ask for more with states(limit: 100), or page with offset.

Query budget

Before running a query, the API estimates its cost from the limit of every list field (the value you pass, or the default above), not from how many rows actually match:
  • Lookups: every field that returns an object or a list of objects costs one lookup per parent row it runs under. Scalar fields such as name are free.
  • Rows: every list field adds its limit multiplied by the number of parent rows.
One query may use at most 50 lookups and 25,000 rows. A query over either limit is rejected with HTTP 400 before any data is fetched:
400 - Query too expensive
That response is for { countries { states { name } } }: countries defaults to 1,000 rows, so states would run 1,000 lookups. To stay within the budget, add or lower limit on the outer lists, drop a level of nesting, or split the work into several requests. For example, fetch a page of countries first, then request each country’s states and cities in a separate query, using offset for further pages. Queries are also limited to 5 levels of nesting. That is exactly the deepest chain the schema allows (regions → subregions → countries → states → cities), so in practice the budget is the limit you will reach.

Errors

The HTTP status and body depend on where a request fails: Field errors carry extensions.code and extensions.statusCode: BAD_REQUEST (400) for an invalid argument such as a malformed country code or a limit over 1,000; FORBIDDEN (403) when your plan lacks a feature that field needs, with extensions.details naming the feature (standard Professional and Business plans include every feature these queries use); and API_ERROR for anything else. A 503 with GraphQL is busy. Please retry shortly. is temporary, so retry after a short wait. The failed field becomes null. When that field is nullable (the single-item queries such as country, and the nested lists such as Country.states), the rest of data still comes back. This response is for { country(ciso: "US") { name states(limit: 5000) { name } } }:
200 - Partial data
Root list queries (countries, states, cities, and the others) can’t be null, so an error in one of them makes all of data null.

Start querying

Compare API plans

Choose Professional or Business to add GraphQL to your API key.

Errors & Rate Limits

Handle the 401, 403, and 429 responses returned before a query runs.