Learning Goals
3 min- Explain what an API is — the "menu" a server offers to programs.
- Install and import the third-party
requestslibrary. - Make a GET request, read the status code, parse the JSON body.
- Handle the obvious failure modes: no internet, 404, malformed JSON.
Warm-Up · What Is an API?
5 minAPI stands for Application Programming Interface. Most of the time on the web, "API" means an HTTP endpoint that takes a URL and returns JSON.
GET https://icanhazdadjoke.com/
Accept: application/json
─────────────────────────────────────
200 OK
{"id": "...", "joke": "Why don't skeletons fight each other? They don't have the guts."}The waiter analogy: you give the kitchen (server) an order via the menu (URL), it returns the dish (JSON). You don't need to know how the kitchen works.
Most modern Python apps grow by talking to APIs. Two lines with requests turn the entire internet into data your program can use.
New Concept · requests.get
14 minInstall once
requests is the de-facto HTTP library for Python. It's not built-in; install it once per environment:
pip install requests
The simplest call
import requests r = requests.get("https://icanhazdadjoke.com/", headers={"Accept": "application/json"}) print(r.status_code) # 200 means success print(r.json()) # the JSON body, parsed into a Python dict
200
{'id': 'R7UfaahVfFd', 'joke': "I'm reading a book about anti-gravity. It's impossible to put down.", 'status': 200}What the response gives you
r.status_code → int (200, 404, 500, ...) r.text → str (raw body, no parsing) r.json() → any (body parsed as JSON) r.headers → dict (response headers) r.url → str (final URL after redirects) r.ok → bool (True if status < 400)
Status codes — the three you'll see most
200 OK 404 Not Found (wrong URL or missing resource) 500 Server Error (their bug, not yours)
Handling failures
Two things can go wrong: the network itself, and a bad HTTP status. Handle both:
import requests try: r = requests.get("https://icanhazdadjoke.com/", headers={"Accept": "application/json"}, timeout=5) r.raise_for_status() # raises if status >= 400 data = r.json() print(data["joke"]) except requests.exceptions.RequestException as e: print(f"network error: {e}") except ValueError: print("response was not valid JSON")
Always pass timeout=. Without it your program can hang indefinitely if the server gets stuck.
Worked Example · Daily Random Dog Photo
12 mindog.ceo is a free no-key API that returns a random dog photo URL. Perfect for our first program.
# random_dog.py — first real API call import requests import webbrowser URL = "https://dog.ceo/api/breeds/image/random" try: r = requests.get(URL, timeout=5) r.raise_for_status() except requests.exceptions.RequestException as e: print(f"❌ network error: {e}") raise SystemExit data = r.json() print("status:", data["status"]) print("photo: ", data["message"]) # Pop the dog up in your default browser webbrowser.open(data["message"])
Sample output
status: success photo: https://images.dog.ceo/breeds/poodle-toy/n02113624_5060.jpg
The shape of the response is a dict with status and message keys. To know that, you check the API's docs — but you can also just print(data) the first time and read what came back.
Read the diff
Four lines do the actual work: get, raise_for_status, json, then use it. Everything else is error-handling boilerplate. From here every API is the same shape — only the URL and the response keys change.
Try It Yourself
13 minGet a fresh joke and print just the joke text — no extra fields.
Hint
import requests r = requests.get("https://icanhazdadjoke.com/", headers={"Accept": "application/json"}, timeout=5) print(r.json()["joke"])
https://api.ipify.org?format=json returns your public IP. Fetch it, print the IP as a single line.
Hint
import requests ip = requests.get("https://api.ipify.org?format=json", timeout=5).json()["ip"] print(f"public IP: {ip}")
Call the dad-joke API ten times in a loop and save the responses to jokes.json as a list of dicts. Add a 0.3-second pause between calls to be polite to the server.
Hint
import requests, json, time jokes = [] for _ in range(10): r = requests.get("https://icanhazdadjoke.com/", headers={"Accept": "application/json"}, timeout=5) jokes.append(r.json()) time.sleep(0.3) with open("jokes.json", "w") as f: json.dump(jokes, f, indent=2, ensure_ascii=False) print(f"saved {len(jokes)} jokes")
The time.sleep(0.3) is your manners — bombarding an API with 1000 requests per second is a great way to get blocked.
Mini-Challenge · Cat or Dog?
8 minBuild cat_or_dog.py. Ask the user "cat or dog?". Fetch a random photo of the chosen animal and open it in their browser. Cats live at https://api.thecatapi.com/v1/images/search (returns a list; the URL is at response[0]["url"]).
Show one possible solution
# cat_or_dog.py import requests, webbrowser choice = input("cat or dog? ").strip().lower() if choice == "dog": url = requests.get("https://dog.ceo/api/breeds/image/random", timeout=5).json()["message"] elif choice == "cat": data = requests.get("https://api.thecatapi.com/v1/images/search", timeout=5).json() url = data[0]["url"] else: print("not a real animal in this script.") raise SystemExit print("opening:", url) webbrowser.open(url)
Non-negotiables: branch on user input, fetch the correct API, open the resulting URL. Notice the cat API returns a list, not a dict — read the docs (or print first) before assuming a shape.
Recap
3 minAn API is a URL the server promises will return data. requests.get(url) fetches it; .status_code, .json() and .raise_for_status() let you check and parse it. Always pass timeout=. Always handle RequestException. Tomorrow we level up with query parameters and headers.
Vocabulary Card
- API
- A defined way for one program to ask another for data or actions.
- GET request
- The default HTTP method — "please give me this resource".
- status code
- 3-digit integer summarising the response. 200 = OK, 404 = not found, 500 = server bug.
- r.json()
- Parse the response body as JSON; returns a dict / list.
Homework
4 minPick a free no-key API from https://github.com/public-apis/public-apis. Examples: Bored API (random activity), Useless Facts, Public Holidays, Numbers API. Write my_api.py that:
- Fetches one response.
- Handles all three failure modes (timeout, bad status, bad JSON) with friendly messages.
- Pretty-prints the response.
Sample · my_api.py (Bored API)
# my_api.py — pick a random activity to do today import requests, json URL = "https://www.boredapi.com/api/activity" try: r = requests.get(URL, timeout=5) r.raise_for_status() data = r.json() except requests.exceptions.Timeout: print("⌛ took too long — try again later") raise SystemExit except requests.exceptions.HTTPError as e: print(f"🚫 server said {e.response.status_code}") raise SystemExit except ValueError: print("⚠️ response was not JSON") raise SystemExit print("✨ Try this activity!") print(json.dumps(data, indent=2, ensure_ascii=False)) print(f"\n→ {data['activity']}")
Non-negotiables: timeout, status check, JSON parse, all three errors caught separately.