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 codes
  • name — Official country name
  • capital — Capital city
  • region, subregion — Geographic region
  • flag_url — Flag image URL
  • currency, currency_symbol — Local currency
  • phone_code — International dial code
  • timezones — 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?

FeatureRestCountriesGeoDBApogeoAPI
Countries
States
Cities (150K+)
Exchange rates
IP Geolocation
Simple RESTPartial
Free tierAlways freeVery limitedFree + 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=50 and 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