🎉 Welcome to PyVerse! Start Learning Today

Understanding APIs and Requests Module

What you'll learn

  • What an API is and how web APIs work
  • How to send HTTP requests with Python's requests library
  • How to read JSON responses and handle errors
  • How to use query parameters, headers, and timeouts
  • How to make GET and POST requests
  • Build a Mini Pokedex that fetches live data from a real API

1) What is an API?

  • API stands for Application Programming Interface. It's a set of rules that lets one program talk to another.
  • Think of it like a restaurant menu. You (the client) ask for a dish (data) using the menu's format (the API), and the kitchen (the server) returns it.
  • Web APIs use HTTP (the same protocol your browser uses) so your Python code can request data from websites and services.

2) How the web talks (HTTP basics)

Request: what your program sends

  • Method: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)
  • URL: where to send it
  • Headers: extra info (like "I want JSON")
  • Body: the data you send (mostly for POST/PUT)

Response: what the server sends back

  • Status code: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests, 500 Server Error
  • Headers: extra info (like rate limits)
  • Body: the data (often JSON)

3) JSON (the data format you'll see most)

JSON looks like Python dictionaries/lists. Example:

{ "name": "pikachu", "types": ["electric"], "stats": {"hp": 35, "attack": 55} }

In Python, you'll turn JSON into dict/list with response.json().

4) Meet the requests library

Install it:

  • In Terminal: pip install requests
  • In a Jupyter notebook: %pip install requests

Basic GET request example (GitHub public API):

import requests url = "https://api.github.com/repos/python/cpython" resp = requests.get(url, timeout=10) # timeout prevents hanging forever print("Status:", resp.status_code) resp.raise_for_status() # raises an error for 4xx/5xx data = resp.json() # parse JSON into a Python dict print("Repo name:", data["name"]) print("Stars:", data["stargazers_count"]) print("License:", data["license"]["name"] if data["license"] else "No license") # Some APIs include rate-limit info in headers: print("Rate limit remaining:", resp.headers.get("X-RateLimit-Remaining"))

5) Query parameters (asking for specific data)

Instead of building URLs manually, pass params as a dict. Example with a free weather API (no key needed):

import requests url = "https://api.open-meteo.com/v1/forecast" params = { "latitude": 52.52, "longitude": 13.41, "hourly": "temperature_2m" } resp = requests.get(url, params=params, timeout=10) resp.raise_for_status() data = resp.json() print("Timezone:", data["timezone"]) print("First 5 temps:", data["hourly"]["temperature_2m"][:5]) print("Final URL sent:", resp.url) # shows the URL with ?latitude=...&...

6) Headers (asking politely)

Some APIs want specific headers like Accept or User-Agent.

import requests url = "https://api.github.com/repos/python/cpython" headers = { "Accept": "application/vnd.github+json", "User-Agent": "PyVerse-Student-Example" # be descriptive and polite } resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() print(resp.json()["full_name"])

7) Handling errors and timeouts

  • Use timeout to avoid hanging forever.
  • Use try/except and raise_for_status to handle issues.
import requests from requests.exceptions import HTTPError, Timeout, RequestException url = "https://api.github.com/this/does/not/exist" try: resp = requests.get(url, timeout=5) resp.raise_for_status() data = resp.json() except Timeout: print("The request timed out. Try again later.") except HTTPError as e: print("HTTP error:", e, "Status:", getattr(e.response, "status_code", None)) except RequestException as e: print("Network or other error:", e) else: print("Success:", data)

Handling 429 Too Many Requests (basic backoff):

import time import requests url = "https://api.github.com/rate_limit" for attempt in range(3): r = requests.get(url, timeout=10) if r.status_code == 429: wait = (attempt + 1) * 2 # 2s, 4s, 6s print(f"Rate limited. Waiting {wait}s...") time.sleep(wait) continue r.raise_for_status() print(r.json()) break

8) Making a POST request (creating data)

Use JSONPlaceholder (a free test API) to simulate creating a post:

import requests url = "https://jsonplaceholder.typicode.com/posts" payload = { "title": "My first API post", "body": "Hello from Python!", "userId": 42 } resp = requests.post(url, json=payload, timeout=10) # json= auto-encodes and sets header resp.raise_for_status() print("Status:", resp.status_code) # often 201 Created print("Returned JSON:", resp.json()) # includes an id

9) Authentication (API keys and tokens)

  • Some APIs require keys. Never hard-code secrets in your script.
  • Store your key in an environment variable, then read it in Python:
import os import requests API_KEY = os.getenv("MY_API_KEY") # set this in your system first url = "https://example.com/data" headers = {"Authorization": f"Bearer {API_KEY}"} # or use params = {"apikey": API_KEY} if the API uses query params # resp = requests.get(url, headers=headers, timeout=10)

