Auth in practice
Pass API credentials safely by reading them from environment variables and attaching them as request headers — never from hardcoded strings.
- Add an Authorization header to a requests call
- Read an API key from an environment variable with os.environ.get()
- Use python-dotenv to load a .env file at startup
- Recognise a 401 response and diagnose missing credentials
You know the theory: credentials go in a header, never in source code. This lesson makes it concrete — a working pattern you can copy into any integration script.
The request header pattern
Passing a bearer token to requests means building a headers dict and handing it
to the request call:
import os
import requests
api_key = os.environ.get("MY_API_KEY")
if not api_key:
raise RuntimeError("MY_API_KEY is not set")
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get("https://api.example.com/data", headers=headers)
response.raise_for_status()
data = response.json()Three things happen here. First, the key is read from the environment rather than the
source file. Second, a missing key raises immediately — before any network call — so
the error message is clear. Third, raise_for_status() catches a 401 from the server
if the key is present but wrong.
Loading from a .env file
In production, environment variables are set by the platform (a CI runner, a server's
environment configuration, a secrets manager). In local development, the standard
convenience is a .env file:
# .env — add this file to .gitignore immediately
MY_API_KEY=sk-abc123...Load it with python-dotenv before anything else in your script:
from dotenv import load_dotenv
import os
load_dotenv() # reads .env from the current working directory
api_key = os.environ.get("MY_API_KEY")After load_dotenv(), the variables in .env are available via os.environ for the
rest of the process. Nothing else in the call chain needs to change.
Commit a .env.example file with placeholder values so teammates know which
variables are required. Never commit the real .env. Add .env to .gitignore
on the first day of the project — not after you accidentally push it.
What a 401 looks like
When credentials are missing or invalid, the server returns 401 Unauthorized. With
raise_for_status(), this becomes an exception:
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: ...A 401 means authentication failed — check that the variable is set and the value is correct. A 403 means authenticated but not permitted — check the account's permissions or which scopes the token has.
Try it
The runner below simulates the header-construction and missing-key guard — without a real API call, since the browser sandbox has no credentials to read:
In a real script, replace the simulation with a requests.get() call and pass the
headers dict to it. The guard logic and the header construction stay exactly as shown.
Where to go next
Next: pagination and rate limiting — most APIs cap how many results you get per request and how many requests you can make per minute. Knowing how to page through results and how to back off when rate-limited keeps your script from hitting walls.
Authentication
Most APIs require credentials. Learn the three common authentication patterns and why storing secrets in environment variables — not source code — is non-negotiable.
Pagination and rate limiting
Most APIs cap how many results they return per request and how many requests you can make per minute — learn to navigate both limits reliably.