Prerequisites
You need three things:
- Python 3.8 or newer.
- The
requestslibrary. - A free API Ninjas API key — issued instantly, no credit card. If you already have an account, it's on your profile page.
pip install requestsStep 1: Geocode your first city
The endpoint is /v1/geocoding. Pass a city (a US 5-digit ZIP code works too), and optionally country and state:
import requests
response = requests.get(
'https://api.api-ninjas.com/v1/geocoding',
params={'city': 'San Francisco', 'country': 'US'},
headers={'X-Api-Key': 'YOUR_API_KEY'},
timeout=10,
)
response.raise_for_status()
results = response.json()
place = results[0]
print(f"{place['latitude']}, {place['longitude']}")
# 37.7790262, -122.4199061The request on the wire and the full response:
[
{
"name": "San Francisco",
"latitude": 37.7790262,
"longitude": -122.4199061,
"country": "US",
"state": "California"
}
]Coordinates are WGS 84 decimal degrees — the format every map library and GPS system expects.
Step 2: Handle ambiguous names
The response is a list because city names repeat across the world. The fix is to be specific — pass country, and state where countries reuse names internally. A small helper that returns the best match (or None) keeps the rest of your code clean:
import os
import requests
API_URL = 'https://api.api-ninjas.com/v1/geocoding'
session = requests.Session()
session.headers['X-Api-Key'] = os.environ.get('API_NINJAS_KEY', 'YOUR_API_KEY')
def geocode(city: str, country: str | None = None, state: str | None = None) -> dict | None:
"""Return the best match for a place name, or None if nothing matched."""
params = {'city': city}
if country:
params['country'] = country
if state:
params['state'] = state
response = session.get(API_URL, params=params, timeout=10)
response.raise_for_status()
results = response.json()
return results[0] if results else None
place = geocode('San Francisco', country='US')
print(f"{place['name']}: {place['latitude']:.4f}, {place['longitude']:.4f}")# "Paris" alone matches France AND Texas — disambiguate with country/state
paris_fr = geocode('Paris', country='FR')
paris_tx = geocode('Paris', country='US', state='Texas')
print(f"Paris, France: {paris_fr['latitude']:.3f}, {paris_fr['longitude']:.3f}")
print(f"Paris, Texas: {paris_tx['latitude']:.3f}, {paris_tx['longitude']:.3f}")Step 3: Geocode a batch
Store lists, office locations, event venues — batch geocoding is a loop with the shared session:
places = [('London', 'GB'), ('Paris', 'FR'), ('Tokyo', 'JP'), ('Sydney', 'AU')]
for city, country in places:
place = geocode(city, country=country)
if place:
print(f"{city:10} ({country}) {place['latitude']:>9.4f}, {place['longitude']:>10.4f}")Geocode once and store the coordinates — place names don't move, so there's no reason to resolve the same city on every page load.
Step 4: Chain into other APIs
Coordinates are rarely the goal — they're the key that unlocks location APIs. The classic chain is geocode-then-weather, both on the same API key:
def weather_for_city(city: str, country: str) -> dict:
"""Geocode a city, then fetch current weather for its coordinates."""
place = geocode(city, country=country)
if not place:
raise ValueError(f'Could not geocode {city}, {country}')
response = session.get(
'https://api.api-ninjas.com/v1/weather',
params={'lat': place['latitude'], 'lon': place['longitude']},
timeout=10,
)
response.raise_for_status()
return response.json()
weather = weather_for_city('Reykjavik', 'IS')
print(f"Reykjavik: {weather['temp']}°C, wind {weather['wind_speed']} m/s")The same two-step works for the Timezone API and Air Quality API — any endpoint that takes lat/lon.
Step 5: Handle empty results
An unknown or misspelled place is not an HTTP error — the API returns an empty list. The helper from Step 2 turns that into None; always check it before using the result:
place = geocode('Atlantis')
if place is None:
print('No match found — check the spelling or add country/state')Where to go from here
For the reverse direction — coordinates back to a place — use Reverse Geocoding. And if your input is an IP address rather than a city name, the IP Lookup API jumps straight to coordinates. The complete script is below.