Working with Web APIs in Python

Fetch and send data over the internet with the requests library: GET and POST, JSON responses, query parameters, status codes, headers, API keys and error handling.

Lesson 46 of 107Advanced17 min read

Many of the most useful things a program can do involve data from somewhere else: today's weather, exchange rates, the latest posts from a service, a list of books matching a search, or a payment confirmation. Services share this data through web APIs: addresses that a program, rather than a person, visits to ask for information or send it.

An API usually answers in JSON, a text format that looks almost exactly like Python dictionaries and lists. That makes Python a natural fit: one function call fetches the data, and another turns it into structures you already know how to work with.

In this lesson you'll use the popular requests library to call APIs, read JSON responses, pass search options, check whether a request worked, send data with POST, keep API keys secret and handle the network problems that real programs always meet.

Your first request

Install requests in your virtual environment with pip install requests, then:

Python
import requests

response = requests.get("https://api.example.com/books/42", timeout=10)
print(response.status_code)    # 200
book = response.json()
print(book["title"])
  • requests.get(url) sends an HTTP GET request, which asks for data.
  • response.status_code is a number describing the result.
  • response.json() converts the JSON body into Python dictionaries and lists.
  • timeout=10 stops waiting after 10 seconds instead of hanging for ever.

Status codes and errors

CodeMeaning
200OK
201Created (after a POST)
400Bad request: something in your request is wrong
401 / 403Not signed in / not allowed
404Not found
429Too many requests: slow down
500The server had an error

Codes in the 200s mean success, 400s mean a problem with your request, and 500s a problem on the server. response.raise_for_status() raises an HTTPError for any 400 or 500 code, so you can handle failures with try and except instead of checking numbers by hand.

Query parameters and POST

Many APIs take options in the address, after a ?. Let requests build them safely with params:

Python
r = requests.get("https://api.example.com/books",
                 params={"q": "python", "limit": 5}, timeout=10)
# requests visits .../books?q=python&limit=5

To send data, use POST with json=, which converts a dictionary to JSON and sets the right header:

Python
r = requests.post("https://api.example.com/orders",
                  json={"item": "mug", "qty": 2}, timeout=10)

Other methods follow the same pattern: requests.put(), patch() and delete().

Headers and API keys

Most APIs need a key to identify you, usually sent in a header:

Python
import os
key = os.environ["WEATHER_API_KEY"]
r = requests.get(url, headers={"Authorization": f"Bearer {key}"}, timeout=10)

Never write keys directly in your code: if the file is shared or pushed to GitHub, anyone can use your account. Keep them in environment variables or a .env file that's listed in .gitignore. Read each API's documentation for its exact header name and rate limits.

Sessions, pagination and rate limits

When you make many requests to the same API, a requests.Session reuses the network connection and remembers headers, which is faster and keeps your code shorter:

Python
import requests

session = requests.Session()
session.headers["Authorization"] = f"Bearer {key}"

page = 1
books = []
while True:
    r = session.get("https://api.example.com/books",
                    params={"page": page}, timeout=10)
    r.raise_for_status()
    data = r.json()
    books.extend(data["results"])
    if not data["next"]:
        break
    page += 1

Most APIs return results in pages, so a loop like this keeps fetching until the response says there is no next page. Field names such as results and next vary between APIs; check the documentation.

If you receive status 429, you are sending requests too quickly. Many APIs include a Retry-After header saying how many seconds to wait. Respect it with time.sleep() before trying again, and don't retry indefinitely.

Python Web APIs Syntax

requests essentials

import requests
r = requests.get(url, params={"key": "value"}, headers={...}, timeout=10)
r.status_code
r.raise_for_status()
data = r.json()
r = requests.post(url, json={"field": "value"}, timeout=10)
except requests.RequestException as error:

Python Web APIs Summary

  • requests.get(url, params=..., timeout=...) fetches data; response.json() turns JSON into Python objects.
  • Status codes: 200s success, 400s your request, 500s the server; raise_for_status() turns errors into exceptions.
  • requests.post(url, json=data) sends data to an API.
  • Send API keys in headers and keep them in environment variables, never in code.
  • Always set a timeout and handle network errors with try and except.

Python Web APIs Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Searching an API

Python
import requests

r = requests.get(
    "https://openlibrary.org/search.json",
    params={"title": "the hobbit", "limit": 3},
    timeout=10,
)
r.raise_for_status()
data = r.json()

print("Results found:", data["numFound"])
for book in data["docs"]:
    year = book.get("first_publish_year", "unknown")
    print(f"- {book['title']} ({year})")
Output
Results found: 418
- The Hobbit (1937)
- The Hobbit, or There and Back Again (1937)
- The Annotated Hobbit (1988)
Line-by-line explanation
  1. Line 5requests adds ?title=the+hobbit&limit=3 to the address, encoding the space correctly.
  2. Line 8Stop with a clear error if the request failed.
  3. Lines 11 to 13The response is a dictionary with a "docs" list inside. .get() handles books without a year. Live data changes, so your numbers will differ.
