APICountriesTutorial
Countries, States, and Cities API — Complete Guide 2026
ApogeoAPI6 min read
Geographic hierarchy — country → state → city — is one of the most common data needs in web apps. Shipping forms, user profiles, store locators, tax calculations. Here's everything you need to know about using a countries/states/cities API effectively.
What Data Does a Countries API Return?
A good countries API returns more than just country names. The core fields you should expect:
iso2,iso3— Standard country codesname— Official country namecapital— Capital cityregion,subregion— Geographic regionflag_url— Flag image URLcurrency,currency_symbol— Local currencyphone_code— International dial codetimezones— Array of IANA timezone strings
What About States and Cities?
The hierarchy goes: 250+ countries → 5,000+ states/provinces → 150,000+ cities. Not all APIs support all three levels. RestCountries has no cities. GeoDB Cities covers all three but with complex query params. ApogeoAPI covers all three with a simple REST structure.
Building a Cascading Country → State → City Selector
'use client';
import { useState, useEffect } from 'react';
const API = 'https://api.apogeoapi.com/v1';
const headers = { 'X-API-Key': process.env.NEXT_PUBLIC_APOGEO_KEY! };
export function LocationSelector() {
const [countries, setCountries] = useState([]);
const [states, setStates] = useState([]);
const [cities, setCities] = useState([]);
const [country, setCountry] = useState('');
const [state, setState] = useState('');
useEffect(() => {
fetch(`${API}/countries`, { headers }).then(r => r.json()).then(setCountries);
}, []);
useEffect(() => {
if (!country) return;
setStates([]); setState(''); setCities([]);
fetch(`${API}/countries/${country}/states`, { headers })
.then(r => r.json()).then(setStates);
}, [country]);
useEffect(() => {
if (!country || !state) return;
setCities([]);
fetch(`${API}/countries/${country}/states/${state}/cities`, { headers })
.then(r => r.json()).then(d => setCities(d.data ?? []));
}, [country, state]);
return (
<div className="flex flex-col gap-3">
<select value={country} onChange={e => setCountry(e.target.value)}>
<option value="">Select country</option>
{countries.map((c: any) => <option key={c.iso2} value={c.iso2}>{c.name}</option>)}
</select>
<select value={state} onChange={e => setState(e.target.value)} disabled={!states.length}>
<option value="">Select state</option>
{states.map((s: any) => <option key={s.code} value={s.code}>{s.name}</option>)}
</select>
<select disabled={!cities.length}>
<option value="">Select city</option>
{cities.map((c: any) => <option key={c.name} value={c.name}>{c.name}</option>)}
</select>
</div>
);
}
Which API Should You Use?
| Feature | RestCountries | GeoDB | ApogeoAPI |
|---|---|---|---|
| Countries | ✅ | ✅ | ✅ |
| States | ✅ | ✅ | ✅ |
| Cities (150K+) | ❌ | ✅ | ✅ |
| Exchange rates | ❌ | ❌ | ✅ |
| IP Geolocation | ❌ | ❌ | ✅ |
| Simple REST | ✅ | Partial | ✅ |
| Free tier | Always free | Very limited | Free + 14-day trial |
Performance Tips
- Cache countries and states on the client — they rarely change. Use SWR with a 24h stale time.
- Paginate cities — 150K cities don't load at once. Use
?page=1&limit=50and load more on scroll. - Pre-select via IP — Use IP geolocation to pre-select the user's country automatically on form load.
Try ApogeoAPI free
1,000 requests/month forever. 14-day full-access trial. No credit card.
Get your free API key