Authentication
Most APIs require credentials. Learn the three common authentication patterns and why storing secrets in environment variables — not source code — is non-negotiable.
- Distinguish API key in header, API key as query param, and OAuth 2.0 bearer token flows
- Explain why hardcoding credentials in source code is dangerous
- Use environment variables and a .env file as the standard safe alternative
Almost every API beyond the purely public ones requires you to prove who you are before it will respond. That proof takes several forms. Knowing which pattern an API uses — and how to pass credentials safely — is a practical necessity before you write a single line of integration code.
Pattern 1: API key in a header
The most common pattern. You include your key in a request header, typically
Authorization:
Authorization: Bearer sk-abc123...Some APIs use a custom header name instead: X-API-Key: sk-abc123.... The API
documentation always specifies which header to use and what prefix (if any) to
add before the key value.
In Python with requests:
headers = {"Authorization": "Bearer sk-abc123..."}
response = requests.get("https://api.example.com/data", headers=headers)Pattern 2: API key as query parameter
Older or simpler APIs embed the key directly in the URL:
https://api.example.com/data?api_key=sk-abc123...This works, but it is less secure: query parameters appear in server logs, browser history, and proxy logs. Prefer the header approach when you have a choice.
Pattern 3: OAuth 2.0 bearer tokens
OAuth is a token-exchange protocol. You do not pass your API key directly; instead
you exchange credentials for a short-lived access token, then use that token as
a bearer credential in each request. The exact exchange flow varies (client credentials,
authorization code, device flow), but the end result is the same: you get a token,
you include it in the Authorization header, you refresh it when it expires.
OAuth is the pattern used by Google, GitHub, Slack, and most large platforms. For scripting purposes, many of these platforms also offer long-lived personal access tokens that behave like API keys — simpler to use for automation, but with the same security obligations.
Why hardcoding credentials is dangerous
The temptation is to write:
API_KEY = "sk-abc123..." # do not do thisThe moment that file enters a git repository — even a private one — the key is in the commit history. History cannot be easily scrubbed. If the repository ever becomes public, or if someone clones it who should not have access, the key is exposed. API key leaks are one of the most common causes of unauthorised cloud charges and data breaches.
Treat an API key exactly like a password. You would not hardcode your database password in a source file — apply the same rule to every credential.
The solution: environment variables and .env files
Environment variables live outside the source tree:
import os
api_key = os.environ.get("MY_API_KEY")
if not api_key:
raise RuntimeError("MY_API_KEY environment variable is not set")For local development, .env files are the standard convenience: a plain text file
listing KEY=value pairs that you add to .gitignore immediately. The
python-dotenv library loads the file into os.environ at startup:
from dotenv import load_dotenv
import os
load_dotenv() # reads .env in the current directory
api_key = os.environ.get("MY_API_KEY")Your .gitignore should always contain .env. The .env.example file (with
placeholder values, no real secrets) is what you commit so teammates know which
variables are expected.
Where to go next
Next: auth in practice — putting the header pattern and environment variable loading together in a runnable example, including what a 401 response looks like when credentials are missing.
Making requests
Use Python's requests library to send HTTP GET requests, inspect the response, and raise an error automatically when the server signals failure.
Auth in practice
Pass API credentials safely by reading them from environment variables and attaching them as request headers — never from hardcoded strings.