Example 2

Handling failures gracefully

Python
import requests

def get_rate(currency):
    url = f"https://api.example.com/rates/{currency}"
    try:
        r = requests.get(url, timeout=5)
        r.raise_for_status()
        return r.json()["rate"]
    except requests.Timeout:
        print("The service took too long. Try again later.")
    except requests.HTTPError:
        print(f"No rate available for {currency} ({r.status_code}).")
    except requests.RequestException as error:
        print("Network problem:", error)
    return None

print(get_rate("XYZ"))
Output
No rate available for XYZ (404).
None
Line-by-line explanation
  1. Line 6A short timeout keeps the program responsive.
  2. Lines 9 to 12Specific errors get specific, helpful messages.
  3. Line 13RequestException catches every other requests error, such as no internet connection.
  4. Line 15The caller gets None, which it can check for.
Example 3

Sending data and saving the result

Python
import json
import os
import requests

token = os.environ.get("TASKS_TOKEN")
new_task = {"title": "Write tests", "done": False}

r = requests.post(
    "https://api.example.com/tasks",
    json=new_task,
    headers={"Authorization": f"Bearer {token}"},
    timeout=10,
)
print(r.status_code)
created = r.json()
print(created)

with open("task.json", "w") as file:
    json.dump(created, file, indent=2)
Output
201
{'id': 731, 'title': 'Write tests', 'done': False}
Line-by-line explanation
  1. Line 5The token comes from an environment variable, not the code.
  2. Line 10json= converts the dictionary to JSON for you.
  3. Line 14201 means "created".
  4. Lines 18 to 19Save the response to a file for later.

Common Python Web APIs Mistakes

No timeout

Don't
r = requests.get(url)              # can hang for ever
Do
r = requests.get(url, timeout=10)

Assuming the request worked

Don't
data = requests.get(url, timeout=10).json()   # 404 page may not be JSON
Do
r = requests.get(url, timeout=10)
r.raise_for_status()
data = r.json()

API key in the source code

Don't
API_KEY = "sk_9f8e7d6c5b4a"     # pushed to GitHub
Do
import os
API_KEY = os.environ["API_KEY"]

Python Web APIs in Real Projects

APIs everywhere

Python scripts call APIs to post messages to chat tools, pull sales figures into reports, send text messages, check the weather for farm sensors and collect data for research. Data scientists fetch datasets from public APIs, and companies connect their own systems through internal APIs. The pattern is always the same: request, check the status, read the JSON.

Python Web APIs FAQ

What is an API in simple terms?

An API (application programming interface) is a defined way for one program to ask another for data or actions. A web API does this over HTTP: your code sends a request to a URL and receives a structured answer, usually JSON, instead of a web page meant for people.

Is requests part of the Python standard library?

No. requests is a third-party package installed with pip install requests. The standard library has urllib.request, which works without installing anything but needs more code for the same tasks. Most Python developers use requests, or the newer httpx library, which also supports async code.

What is the difference between GET and POST?

GET asks for data and sends any options in the URL's query string; it should not change anything on the server. POST sends data in the request body, typically to create something, such as a new order. The HTTP and REST lesson explains the other methods and when to use each.

Why does response.json() raise an error?

The response body isn't valid JSON, usually because the request failed and the server returned an HTML error page, or because the endpoint returns plain text. Call response.raise_for_status() first, and print response.text[:200] to see what actually came back.

How do I keep my API key out of my code?

Store it in an environment variable and read it with os.environ["NAME"], or keep it in a .env file loaded with a package such as python-dotenv. Add .env to .gitignore so it's never committed. The API authentication lesson covers tokens and OAuth in more depth.

Python Web APIs Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the line so the JSON response is converted into Python objects.

data = response.()
Multiple choiceeasy

Which status code means "not found"?

Multiple choicemedium

What does params={"q": "cats"} do in requests.get?

Predict the outputmedium

r.status_code is 500 and you call r.raise_for_status(). What happens?

Multiple choicehard

Where should an API key live?

Python Web APIs Quiz

5 questions to check what you learned.

  1. What format do most web APIs use for their responses?

    • CSV
    • PDF
    • HTML only
    • JSON
  2. Which HTTP method is normally used to create something new?

    • POST
    • GET
    • HEAD
    • OPTIONS
  3. What does status code 429 tell you?

    • Success
    • The server crashed
    • You're sending too many requests
    • The page moved
  4. Why always pass timeout=?

    • It makes requests faster
    • Without it, a request to a slow server can wait for ever
    • Some APIs require it
    • It retries failed requests
  5. Which exception catches all errors raised by requests, including connection problems?

    • ValueError
    • requests.RequestException
    • HTTPError only
    • KeyError