> ## 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.

# Demo Working with APIs and External Services

> Demonstrates building a Python script and FastAPI service that geocodes cities with Nominatim and fetches current weather from OpenWeather using an environment API key

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.

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

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/{city} that returns Fahrenheit JSON

Complete, consolidated, and runnable weather.py

```python theme={null}
#!/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

<Callout icon="lightbulb" color="#1CB2FE">
  Note: Nominatim requires a valid User-Agent identifying your application. Respect their [usage policy](https://operations.osmfoundation.org/policies/nominatim/) and rate limits when making requests.
</Callout>

<Callout icon="warning" color="#FF6B6B">
  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.
</Callout>

Quick setup and run commands

| Step                        | Command                                             | Purpose                                                        |
| --------------------------- | --------------------------------------------------- | -------------------------------------------------------------- |
| Create venv                 | python3 -m venv venv                                | Create a virtual environment                                   |
| Activate venv (macOS/Linux) | source venv/bin/activate                            | Activate the venv                                              |
| Install deps                | pip install requests fastapi uvicorn                | Install required Python packages                               |
| Set API key                 | export OPENWEATHER\_API\_KEY="your\_api\_key\_here" | Provide OpenWeather credential                                 |
| Run CLI                     | python3 weather.py "New York"                       | Use script as a CLI (prints Celsius)                           |
| Run FastAPI server          | uvicorn weather:app --reload                        | Start server on [http://127.0.0.1:8000](http://127.0.0.1:8000) |

Example FastAPI request
GET [http://127.0.0.1:8000/weather/Forest%20Grove](http://127.0.0.1:8000/weather/Forest%20Grove)

Sample JSON response

```json theme={null}
{
  "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

| Resource                  | Description                                             | Link                                                                   |
| ------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- |
| Nominatim (OpenStreetMap) | Free geocoding to get coordinates from a city name      | [https://nominatim.org/](https://nominatim.org/)                       |
| OpenWeather API           | Current weather API used for temperature and conditions | [https://openweathermap.org/api](https://openweathermap.org/api)       |
| FastAPI                   | Python web framework used to expose the endpoint        | [https://fastapi.tiangolo.com/](https://fastapi.tiangolo.com/)         |
| requests                  | HTTP library used for external API requests             | [https://docs.python-requests.org/](https://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.

<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/2966483e-d854-40d5-acbd-023cce64e482" />
</CardGroup>
