Making HTTP Requests and Working with APIs

· 5 min read · Updated March 7, 2026 · beginner
http requests api web beginner

APIs power most of the internet. Every time you check the weather, log into a service, or fetch data from a database, you’re making an HTTP request. Python’s requests library makes this incredibly simple.

This tutorial teaches you how to make HTTP requests, handle responses, and work with REST APIs. By the end, you’ll be able to fetch data from public APIs, send data to servers, and handle errors gracefully.

What is HTTP?

HTTP stands for Hypertext Transfer Protocol. It’s the protocol browsers and servers use to communicate. When you visit a website, your browser sends an HTTP request to a server, which responds with the page content.

HTTP requests have a method that tells the server what you want to do:

  • GET retrieves data. Your browser uses this to load pages.
  • POST sends data to create something new. Forms use this.
  • PUT updates existing data completely.
  • DELETE removes data.

Each request goes to a URL, and the server responds with a status code and optional data.

Installing the Requests Library

The requests library is not part of Python’s standard library. You need to install it first:

pip install requests

Once installed, import it like any other package:

import requests

That’s it. You’re ready to make HTTP requests.

Making Your First GET Request

The most common operation is fetching data with GET. You pass a URL to requests.get() and get a response object back:

import requests

response = requests.get('https://api.github.com/users/octocat')
print(response.status_code)
# 200

A status code of 200 means the request succeeded. The response object contains everything the server sent back.

You can also use the response object directly in a conditional. Requests treats status codes below 400 as truthy:

response = requests.get('https://api.github.com/users/octocat')
if response:
    print('Success!')
else:
    print(f'Failed with code {response.status_code}')

This is useful for quick success checks without manually comparing status codes.

Reading Response Data

The response object gives you access to the data the server sent. For most APIs, you’ll work with JSON:

response = requests.get('https://api.github.com/users/octocat')
data = response.json()

print(data['login'])
# octocat
print(data['public_repos'])
# 72

The .json() method parses the response body as JSON and returns a Python dictionary. If the response isn’t valid JSON, it raises an exception.

Sometimes you need the raw text instead:

response = requests.get('https://example.com')
print(response.text)
# <!doctype html>...

Or raw bytes for binary content like images:

response = requests.get('https://httpbin.org/image')
print(response.content[:20])
# b'\\x89PNG\\r\\n\\x1a\\n...'

Sending Data with POST

GET requests retrieve data. POST requests send data to create something. The json parameter sends JSON-encoded data automatically:

import requests

payload = {'username': 'alice', 'password': 'secret123'}
response = requests.post('https://httpbin.org/post', json=payload)

print(response.status_code)
# 200

data = response.json()
print(data['json'])
# {'username': 'alice', 'password': 'secret123'}

The server receives your data in the request body. Using json=payload sets the Content-Type header to application/json automatically.

For form-encoded data (what HTML forms send), use the data parameter instead:

response = requests.post('https://httpbin.org/post', data={'username': 'alice'})

Handling Errors

Status codes tell you what happened. Codes in the 200s mean success. Codes in the 400s mean client errors (you did something wrong). Codes in the 500s mean server errors (the server failed).

Always check for errors:

response = requests.get('https://api.github.com/users/nonexistent-user')

if response.status_code == 404:
    print('User not found')
elif response.status_code >= 500:
    print('Server error, try again later')
else:
    print('Success')

Requests provides .raise_for_status() to raise an exception for error status codes:

response = requests.get('https://api.github.com/users/nonexistent-user')
response.raise_for_status()
# Raises HTTPError for 4xx and 5xx responses

This is useful when you want your code to stop immediately on errors rather than checking status codes manually.

Customizing Requests with Headers

APIs often require specific headers. Pass a dictionary to the headers parameter:

headers = {'User-Agent': 'MyApp/1.0'}
response = requests.get('https://api.github.com/users/octocat', headers=headers)

print(response.headers['Content-Type'])
# application/json; charset=utf-8

Common headers include:

  • User-Agent — identifies your application
  • Authorization — passes authentication credentials
  • Accept — tells the server what response format you want

For authorization, many APIs use a Bearer token:

headers = {'Authorization': 'Bearer YOUR_TOKEN_HERE'}
response = requests.get('https://api.github.com/user', headers=headers)

Never hardcode secrets in your code. Use environment variables instead:

import os
headers = {'Authorization': f'Bearer {os.environ.get("API_TOKEN")}'}

Using Query Parameters

To pass parameters in the URL (like ?search=python), use the params parameter:

params = {'q': 'requests+language:python', 'sort': 'stars'}
response = requests.get('https://api.github.com/search/repositories', params=params)

data = response.json()
print(data['total_count'])
# Number of matching repositories

Requests handles URL encoding automatically. You can pass a dictionary, list of tuples, or bytes.

Common API Patterns

Most REST APIs follow similar patterns. Once you understand these, you can work with almost any API.

Pagination — APIs limit how much data they return. You need to request multiple pages:

all_results = []
page = 1

while True:
    response = requests.get(
        'https://api.github.com/users/octocat/repos',
        params={'page': page, 'per_page': 100}
    )
    repos = response.json()
    
    if not repos:
        break
    
    all_results.extend(repos)
    page += 1

Rate limiting — APIs restrict how many requests you can make. Check the headers:

response = requests.get('https://api.github.com/users/octocat')
remaining = response.headers.get('X-RateLimit-Remaining')
print(f'Remaining requests: {remaining}')

Timeouts — Always set a timeout to avoid hanging indefinitely:

response = requests.get('https://slow-api.example.com', timeout=5)

Without a timeout, your code waits forever if the server never responds.

Next Steps

You now have the foundation for working with APIs in Python. The requests library handles the complexity of HTTP so you can focus on the data.

To practice, try fetching data from a public API like:

The next tutorial in this series covers working with JSON data in more detail. You’ll learn parsing, serialization, and handling nested data structures.