How to Authenticate a REST API with OAuth 2.0 in Python
The most robust data APIs are not open to the public: they require authentication. In server-to-server scenarios, the most common standard is OAuth 2.0 with the client credentials flow, and mastering it is essential for building reliable data pipelines. This guide shows, step by step, how to obtain an access token and call a protected endpoint in Python.
Prerequisites
- Python 3.8 or later installed.
- The
requestslibrary (install it withpip install requests). - Your API credentials:
client_idandclient_secret, provided by the vendor's portal. - The token endpoint URL (where tokens are requested) and the URL of the protected resource you want to consume.
Step 1: Understand the client credentials flow
OAuth 2.0 defines several flows. The client credentials flow is the right one when it is your application — not a user — that accesses the data, as happens in an ETL script or a scheduled service. The idea is simple: the application presents its client_id and client_secret to a token endpoint, receives a temporary access token in return, and uses that token to authenticate every request to the API. There are no login screens and no human intervention, which makes it ideal for automation.
Step 2: Get the access token
The first request is a POST to the token endpoint, specifying grant_type=client_credentials. The response is a JSON object with the token and its lifetime in seconds (expires_in). Note that the credentials go in the request body, over HTTPS, and never in the URL.
import requests
TOKEN_URL = "https://auth.example.com/oauth/token"
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
def get_token():
response = requests.post(TOKEN_URL, data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "data.read",
}, timeout=10)
response.raise_for_status()
return response.json()["access_token"]
token = get_token()
print("Access token obtained")
The raise_for_status() method ensures that a credentials error (HTTP 401) stops the script immediately, instead of continuing with an invalid token.
Step 3: Call the protected endpoint
With the token in hand, every request to the API carries the Authorization: Bearer <token> header. That is how the server knows you are authorized.
API_URL = "https://api.example.com/v1/customers"
def get_data(token):
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(API_URL, headers=headers, timeout=10)
response.raise_for_status()
return response.json()
records = get_data(token)
print(f"Received {len(records)} records")
Step 4: Reuse the token and handle expiry
Requesting a new token on every call is slow and often subject to limits. The best approach is to store the token and its expiry time, refreshing it only when little time is left. A simple class solves the problem:
import time
class APIClient:
def __init__(self):
self._token = None
self._expires_at = 0
def valid_token(self):
# Refresh 60 seconds before expiry, to be safe
if not self._token or time.time() > self._expires_at - 60:
response = requests.post(TOKEN_URL, data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
}, timeout=10)
response.raise_for_status()
payload = response.json()
self._token = payload["access_token"]
self._expires_at = time.time() + payload["expires_in"]
return self._token
client = APIClient()
headers = {"Authorization": f"Bearer {client.valid_token()}"}
This way, the same token is reused while it is valid and refreshed automatically when needed.
Verify the result
To confirm everything works, run the script and check two signals: the token request returns HTTP 200, and the API call returns data instead of a 401 error. If you get a 401, review the client_id, the client_secret and the scope. A 403 error means the token is valid but lacks permission for that resource — in that case, the problem lies in the scopes granted to the application.
Conclusion
With these steps you have a solid foundation for authenticating any data API that uses OAuth 2.0 client credentials, from obtaining the token to refreshing it automatically. The natural next step is to combine this with pagination and writing to a database, turning these calls into a complete pipeline. Which API will you integrate first?