> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with APIs and External Services

> Guide to building a Python CLI and FastAPI service that geocodes cities with Nominatim, fetches OpenWeather data, converts temperatures to Fahrenheit, and returns JSON weather responses.

This lesson shows two practical approaches to integrating external APIs from Python:

1. Use a Claude-generated Python script to call public services:
   * Geocode a city name to latitude/longitude using Nominatim (OpenStreetMap).
   * Fetch current weather for those coordinates from OpenWeather.

2. Convert that script into a FastAPI application so anyone can request `/weather/{city}` and receive JSON with:
   * city name
   * coordinates (latitude, longitude)
   * temperature (in Fahrenheit)
   * short weather description

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/Xs8Ruv7b0QCZf7iM/images/Claude-Code-for-Beginners/Working-with-Claude-Code/Working-with-APIs-and-External-Services/working-with-apis-external-services-demo.jpg?fit=max&auto=format&n=Xs8Ruv7b0QCZf7iM&q=85&s=7b786325b61214d56987ace4846c977c" alt="A presentation slide titled &#x22;Working with APIs and External Services.&#x22; A dark curved panel on the right shows the word &#x22;Demo&#x22; in blue, with a small &#x22;© Copyright KodeKloud&#x22; at the bottom left." width="1920" height="1080" data-path="images/Claude-Code-for-Beginners/Working-with-Claude-Code/Working-with-APIs-and-External-Services/working-with-apis-external-services-demo.jpg" />
</Frame>

Overview

