Chapter 21 Exercises: Working with APIs and External Data Services

These exercises progress through five tiers of difficulty. Complete each tier before advancing. All exercises use freely available public APIs where noted — no paid subscription should be required for any Tier 1–3 exercise.


Tier 1: Comprehension (Understanding the Concepts)

These exercises confirm you understand the vocabulary and mental models from the chapter.

Exercise 21.1 — API Vocabulary Matching

Match each term to its correct definition:

Terms: endpoint, status code, query parameter, response body, authentication, rate limit, JSON, pagination

Definitions: a) A three-digit number the server sends back indicating whether a request succeeded or failed b) The main data payload the server returns, typically formatted as key-value pairs c) A constraint on how many API requests you can make per unit of time d) A specific URL that represents a resource or operation you can call e) Key-value pairs appended to the URL after a ? to filter or configure results f) A data format that maps directly to Python dictionaries and lists g) The process of verifying who is making an API request, typically via a key or token h) The technique of returning large result sets in multiple sequential page requests

Exercise 21.2 — HTTP Method Selection

For each business operation, identify the correct HTTP method (GET, POST, PUT, PATCH, DELETE):

a) Retrieve a list of all open customer invoices from an accounting API b) Submit a new expense report to an expenses API c) Change the status field of an existing order from "pending" to "shipped" d) Replace an entire customer record with an updated version e) Download current commodity pricing data f) Delete a draft report from a reporting system g) Look up the current exchange rate for USD to EUR

Exercise 21.3 — Status Code Interpretation

For each scenario, identify the likely HTTP status code and explain what you should do:

a) You call an API and get a response containing data — everything worked as expected b) Your API key was recently regenerated but you forgot to update your script c) You're calling the API 500 times per minute but your plan only allows 60 d) You passed a customer_id parameter but spelled it customeId (missing the 'r') e) The API server has a bug in its code and cannot process any requests right now f) You requested /v1/orders/999999 but order 999999 does not exist

Exercise 21.4 — Credentials Security Review

Review this code snippet and identify every security problem:

import requests

# Fetch competitor pricing data
api_key = "sk_live_ac92847fab12cc309814aab7723901ff"

response = requests.get(
    "https://api.marketdata.com/v1/prices",
    params={
        "category": "office-supplies",
        "apikey": api_key,
    }
)

data = response.json()
print(data["prices"])

List every problem you can find, explain why it is a problem, and write the corrected version of the code.

Exercise 21.5 — JSON Navigation

Given this nested JSON response from a hypothetical company data API:

company_data = {
    "id": "COMP-8821",
    "name": "Pinnacle Office Solutions",
    "status": "active",
    "financials": {
        "fiscal_year": "2024",
        "revenue": {
            "annual": 143_500_000,
            "q4": 38_200_000,
            "currency": "USD"
        },
        "segments": [
            {"name": "Commercial", "revenue": 98_700_000, "margin": 0.142},
            {"name": "Retail", "revenue": 44_800_000, "margin": 0.091},
        ]
    },
    "locations": [
        {"city": "Chicago", "type": "headquarters"},
        {"city": "Cleveland", "type": "warehouse"},
        {"city": "Detroit", "type": "warehouse"},
    ],
    "contact": {
        "email": "info@pinnacle.com",
        "phone": None
    }
}

Write Python expressions to extract: a) The company's annual revenue as an integer b) The Q4 revenue as a formatted string: "$38,200,000" c) The margin for the "Commercial" segment d) A list of all city names where Pinnacle has locations e) The contact phone number, with a default of "Not provided" if it's None f) The total margin weighted by segment revenue (hint: this requires a calculation)


Tier 2: Guided Practice (Following Patterns)

These exercises give you specific API calls to make using the patterns from the chapter.

Exercise 21.6 — Your First API Call

