Places Autocomplete
Search countries, states and cities as the user types
The /v1/places/autocomplete endpoint powers the address field of a checkout, a signup form or a shipping calculator. You send whatever the user has typed so far and get back ranked suggestions, each one already carrying its full hierarchy so you can render Madrid, Comunidad de Madrid, Spain without a second call.
It covers the country, state and city levels. It does not cover street addresses or points of interest such as businesses, opening hours or reviews.
Endpoint
GET https://api.countrydataapi.com/v1/places/autocomplete
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
apikey |
string | Yes | Your API authentication key |
q |
string | Yes | What the user typed. Minimum 2 characters |
country |
string | No | Restrict results to one country: internal id, ISO-2, ISO-3 or name |
types |
string | No | Comma-separated levels to search: country, state, city. Defaults to all three |
limit |
number | No | Number of suggestions, 1-20. Defaults to 5 |
lang |
string | No | Language of the returned names. Defaults to en |
sessiontoken |
string | No | Groups every keystroke of one form field into a single billable session |
How matching works
The query is normalised before searching, so users do not have to fight the keyboard:
- Case and accent insensitive —
avilafindsÁvila,munchenfindsMünchen. - Punctuation insensitive —
hospitaletfindsL'Hospitalet de Llobregat. - Any word matches —
yorkfindsNew York, not just names that start with York.
Results are ranked by how well they match (exact name, then start of the name, then start of an inner word), then from broader to narrower administrative level, then by name length. The match field on each suggestion tells you which rule fired, which is handy for highlighting.
Request Example
curl "https://api.countrydataapi.com/v1/places/autocomplete?apikey=your-api-key&q=mad&country=ES&limit=5"
JavaScript
const API_KEY = 'your-api-key';
async function search(query, sessionToken) {
const params = new URLSearchParams({
apikey: API_KEY,
q: query,
limit: '5',
sessiontoken: sessionToken,
});
const response = await fetch(
`https://api.countrydataapi.com/v1/places/autocomplete?${params}`
);
return response.json();
}
Python
import requests
response = requests.get(
'https://api.countrydataapi.com/v1/places/autocomplete',
params={
'apikey': 'your-api-key',
'q': 'mad',
'country': 'ES',
'limit': 5,
},
)
data = response.json()
TypeScript SDK
import { CountryDataApi, Places } from '@countrydataapi/sdk';
const api = new CountryDataApi({ apiKey: 'your-api-key' });
const session = Places.createSession();
const { suggestions } = await api.places.autocomplete({
q: 'mad',
country: 'ES',
sessiontoken: session,
});
Response Format
{
"success": true,
"query": "mad",
"suggestions": [
{
"id": "66c7a6c9e4bda21f4ab1a0f1",
"type": "city",
"name": "Madrid",
"description": "Madrid, Comunidad de Madrid, Spain",
"match": "prefix",
"components": {
"city": { "id": "66c7a6c9e4bda21f4ab1a0f1", "name": "Madrid" },
"state": { "id": "66c7a6c9e4bda21f4ab10a22", "name": "Comunidad de Madrid" },
"country": {
"id": "66c7a6c9e4bda21f4ab10ef2",
"name": "Spain",
"iso2": "ES",
"iso3": "ESP",
"phone_code": "+34",
"flag": "🇪🇸"
}
}
}
],
"count": 1,
"session": { "token": "6f9e...", "billed": true, "ttl_seconds": 180 },
"tokens_used": 1,
"remaining_tokens": 4871
}
Response Fields
| Field | Type | Description |
|---|---|---|
suggestions[].id |
string | Pass this to /v1/places/details |
suggestions[].type |
string | country, state or city |
suggestions[].name |
string | Name in the requested language |
suggestions[].description |
string | Ready to render: "Madrid, Comunidad de Madrid, Spain" |
suggestions[].match |
string | exact, prefix or word |
suggestions[].components |
object | Resolved hierarchy: city, state, country |
session |
object | Present only when you sent a sessiontoken. billed says whether this call was charged |
Token Usage: pay per session, not per keystroke
A typeahead fires a request on every key. Charging per request would make a single address field cost 8-10 tokens and turn your bill into a function of how fast your users type.
Send a sessiontoken — any UUID, generated when the field gains focus — and the whole session costs 1 token no matter how many requests it took:
- Generate a token when the address input gains focus.
- Send it on every
/autocompletecall. Only the first one is charged. - Send it on the
/detailscall for the suggestion the user picked. That call is free and closes the session. - Generate a new token for the next address field.
Sessions expire after 3 minutes. Without a sessiontoken, each request costs 1 token.
// One session per address field
let session = crypto.randomUUID();
input.addEventListener('focus', () => { session = crypto.randomUUID(); });
// ...every keystroke reuses `session`, so the whole field costs 1 token
Error Response
{
"success": false,
"error": {
"code": "INVALID_PARAMETER",
"message": "Parameter \"q\" is required and must be at least 2 characters long.",
"status": 400
},
"message": "Parameter \"q\" is required and must be at least 2 characters long."
}
Unlike the older endpoints, /v1/places/* returns a real HTTP status code (400, 401, 402, 404) alongside a stable error.code. See the Error Codes documentation.
Calling from the browser
/v1/* sends Access-Control-Allow-Origin: *, so you can call the autocomplete directly from your frontend without a proxy. Bear in mind that the API key is then visible to anyone who opens devtools — use a key with a budget you are comfortable exposing, or proxy through your own backend if that matters to you.
Practical tips
- Debounce at around 150 ms. Fewer requests, same feel.
- Require 2-3 characters before firing. Shorter queries are not useful.
- Pass
countrywhen you already know it. Fewer, better suggestions. - Pass
types=cityif the field is specifically a city field. - Cache nothing per user — but you are free to store and cache the returned data for as long as you want. There is no restriction on retaining it.
Related Endpoints
- GET /v1/places/details - Full record for a suggestion
- GET /v1/places/validate - Validate a full address
- GET /v1/places/postal-format - Postcode format per country