* Use [Nominatim (OpenStreetMap)](https://nominatim.openstreetmap.org) to geocode a city name to latitude/longitude.
* Use [OpenWeather](https://openweathermap.org/) to fetch current weather for those coordinates.
* Provide an optional CLI entrypoint and a FastAPI HTTP endpoint `/weather/{city}`.
* Return temperature in Fahrenheit (converted from metric API response), coordinates, and a short description.

Quick reference

| Item | Description | Example / Usage |
| - | - | - |
| Endpoint | FastAPI route to fetch weather for a city | `GET /weather/{city}` |
| Environment variable | OpenWeather API key used by both CLI and FastAPI | `OPENWEATHER_API_KEY` |
| Geocoding API | Nominatim (OpenStreetMap) | `https://nominatim.openstreetmap.org/search` |
| Weather API | OpenWeather current weather endpoint | `https://api.openweathermap.org/data/2.5/weather` |

weather.py (single file: CLI + FastAPI)
Below is the cleaned, production-friendly single-file example. It keeps logic in small functions for testability and exposes both CLI and HTTP access.

```python theme={null}
#!/usr/bin/env python3
import os
import sys
import requests
from typing import Optional, Tuple

from fastapi import FastAPI, HTTPException

app = FastAPI(title="Weather API", description="Get weather information for cities")


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

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


def get_weather(lat: float, lon: float, api_key: str, debug: bool = False) -> Optional[dict]:
    """
    Get current weather from OpenWeather using latitude and longitude.
    Uses metric units (Celsius) and returns the parsed JSON or None on error.
    """
    url = "https://api.openweathermap.org/data/2.5/weather"
    params = {
        "lat": lat,
        "lon": lon,
        "appid": api_key,
        "units": "metric",
    }

    if debug:
        short_key = api_key[:8] + "..." if api_key else "(no key)"
        print(f"Debug: Making request to {url}")
        print(f"Debug: Parameters: lat={lat}, lon={lon}, appid={short_key}")

    try:
        response = requests.get(url, params=params, timeout=10)
        if debug:
            print(f"Debug: Response status: {response.status_code}")
            if response.status_code != 200:
                print(f"Debug: 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


def c_to_f(temp_c: float) -> float:
    """Convert Celsius to Fahrenheit."""
    return temp_c * 9.0 / 5.0 + 32.0


def format_temperature_f(temp_c: float) -> str:
    """Return temperature formatted in Fahrenheit with two decimals and °F suffix."""
    temp_f = c_to_f(temp_c)
    return f"{temp_f:.2f}°F"


@app.get("/weather/{city}")
async def get_weather_for_city(city: str):
    """
    FastAPI endpoint: /weather/{city}
    Returns JSON with:
      - city: input city name
      - coordinates: latitude and longitude (rounded to 4 decimals)
      - temperature: Fahrenheit string e.g. "60.76°F"
      - conditions: weather description
    """
    api_key = os.getenv("OPENWEATHER_API_KEY")
    if not api_key:
        raise HTTPException(status_code=500, detail="OpenWeather API key not configured")

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

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

    # Extract temperature (Celsius) and description
    try:
        temp_c = weather_data["main"]["temp"]
        conditions = weather_data["weather"][0]["description"]
    except (KeyError, IndexError):
        raise HTTPException(status_code=500, detail="Unexpected response format from weather API")

    return {
        "city": city,
        "coordinates": {"latitude": round(lat, 4), "longitude": round(lon, 4)},
        "temperature": format_temperature_f(temp_c),
        "conditions": conditions,
    }


def main():
    # CLI entrypoint: python3 weather.py "New York"
    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
    # Set debug=True if you want to see request/response debug logs
    weather_data = get_weather(lat, lon, api_key, debug=False)
    if not weather_data:
        print("Error: Failed to fetch weather data")
        sys.exit(1)

    temp_c = weather_data["main"]["temp"]
    conditions = weather_data["weather"][0]["description"]

    print(f"City: {city_name}")
    print(f"Coordinates: {lat:.4f}, {lon:.4f}")
    print(f"Temperature: {temp_c:.2f}°C / {format_temperature_f(temp_c)}")
    print(f"Conditions: {conditions}")


if __name__ == "__main__":
    main()
```

Running locally (example)

1. Create and activate a virtual environment and install dependencies:

```bash theme={null}
python3 -m venv venv
source venv/bin/activate
pip install requests fastapi uvicorn
```

2. Set your OpenWeather API key (replace with your actual key):

```bash theme={null}
export OPENWEATHER_API_KEY=your_actual_api_key_here
```

3a. Run the CLI example:

```bash theme={null}
python3 weather.py "New York"
```

Example CLI output (when successful):

```text theme={null}
City: New York
Coordinates: 40.7127, -74.0060
Temperature: 18.93°C / 66.07°F
Conditions: clear sky
```

3b. Run the FastAPI app with uvicorn:

```bash theme={null}
uvicorn weather:app --reload
```

Then open: [http://localhost:8000/weather/New%20York](http://localhost:8000/weather/New%20York)

Example JSON response:

```json theme={null}
{
  "city": "Forest Grove",
  "coordinates": {
    "latitude": 45.519,
    "longitude": -123.1111
  },
  "temperature": "60.76°F",
  "conditions": "overcast clouds"
}
```

Troubleshooting and tips

<Callout icon="lightbulb" color="#1CB2FE">
  Tips:

  * Use `python3` on macOS (some systems don't have `python` mapped to Python 3).
  * If you get a 401 Unauthorized from OpenWeather, verify the `OPENWEATHER_API_KEY` environment variable is set correctly and that the key is active (new keys can take a few minutes to become active).
  * For debugging, set `debug=True` when calling `get_weather` to print the request/response status and the first 8 characters of your API key (the code is already prepared to show these debug lines). Avoid enabling debug logging in production, as even truncated key output can be sensitive.
  * Respect Nominatim's usage policy: include a descriptive User-Agent (as shown), avoid excessive requests, and follow rate limits. For heavy usage or production systems consider a dedicated geocoding service or local instance. See Nominatim's usage policy for details: [https://operations.osmfoundation.org/policies/nominatim/](https://operations.osmfoundation.org/policies/nominatim/)
  * The FastAPI endpoint in this example calls synchronous `requests` functions from an `async` endpoint. This will block the event loop for the duration of those requests. For production use, either:
    * use an async HTTP client such as [`httpx.AsyncClient`](https://www.python-httpx.org/), or
    * run blocking calls in a thread pool (e.g., via `asyncio.to_thread` or FastAPI dependency/background task) to avoid blocking other requests.
</Callout>

What we demonstrated

* Combining Nominatim and OpenWeather APIs from Python to build a small, useful service.
* Handling common errors (missing API key, city not found, API failures, unexpected response formats).
* Converting a CLI script into a simple HTTP API with FastAPI so other people or services can request weather by city name.

References and further reading

* [Nominatim (OpenStreetMap) documentation](https://nominatim.openstreetmap.org)
* [OpenWeather API](https://openweathermap.org/api)
* [FastAPI documentation](https://fastapi.tiangolo.com/)
* [httpx — an async HTTP client for Python](https://www.python-httpx.org/)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/claude-code-for-beginners/module/3e896e50-3c07-4fdc-8603-bf125255d0a9/lesson/5681f91d-7538-4d69-80d9-f9848600103d" />

  <Card title="Practice Lab" icon="flask-conical" cta="Learn more" href="https://learn.kodekloud.com/user/courses/claude-code-for-beginners/module/3e896e50-3c07-4fdc-8603-bf125255d0a9/lesson/2afc3432-4992-433d-8008-19d47d86ad48" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.