Using Open-Meteo (free, no API key required: https://api.open-meteo.com), write a script that:

  1. Fetches the current weather for your city (look up its latitude/longitude)
  2. Prints the temperature in both Celsius and Fahrenheit
  3. Prints the wind speed in km/h and mph
  4. Prints whether conditions are suitable for outdoor work (your judgment call on the threshold)

Starter URL structure:

https://api.open-meteo.com/v1/forecast?latitude=<lat>&longitude=<lon>&current_weather=true

Exercise 21.7 — Exchange Rate Calculator

Using the open.er-api.com free API (no key required):

  1. Fetch current exchange rates with USD as the base currency
  2. Write a function convert_currencies(amount, from_currency, to_currency, rates) that converts between any two currencies (not just from/to USD)
  3. Test your function with: - 1,000 EUR to GBP - 50,000 JPY to USD - 5,000 CAD to EUR
  4. Print a formatted conversion table showing the result and the intermediate rate used

Hint: To convert EUR to GBP, you need to go EUR → USD → GBP.

Exercise 21.8 — Multi-City Weather Comparison

Acme Corp has warehouses in four cities. Using Open-Meteo:

  1. Fetch current weather for all four cities: - Chicago (41.8781, -87.6298) - Cleveland (41.4993, -81.6944) - Memphis (35.1495, -90.0490) - Denver (39.7392, -104.9903)

  2. Print a formatted table with city, temperature (°F), and conditions

  3. Identify which warehouse has the most extreme weather today
  4. Write the results to a CSV file with a timestamp column

Exercise 21.9 — Error Handling Practice

Take this minimal API call and wrap it in proper error handling:

import requests

response = requests.get(
    "https://open.er-api.com/v6/latest/USD",
)
data = response.json()
rates = data["rates"]
eur_rate = rates["EUR"]
print(f"1 USD = {eur_rate} EUR")

Add handling for: - No internet connection (ConnectionError) - Request timeout (add a 10-second timeout) - Non-200 status code - Response that doesn't parse as JSON - Response that parses as JSON but is missing the "rates" key - "EUR" not being present in the rates dictionary

Each error case should print a clear, actionable message explaining what went wrong and what the user should do.


Tier 3: Applied (Building Business Tools)

These exercises require independent design decisions and combine multiple concepts.

Exercise 21.10 — Historical Exchange Rate Tracker

Build a script that:

  1. Fetches today's exchange rates for these currencies: EUR, GBP, CAD, JPY, AUD, MXN, CHF
  2. Saves them to a CSV file called exchange_rates_history.csv
  3. If the CSV already exists, appends today's rates (does not overwrite)
  4. If a currency's rate has changed more than 2% since the previous day's entry in the CSV, prints a warning
  5. Prints a summary showing today's rates and any significant movements

Requirement: The CSV file must be usable as a historical record — each row should include the date, currency, and rate.

Exercise 21.11 — Paginated Data Retrieval

Some APIs return results in pages. Simulate this experience:

  1. Use the RestCountries API (https://restcountries.com/v3.1/all) to fetch information about all countries
  2. This API returns all results in one call, but filter the response to only include countries in: - North America, South America, or Europe - With a population greater than 1,000,000
  3. Extract for each country: name, capital, population, region, and official language(s)
  4. Save the results to a CSV file, sorted by population descending
  5. Print summary statistics: total countries, total population, and average population

Note: The RestCountries API is completely free and requires no authentication.

Exercise 21.12 — Industry News Monitor

Build a news monitoring tool for a business of your choice:

  1. Sign up for a free NewsAPI key at https://newsapi.org/register
  2. Store your key as an environment variable
  3. Search for news using at least three different search queries relevant to the business
  4. Deduplicate results by URL
  5. Filter out articles with [Removed] titles
  6. Display the results grouped by source (news outlet)
  7. Save all articles to a CSV with columns: date, source, title, description, url

Requirement: The script must handle the case where the API key is not set and print a clear error message rather than crashing.

Exercise 21.13 — Retry Logic Implementation

Build a function called robust_get(url, params=None, headers=None, max_retries=3) that:

  1. Makes a GET request to the specified URL
  2. On 429 Too Many Requests: reads the Retry-After header (if present) and waits that many seconds, then retries
  3. On 500, 502, 503, 504: waits with exponential backoff (1s, 2s, 4s...) and retries
  4. On connection timeout: retries with exponential backoff
  5. On 401, 403, 404: raises the error immediately (retrying won't help)
  6. Logs each retry attempt with a timestamp

Test your function by calling it against a real URL and verifying the success case works correctly.


Tier 4: Challenge (Independent Design)

These exercises require significant independent problem-solving. There is no single correct answer.

Exercise 21.14 — Complete API Client Class

Design and build an APIClient class that a non-technical team member could use to call any JSON API. Requirements:

Functional requirements: - Constructor accepts: base_url, api_key (optional), api_key_header (optional), bearer_token (optional) - Methods: get(endpoint, params), post(endpoint, data), get_all_pages(endpoint, params, results_key, page_size) - All methods return parsed JSON - All methods retry on rate limits and server errors - All methods include timeouts

Non-functional requirements: - Every method logs its activity at the DEBUG level - Every error produces a user-readable message (not a raw exception traceback) - The class works as a context manager (supports with APIClient(...) as client:) - Constructor validates that at least base_url was provided

Write at least three test calls using free public APIs to demonstrate the class works.

Exercise 21.15 — Multi-Source Business Intelligence Report

Build a script that produces a weekly market intelligence brief by combining:

  1. Exchange rates from open.er-api.com — for at least 5 currencies
  2. Weather from Open-Meteo — for at least 3 cities relevant to your fictional business
  3. News from NewsAPI — for at least 2 search queries
  4. RestCountries data — showing GDP and population for countries where the business operates

The output should be a well-formatted text file that a manager could read in 5 minutes. It should include: - A one-paragraph executive summary (you write this manually based on the data) - All data tables clearly labeled with sources and dates - Any notable anomalies flagged (significant exchange rate movements, severe weather, etc.)

Requirement: The script must run successfully even if one of the API calls fails — it should note what's missing but produce a partial report rather than crashing.


Tier 5: Mastery (Extending the Chapter Tools)

These exercises build on the chapter's code files directly.

Exercise 21.16 — Extend the Currency Tracker

The currency_tracker.py file saves daily rates to a CSV and converts sales records. Extend it to:

  1. Trend detection: After appending today's rates, analyze the historical CSV and flag any currency that has moved more than 5% in either direction over the past 7 days
  2. Visualization: Add a function that reads the historical CSV and prints an ASCII chart of a specified currency's rate over time (e.g., the last 30 days of EUR/USD)
  3. What-if analysis: Add a function simulate_rate_impact(sales_records, base_rates, alternative_rates) that shows how the total USD revenue would differ under a different exchange rate scenario

Exercise 21.17 — Extend the API Client

The api_client.py class handles GET and POST. Extend it to handle the full REST pattern:

  1. Add put(endpoint, data) and patch(endpoint, data) methods
  2. Add a delete(endpoint) method
  3. Add a request_count property that tracks how many API calls this client instance has made
  4. Add a rate_limit_remaining property that reads from the last response's headers (returns None if not available)
  5. Add an optional cache parameter: when enabled, get() calls with identical URL+params combinations return cached results for a configurable TTL (time-to-live in seconds)

Exercise 21.18 — Extend the Client Briefing Generator (Maya's Tool)

The case-study-02.md script generates a pre-call briefing. Extend it with:

  1. Batch processing: A --batch-file argument that accepts a CSV of companies (with columns: company_name, ticker, city) and generates a briefing for each one
  2. Comparison view: When run with --compare TICKER1 TICKER2, generate a side-by-side comparison of two companies' key financial metrics
  3. Template customization: Load the briefing template from an external Markdown file so Maya can customize the format without editing Python code
  4. Email delivery: Add an --email flag that, when specified, sends the briefing to the configured email address using smtplib (connecting to Chapter 19's email automation work)

Answer Key Notes

Exercise 21.1: a-status code, b-response body, c-rate limit, d-endpoint, e-query parameter, f-JSON, g-authentication, h-pagination

Exercise 21.2: a-GET, b-POST, c-PATCH, d-PUT, e-GET, f-DELETE, g-GET

Exercise 21.3: a-200, b-401, c-429, d-400, e-500, f-404

Exercise 21.4: Problems: (1) API key is hardcoded in the source code — should be in an environment variable; (2) response.json() is called without first checking response.status_code — will fail or produce garbage on error responses; (3) data["prices"] uses direct dictionary access rather than .get() — will crash with KeyError if the key is missing; (4) No timeout specified — request could hang indefinitely.

Exercise 21.5 hints: d) requires a list comprehension over company_data["locations"]; f) is a weighted average calculation: sum(seg["revenue"] * seg["margin"] for seg in segments) / total_revenue


Exercises designed for Python 3.10+. All APIs referenced were operational and free as of early 2026.