Skip to main content
In this lesson we demonstrate two practical ways Claude Code helps when working with external APIs:
  • Generate a Python script that looks up a city’s coordinates with Nominatim and fetches current weather from OpenWeather.
  • Convert that script into a FastAPI application so others can request weather for a city via HTTP.
A presentation slide titled "Working with APIs and External Services" with a dark curved shape on the right containing the word "Demo" in blue. A small "© Copyright KodeKloud" note appears in the bottom-left.
Overview of the final deliverable
  • A single, runnable Python file (weather.py) that:
    • Accepts a city name
    • Uses Nominatim to resolve latitude/longitude
    • Uses OpenWeather to fetch current weather (API key read from OPENWEATHER_API_KEY)
    • Provides a CLI entrypoint (prints Celsius)
    • Exposes a FastAPI endpoint /weather/ that returns Fahrenheit JSON
Complete, consolidated, and runnable weather.py
#!/usr/bin/env python3
import os
import sys
from typing import Optional, Tuple, Dict, Any

import requests
from fastapi import FastAPI, HTTPException

# ----- Geocoding using Nominatim -----
def get_coordinates(city_name: str) -> Optional[Tuple[float, float]]:
    """
    Get latitude and longitude for a city using the Nominatim API.
    Returns (lat, lon) or None if city not found or on error.
    """
    url = "https://nominatim.openstreetmap.org/search"
    params = {
        "q": city_name,
        "format": "json",
        "limit": 1
    }
    headers = {
        "User-Agent": "WeatherScript/1.0 (contact@example.com)"
    }

    try:
        response = requests.get(url, params=params, headers=headers, timeout=10)
        response.raise_for_status()
        data = response.json()
        if not data:
            return None
        lat = float(data[0]["lat"])
        lon = float(data[0]["lon"])
        return lat, lon
    except requests.RequestException as e:
        print(f"Error fetching coordinates: {e}")
        return None

# ----- Weather using OpenWeather -----
def get_weather(lat: float, lon: float, api_key: str, units: str = "metric") -> Optional[Dict[str, Any]]:
    """
    Query the OpenWeather current weather API for given coordinates.
    units: 'metric' (Celsius) or 'imperial' (Fahrenheit).
    Returns JSON dict on success, or None on error.
    """
    url = "https://api.openweathermap.org/data/2.5/weather"
    params = {
        "lat": lat,
        "lon": lon,
        "appid": api_key,
        "units": units
    }

    try:
        response = requests.get(url, params=params, timeout=10)
        if response.status_code != 200:
            # Print response text for debugging if needed
            print(f"Debug: Response status: {response.status_code}, Response text: {response.text}")
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        print(f"Error fetching weather data: {e}")
        return None

# ----- CLI usage (prints Celsius) -----
def main_cli():
    if len(sys.argv) != 2:
        print("Usage: python3 weather.py \"City Name\"")
        sys.exit(1)

    city_name = sys.argv[1]
    api_key = os.getenv("OPENWEATHER_API_KEY")
    if not api_key:
        print("Error: OPENWEATHER_API_KEY environment variable not set")
        sys.exit(1)

    coords = get_coordinates(city_name)
    if not coords:
        print(f"Error: City '{city_name}' not found")
        sys.exit(1)

    lat, lon = coords
    weather_data = get_weather(lat, lon, api_key, units="metric")
    if not weather_data:
        print("Error: Failed to fetch weather data")
        sys.exit(1)

    temp_c = weather_data.get("main", {}).get("temp")
    conditions = weather_data.get("weather", [{}])[0].get("description", "unknown")

    print(f"City: {city_name}")
    print(f"Coordinates: {lat:.4f}, {lon:.4f}")
    if temp_c is not None:
        print(f"Temperature: {temp_c:.2f}°C")
    print(f"Conditions: {conditions}")

# ----- FastAPI application (returns Fahrenheit) -----
app = FastAPI(title="Weather API", description="Get weather information for cities")

@app.get("/weather/{city}")
async def get_weather_for_city(city: str):
    """
    Return JSON:
    {
      "city": <city>,
      "coordinates": { "latitude": <lat>, "longitude": <lon> },
      "temperature": "<value>°F",
      "conditions": "<description>"
    }
    """
    api_key = os.getenv("OPENWEATHER_API_KEY")
    if not api_key:
        raise HTTPException(status_code=500, detail="OpenWeather API key not configured")

    coords = get_coordinates(city)
    if not coords:
        raise HTTPException(status_code=404, detail=f"City '{city}' not found")

    lat, lon = coords
    weather_data = get_weather(lat, lon, api_key, units="imperial")
    if not weather_data:
        raise HTTPException(status_code=500, detail="Failed to fetch weather data")

    temp_f = weather_data.get("main", {}).get("temp")
    conditions = weather_data.get("weather", [{}])[0].get("description", "unknown")

    return {
        "city": city,
        "coordinates": {
            "latitude": round(lat, 4),
            "longitude": round(lon, 4)
        },
        "temperature": f"{temp_f:.2f}°F" if temp_f is not None else None,
        "conditions": conditions
    }

# Allow running the CLI when invoked directly
if __name__ == "__main__":
    main_cli()
Important usage notes
Note: Nominatim requires a valid User-Agent identifying your application. Respect their usage policy and rate limits when making requests.
Warning: Never commit API keys to source control. Use environment variables (e.g., OPENWEATHER_API_KEY) and restrict keys where possible. Also monitor and respect API rate limits to avoid service interruptions.
Quick setup and run commands
StepCommandPurpose
Create venvpython3 -m venv venvCreate a virtual environment
Activate venv (macOS/Linux)source venv/bin/activateActivate the venv
Install depspip install requests fastapi uvicornInstall required Python packages
Set API keyexport OPENWEATHER_API_KEY=“your_api_key_here”Provide OpenWeather credential
Run CLIpython3 weather.py “New York”Use script as a CLI (prints Celsius)
Run FastAPI serveruvicorn weather:app —reloadStart server on http://127.0.0.1:8000
Example FastAPI request GET http://127.0.0.1:8000/weather/Forest%20Grove Sample JSON response
{
  "city": "Forest Grove",
  "coordinates": {
    "latitude": 45.519,
    "longitude": -123.1111
  },
  "temperature": "60.76°F",
  "conditions": "overcast clouds"
}
Troubleshooting
  • 401 Unauthorized from OpenWeather: ensure OPENWEATHER_API_KEY is set in the same environment where you run Python or uvicorn. New keys may take a few minutes to activate.
  • City not found: Nominatim returned no results; verify spelling or try a larger query (e.g., include country).
  • requests missing: ensure your virtual environment is active and run pip install requests.
  • Python not found: use python3 on macOS/Linux if python points to Python 2.
Resources and References
ResourceDescriptionLink
Nominatim (OpenStreetMap)Free geocoding to get coordinates from a city namehttps://nominatim.org/
OpenWeather APICurrent weather API used for temperature and conditionshttps://openweathermap.org/api
FastAPIPython web framework used to expose the endpointhttps://fastapi.tiangolo.com/
requestsHTTP library used for external API requestshttps://docs.python-requests.org/
Suggested next steps
  • Add caching for geocoding results to reduce Nominatim queries.
  • Add input validation and rate limiting to the FastAPI app for production readiness.
  • Containerize the app with Docker for easy deployment.
  • Add tests for get_coordinates and get_weather functions to verify behavior with mocked HTTP responses.
This sequence demonstrates how Claude Code can scaffold working code, how to identify and debug small integration issues (API keys, headers, request params), and how to convert a simple script into a shareable HTTP API with FastAPI.

Watch Video

Practice Lab