Python with AI Tutorial · Chapter 35 of 48
Python requests is the most popular library for talking to web services. With one line you can call a REST API, and with .json() you turn the response into Python dictionaries and lists. This chapter covers GET and POST calls, query parameters, headers, timeouts, error handling and how to pull live exchange rates into a sales report.
Installing requests
requests is not part of the standard library, so install it once with pip. The examples below use two free public APIs: GitHub and open.er-api.com for exchange rates.
pip install requests
Output: Successfully installed requests-2.32.3 ...
Your first GET request
requests.get() sends the request and returns a Response object. Always pass a timeout so a slow server cannot hang your script for ever.
import requests
r = requests.get("https://api.github.com/repos/pandas-dev/pandas", timeout=10)
print(r.status_code)
print(r.headers["Content-Type"])
data = r.json()
print(data["full_name"])
print(data["language"], data["stargazers_count"] > 10000)
Output: 200 application/json; charset=utf-8 pandas-dev/pandas Python True
r.json() parses the JSON body into a normal dictionary, so data["full_name"] works exactly like any other dictionary lookup. GitHub allows 60 unauthenticated calls per hour, which is plenty for learning.
Reading JSON from an exchange-rate API
Most APIs return nested JSON. Walk through it with keys just as you would with a dictionary loaded from a file.
import requests
r = requests.get("https://open.er-api.com/v6/latest/USD", timeout=10)
data = r.json()
print(data["result"], data["base_code"])
print("INR:", data["rates"]["INR"])
print("EUR:", data["rates"]["EUR"])
print("Currencies returned:", len(data["rates"]))
Output: success USD INR: 83.51 EUR: 0.92 Currencies returned: 162
The exact rates change every day, so your numbers will differ. The structure of the JSON stays the same, which is what your code depends on.
Query parameters with params
Never build query strings by hand. Pass a dictionary to params= and requests encodes spaces, colons and other characters correctly.
import requests
params = {"q": "excel dashboard language:python", "sort": "stars", "per_page": 3}
r = requests.get("https://api.github.com/search/repositories", params=params, timeout=10)
print(r.url)
items = r.json()["items"]
print(len(items), "repositories returned")
print(items[0]["full_name"])
Output: https://api.github.com/search/repositories?q=excel+dashboard+language%3Apython&sort=stars&per_page=3 3 repositories returned some-user/excel-dashboard
The last line shows whichever repository currently has the most stars, so it will vary. Notice how r.url reveals the final encoded address, which is handy when debugging.
HTTP status codes you will meet
r.status_code tells you whether the call worked. r.ok is True for any code below 400.
| Code | Meaning | What to do |
|---|---|---|
| 200 | OK | Parse the body |
| 201 | Created | A POST succeeded; the new record is usually returned |
| 204 | No Content | Success with an empty body; do not call .json() |
| 301 / 302 | Redirect | requests follows these automatically |
| 400 | Bad Request | Check your parameters or JSON payload |
| 401 | Unauthorized | Missing or invalid API key or token |
| 403 | Forbidden | Valid login but no permission, or rate limit on GitHub |
| 404 | Not Found | Wrong URL or the record does not exist |
| 429 | Too Many Requests | Slow down; read the Retry-After header |
| 500 / 503 | Server error | Retry later with a delay |
Handling errors properly
r.raise_for_status() converts a 4xx or 5xx response into an exception. Combine it with the requests exception classes so network problems produce a helpful message instead of a crash.
import requests
def get_json(url, **params):
try:
r = requests.get(url, params=params, timeout=10)
r.raise_for_status()
return r.json()
except requests.exceptions.HTTPError as e:
print("HTTP error:", e.response.status_code)
except requests.exceptions.ConnectionError:
print("Network problem - check your connection")
except requests.exceptions.Timeout:
print("The server took too long to respond")
return None
print(get_json("https://api.github.com/repos/pandas-dev/does-not-exist"))
Output: HTTP error: 404 None
Paste the JSON you got back and ask for code that extracts exactly the fields you need.
I called https://open.er-api.com/v6/latest/USD with Python requests and r.json() returned a dictionary with keys result, base_code, time_last_update_utc and rates (a dict of currency code to float). Write a function usd_to(codes) that takes a list like ["INR", "EUR", "GBP"] and returns a dictionary of code to rate rounded to 4 decimals, uses a 10 second timeout, calls raise_for_status(), and returns an empty dict with a printed warning if the request fails.
Using API data in a sales report
Here is the pattern you will use most: fetch a reference value once, then apply it to your own business data.
import requests
sales = [("North", "Dashboard", 3996), ("South", "Tracker", 2990), ("East", "Dashboard", 1998)]
rates = requests.get("https://open.er-api.com/v6/latest/USD", timeout=10).json()["rates"]
inr = rates["INR"]
print(f"Rate used: 1 USD = {inr} INR")
for region, product, usd in sales:
print(f"{region:<6} {product:<10} USD {usd:>5} INR {usd * inr:>10,.0f}")
Output: Rate used: 1 USD = 83.51 INR North Dashboard USD 3996 INR 333,706 South Tracker USD 2990 INR 249,695 East Dashboard USD 1998 INR 166,853
Sending data with POST
To create or submit records, use requests.post() with json=. requests serialises the dictionary and sets the Content-Type header for you. httpbin.org echoes back whatever you send, which makes it a safe practice target.
import requests
payload = {"Region": "West", "Product": "Tracker", "Units": 5, "Revenue": 1495, "Date": "2026-01-09"}
r = requests.post("https://httpbin.org/post", json=payload, timeout=10)
print(r.status_code)
print(r.json()["json"])
Output:
200
{'Region': 'West', 'Product': 'Tracker', 'Units': 5, 'Revenue': 1495, 'Date': '2026-01-09'}
Headers, tokens and sessions
Authenticated APIs expect a token in the Authorization header. Read it from an environment variable, and when you make several calls to the same host use a Session, which reuses the connection and applies shared headers.
import os
import requests
headers = {"Accept": "application/vnd.github+json"}
token = os.environ.get("GITHUB_TOKEN") # optional: raises the limit to 5000/hour
if token:
headers["Authorization"] = f"Bearer {token}"
with requests.Session() as s:
s.headers.update(headers)
for repo in ["pandas-dev/pandas", "numpy/numpy", "matplotlib/matplotlib"]:
r = s.get(f"https://api.github.com/repos/{repo}", timeout=10)
print(f"{repo:<24} {r.json()['stargazers_count']:>7,} stars")
print("Calls left this hour:", r.headers["X-RateLimit-Remaining"])
Output: pandas-dev/pandas 45,812 stars numpy/numpy 29,640 stars matplotlib/matplotlib 21,375 stars Calls left this hour: 56
.env file that is excluded from Git. A key pasted into a script ends up in screenshots, ChatGPT prompts and public repositories faster than you expect.Ask for a robust wrapper with retries and rate-limit handling around an API you use at work.
Write a Python function fetch_all_pages(url, params, token) using requests that: sends a Bearer token header, follows pagination by reading the Link header (GitHub style, rel="next"), waits and retries up to 3 times with exponential backoff on status 429 or 5xx, uses a Session and a 15 second timeout, and returns all items as one list. Add short comments and a usage example for https://api.github.com/orgs/pandas-dev/repos.
Common mistakes
- Leaving out
timeout=. requests waits for ever by default, so one dead server can freeze a scheduled job. - Calling
.json()on an HTML error page or an empty 204 body, which raisesJSONDecodeError. Checkr.okfirst. - Building URLs with string concatenation instead of
params=, which breaks on spaces and special characters. - Ignoring rate limits and getting a 403 or 429. Cache results and add
time.sleep()between calls. - Passing a dictionary to
data=when the API wants JSON.data=sends form fields;json=sends JSON.
Exercise
Call https://open.er-api.com/v6/latest/USD and print how much 1,000 USD is worth in EUR, GBP and INR, each rounded to two decimals and formatted like EUR: 920.00. Use a timeout and raise_for_status().
Show answer
import requests
r = requests.get("https://open.er-api.com/v6/latest/USD", timeout=10)
r.raise_for_status()
rates = r.json()["rates"]
for code in ["EUR", "GBP", "INR"]:
print(f"{code}: {1000 * rates[code]:.2f}")
Output: EUR: 920.00 GBP: 790.00 INR: 83510.00
Your figures will reflect the current rates.
Related chapters
- Python JSON – how
json.loads()andjson.dumps()work under.json(). - Python Web Scraping – when there is no API, read the HTML instead.
- Python Try Except – the exception handling used in
get_json(). - Python with AI course hub – all 48 chapters in order.
FAQ
What is the Python requests library used for?
requests sends HTTP requests such as GET and POST to websites and REST APIs and gives you the response as an object with the status code, headers and body. It is the standard way to fetch JSON data from web services in Python.
How do I read JSON from an API response in Python?
Call r.json() on the Response object. It parses the JSON body and returns Python dictionaries and lists, so you can access values with normal keys such as data[“rates”][“INR”].
Is requests part of the Python standard library?
No. Install it with pip install requests. The standard library has urllib, but requests is simpler to use and is what most tutorials and libraries rely on.
Working with spreadsheets too? Ready-made Excel, Google Sheets and Power BI templates are at NextGenTemplates.com.
Chapter 35 of 48 · Python with AI: all 48 chapters



