
Python REST API Cheatsheet
Whether you're building a web service or integrating with one, understanding how to work with REST APIs in Python is a fundamental skill. This cheatsheet covers the essentials—from making simple requests to handling authentication and responses. Let’s dive in.
Making HTTP Requests
Python offers several ways to interact with REST APIs, but the most common method is using the requests
library. It's simple, powerful, and widely adopted. If you haven't installed it yet, you can do so with pip:
pip install requests
Once installed, you can start making requests. Here’s a quick example of a GET request:
import requests
response = requests.get('https://jsonplaceholder.typicode.com/posts')
print(response.status_code)
print(response.json())
This code fetches a list of posts from a test API and prints both the HTTP status code and the JSON response. The status_code
tells you if the request was successful (e.g., 200 for OK), while response.json()
parses the response body as JSON.
You can also make other types of requests, like POST, PUT, and DELETE. Here’s how to send data with a POST request:
data = {'title': 'New Post', 'body': 'This is the content', 'userId': 1}
response = requests.post('https://jsonplaceholder.typicode.com/posts', json=data)
print(response.json())
Notice the use of the json
parameter—it automatically sets the Content-Type
header to application/json
and serializes the dictionary for you.
HTTP Method | Purpose | Example Usage |
---|---|---|
GET | Retrieve data | requests.get(url) |
POST | Create data | requests.post(url, json=data) |
PUT | Update data | requests.put(url, json=data) |
DELETE | Remove data | requests.delete(url) |
When working with APIs, you’ll often need to include additional information in your requests, such as query parameters, headers, or authentication tokens. Let’s explore those next.
Handling Query Parameters and Headers
Many APIs require you to send query parameters to filter or paginate results. With requests
, you can pass these as a dictionary using the params
argument:
params = {'userId': 1}
response = requests.get('https://jsonplaceholder.typicode.com/posts', params=params)
This appends ?userId=1
to the URL. It’s a clean way to build dynamic URLs without manual string concatenation.
Headers are equally important. They allow you to send metadata about your request, such as content type, authentication tokens, or custom flags. Here’s how to include headers:
headers = {'Authorization': 'Bearer your_token_here'}
response = requests.get('https://api.example.com/data', headers=headers)
Common headers you might use include:
- Authorization
for tokens
- Content-Type
to specify data format
- User-Agent
to identify your client
Always check the API documentation to see which headers are required or recommended.
If you need to send form data instead of JSON, use the data
parameter instead of json
:
form_data = {'key': 'value'}
response = requests.post('https://httpbin.org/post', data=form_data)
This is useful for APIs that expect traditional form submissions.
Authentication Methods
Securing API endpoints is common, and you’ll encounter several authentication methods. The simplest is HTTP Basic Auth, which requires a username and password:
from requests.auth import HTTPBasicAuth
response = requests.get('https://api.example.com/protected', auth=HTTPBasicAuth('user', 'pass'))
You can also pass a tuple directly:
response = requests.get('https://api.example.com/protected', auth=('user', 'pass'))
For token-based authentication, you typically include the token in the Authorization
header. Here’s an example using a Bearer token:
headers = {'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}
response = requests.get('https://api.example.com/data', headers=headers)
Some APIs use API keys passed as query parameters or headers. For example:
params = {'api_key': 'YOUR_API_KEY'}
# or
headers = {'X-API-Key': 'YOUR_API_KEY'}
OAuth is more complex and often requires a dedicated library like requests-oauthlib
, but for simple cases, you can manually handle the token.
Auth Type | How to Implement | Example |
---|---|---|
Basic Auth | Use auth=('user','pass') |
requests.get(url, auth=('user','pass')) |
Token Auth | Add Authorization header |
headers = {'Authorization': 'Bearer token'} |
API Key | Add to headers or params | headers = {'X-API-Key': 'key'} |
Always keep your credentials secure—avoid hardcoding them in your scripts. Use environment variables or configuration files instead.
Handling Responses
After making a request, you need to handle the response appropriately. The response
object contains all the information returned by the server.
First, check the status code to ensure the request was successful:
if response.status_code == 200:
data = response.json()
else:
print(f"Error: {response.status_code}")
For convenience, you can use response.raise_for_status()
, which raises an exception for non-200 status codes:
response = requests.get('https://api.example.com/data')
response.raise_for_status() # Raises an error for bad status codes
data = response.json()
The response body can be accessed in different formats:
- response.text
for plain text
- response.json()
for JSON data (raises error if not JSON)
- response.content
for binary data
If you’re dealing with large responses, you might want to stream the content:
response = requests.get('https://example.com/large-file', stream=True)
for chunk in response.iter_content(chunk_size=8192):
process(chunk)
This avoids loading the entire response into memory at once.
Headers from the response can also be useful:
print(response.headers['Content-Type'])
They might include information like rate limits, content type, or caching directives.
Error Handling and Timeouts
Network requests can fail for many reasons—timeouts, connection errors, or invalid responses. It’s important to handle these gracefully.
Use a try-except
block to catch exceptions:
try:
response = requests.get('https://api.example.com/data', timeout=5)
response.raise_for_status()
except requests.exceptions.Timeout:
print("The request timed out.")
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
Setting a timeout
is crucial to prevent your application from hanging indefinitely. The value is in seconds.
You might also encounter HTTP errors like 404 Not Found or 500 Internal Server Error. raise_for_status()
helps here, but you can also handle specific status codes:
if response.status_code == 404:
print("Resource not found.")
elif response.status_code == 500:
print("Server error.")
For retrying failed requests, you can use the requests
library with a loop or a more advanced method like the tenacity
library.
Always implement robust error handling to make your code resilient and user-friendly.
Working with JSON Data
Most modern APIs use JSON for data exchange. Python’s json
module is built-in, but requests
makes it even easier with response.json()
.
When sending data, you can pass a dictionary to the json
parameter, and it will be serialized automatically:
data = {"name": "John", "age": 30}
response = requests.post('https://api.example.com/users', json=data)
If you need to work with JSON strings directly, you can use json.dumps()
and json.loads()
:
import json
json_string = '{"name": "John", "age": 30}'
response = requests.post('https://api.example.com/users', data=json_string, headers={'Content-Type': 'application/json'})
But using the json
parameter is simpler and less error-prone.
When receiving JSON responses, response.json()
returns a Python dictionary or list, which you can then manipulate:
data = response.json()
print(data['name'])
For nested data, you can traverse the structure as you would with any dictionary.
If the JSON response is large or you want to process it iteratively, you can use response.iter_lines()
or response.json()
might still be efficient enough for most cases.
Session Objects for Efficiency
If you’re making multiple requests to the same API, using a Session
object can improve performance by reusing the underlying TCP connection. It also persists cookies and headers across requests.
Here’s how to use it:
with requests.Session() as session:
session.headers.update({'Authorization': 'Bearer token'})
response1 = session.get('https://api.example.com/data1')
response2 = session.get('https://api.example.com/data2')
Any headers you set on the session will be sent with every request, which is handy for authentication.
Sessions are also useful for maintaining state, such as cookies for logged-in users:
session = requests.Session()
session.post('https://api.example.com/login', json={'username': 'user', 'password': 'pass'})
response = session.get('https://api.example.com/profile') # Now authenticated
Remember to close the session when you’re done, or use a context manager as shown above.
Scenario | Use Session? | Why? |
---|---|---|
Single request | No | Simplicity |
Multiple requests to same host | Yes | Performance, persisted headers/cookies |
Authentication required | Yes | Persist auth tokens or cookies |
Sessions can significantly speed up your code when making many requests, so use them where appropriate.
Advanced Topics: Pagination and Rate Limiting
Many APIs paginate their results to avoid overwhelming the client or server. Pagination means the data is split across multiple pages, and you need to make several requests to fetch everything.
Common pagination methods include:
- Page numbers (e.g., ?page=2
)
- Cursors or tokens (e.g., ?next_token=abc
)
- Link headers (with next
and prev
URLs)
Here’s an example using page numbers:
page = 1
all_data = []
while True:
response = requests.get(f'https://api.example.com/items?page={page}')
data = response.json()
if not data:
break
all_data.extend(data)
page += 1
For APIs that use Link headers, you can check response.headers['Link']
for the next URL.
Rate limiting is another common practice. APIs often restrict how many requests you can make in a given time period. The rate limit info is usually in the response headers:
remaining = response.headers.get('X-RateLimit-Remaining')
if remaining and int(remaining) == 0:
print("Rate limit exceeded. Waiting...")
time.sleep(60)
Always respect rate limits to avoid being blocked. Implement retry logic with backoff if necessary.
Testing and Mocking APIs
When writing tests for code that uses APIs, you don’t want to hit the real server every time. Instead, you can mock the responses using libraries like responses
or unittest.mock
.
Install responses
with:
pip install responses
Here’s how to mock a GET request:
import responses
import requests
@responses.activate
def test_get_data():
responses.add(
responses.GET,
'https://api.example.com/data',
json={'key': 'value'},
status=200
)
response = requests.get('https://api.example.com/data')
assert response.json() == {'key': 'value'}
This allows you to simulate API responses without network calls.
You can also mock errors:
responses.add(
responses.GET,
'https://api.example.com/data',
status=404
)
Testing with mocks makes your tests faster and more reliable, and it avoids dependencies on external services.
Useful Libraries and Tools
While requests
is the go-to library for many, there are other tools you might find helpful:
http.client
: Standard library module for low-level HTTP requests.urllib.request
: Another standard library option, but more verbose.aiohttp
: For asynchronous requests, useful in async applications.httpx
: A modern alternative with async support and HTTP/2.
For command-line testing, tools like curl
or HTTPie are great. For exploring APIs interactively, Postman or Insomnia are popular GUI options.
Here’s a quick example using httpx
for async requests:
import httpx
import asyncio
async def fetch_data():
async with httpx.AsyncClient() as client:
response = await client.get('https://api.example.com/data')
return response.json()
data = asyncio.run(fetch_data())
Choose the tool that best fits your needs and project structure.
Summary of Best Practices
To wrap up, here are some key best practices to keep in mind when working with REST APIs in Python:
- Always use timeouts to prevent hanging requests.
- Handle errors and exceptions gracefully.
- Use sessions for multiple requests to the same host.
- Respect rate limits and implement retry logic if needed.
- Secure your authentication tokens and avoid hardcoding them.
- Test your code with mocked API responses.
By following these guidelines, you’ll write more robust, efficient, and maintainable code. Happy coding!