Check the API docs to see if they want keys in headers or params.

Practical Project: Mini Pokedex (CLI)

Goal

Build a command-line Pokedex using the free PokeAPI. You'll search for a Pokémon by name and display its info.

What you'll use

  • requests for HTTP calls
  • JSON parsing
  • Error handling and timeouts
  • Optional: download the sprite image

API docs: https://pokeapi.co/

Endpoint we'll use: https://pokeapi.co/api/v2/pokemon/{name}

Full program

# mini_pokedex.py import requests import random from requests.exceptions import HTTPError, Timeout API_BASE = "https://pokeapi.co/api/v2/pokemon/" def get_pokemon(name_or_id): """ Fetch Pokémon data by name or id from PokeAPI. Returns a dict with key info, or None if not found. """ url = API_BASE + str(name_or_id).strip().lower() try: resp = requests.get(url, timeout=10) resp.raise_for_status() except Timeout: print("Request timed out. Check your internet and try again.") return None except HTTPError as e: if e.response is not None and e.response.status_code == 404: print("Pokémon not found. Check the name or id.") else: print(f"HTTP error: {e}") return None except Exception as e: print(f"Network error: {e}") return None raw = resp.json() # Extract clean info name = raw["name"].title() pid = raw["id"] types = [t["type"]["name"].title() for t in raw["types"]] stats_map = {s["stat"]["name"]: s["base_stat"] for s in raw["stats"]} info = { "id": pid, "name": name, "height": raw["height"], # decimeters "weight": raw["weight"], # hectograms "types": types, "stats": { "hp": stats_map.get("hp"), "attack": stats_map.get("attack"), "defense": stats_map.get("defense"), "speed": stats_map.get("speed"), }, "sprite": raw["sprites"]["front_default"], } return info def print_pokemon(p): """Pretty-print selected Pokémon info.""" if not p: return print("-" * 40) print(f"{p['name']} (ID: {p['id']})") print(f"Types: {', '.join(p['types'])}") print(f"Height: {p['height']} dm Weight: {p['weight']} hg") s = p["stats"] print(f"Stats -> HP: {s['hp']} ATK: {s['attack']} DEF: {s['defense']} SPD: {s['speed']}") print(f"Sprite URL: {p['sprite']}") print("-" * 40) def download_sprite(url, filename): """Download the sprite image if a URL exists.""" if not url: print("No sprite available.") return try: r = requests.get(url, timeout=10) r.raise_for_status() with open(filename, "wb") as f: f.write(r.content) print(f"Saved sprite to {filename}") except Exception as e: print(f"Could not download sprite: {e}") def main(): print("Mini Pokedex") print("Type a Pokémon name or id (e.g., pikachu or 25).") print("Type 'random' for a random Pokémon, or 'quit' to exit.") while True: q = input("Search: ").strip() if q.lower() in {"quit", "exit"}: break if q.lower() == "random": # PokeAPI has many; choose a safe visible range q = random.randint(1, 898) p = get_pokemon(q) print_pokemon(p) if p and p["sprite"]: choice = input("Download sprite image? (y/n): ").strip().lower() if choice == "y": filename = f"{p['name'].lower()}_{p['id']}.png" download_sprite(p["sprite"], filename) print("Goodbye!") if __name__ == "__main__": main()

How to run

  • Save as mini_pokedex.py
  • Install requests: pip install requests
  • Run: python mini_pokedex.py
  • Try searches like pikachu, charizard, bulbasaur, or random

What you learned in this project

  • Building a URL endpoint and sending GET requests
  • Handling status codes and timeouts
  • Reading JSON and extracting nested fields
  • Optional file download using requests

Optional extensions

  • Cache results in a local JSON file so repeated searches don't call the API again.
  • Add more stats (special-attack, special-defense).
  • Support listing abilities and moves.
  • Add a command to compare two Pokémon by speed, attack, etc.

Pro tips and best practices

  • Always read the API's documentation (rate limits, auth, endpoints).
  • Use timeout in all calls.
  • Handle errors with try/except and resp.raise_for_status().
  • Respect rate limits. If you get 429, wait and retry.
  • Don't hard-code API keys. Use environment variables.

Summary

  • APIs let your Python code talk to web services using HTTP.
  • The requests library makes GET and POST simple, using URL, params, headers, and JSON.
  • You learned how to parse JSON, handle errors, use timeouts, and work with status codes.
  • You built a Mini Pokedex that fetches live data from PokeAPI and optionally downloads images.
  • These skills transfer to any modern web API you'll use in school projects or real apps.

Loading quizzes...