Code of the Day
IntermediateExternal Integrations

Auth in practice

Pass API credentials safely by reading them from environment variables and attaching them as request headers — never from hardcoded strings.

WorkflowIntermediate8 min read
Recommended first
By the end of this lesson you will be able to:
  • 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:

Python — editable, runs in your browser

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.

Finished reading? Mark it complete to track your progress.

On this page