Authentication: API Keys and OAuth
Bearer tokens, API key headers, OAuth2 flows for agent API access.
Authentication: API Keys and OAuth is a free AI Agents lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Agents learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Authentication Matters for Agents
When your agent calls an external API, the server needs to know who is making the request. Authentication proves identity; authorization determines what you can do. Without proper auth, every request returns 401 Unauthorized and your agent can't do anything.
Two patterns dominate agent development: API keys and OAuth 2.0.
import requests
# Without auth — will get 401
response = requests.get('https://api.openai.com/v1/models')
print(response.status_code) # 401 Unauthorized
# With API key in header — works
headers = {'Authorization': 'Bearer sk-proj-abc123'}
response = requests.get(
'https://api.openai.com/v1/models',
headers=headers
)
print(response.status_code) # 200API Key in Authorization Header
The most common pattern is sending your API key in the Authorization header as a Bearer token. The word "Bearer" signals that whoever has this token is authorized — the server trusts the bearer of the key.
This is used by OpenAI, Anthropic, GitHub, and most modern APIs.
import requests
import os
api_key = os.environ['OPENAI_API_KEY']
response = requests.post(
'https://api.openai.com/v1/chat/completions',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
},
json={
'model': 'gpt-4o-mini',
'messages': [{'role': 'user', 'content': 'Hello!'}]
}
)
print(response.json()['choices'][0]['message']['content'])API Key in Custom Header (X-API-Key)
Some APIs — especially older or internal ones — use a custom header like X-API-Key instead of Authorization: Bearer. The pattern is the same, just with a different header name. Always check the API documentation for the exact header name expected.
import requests
import os
api_key = os.environ['SERVICE_API_KEY']
response = requests.get(
'https://api.someservice.com/v1/data',
headers={
'X-API-Key': api_key,
'Accept': 'application/json'
}
)
if response.status_code == 200:
data = response.json()
print('Got data:', data)
elif response.status_code == 401:
print('Invalid API key — check X-API-Key header')Storing Credentials in Environment Variables
Never hard-code API keys in your source code. If you commit a key to a public repo, bots will find and abuse it within seconds. The correct pattern is to store credentials in environment variables and read them at runtime with os.environ.
Use os.environ.get() with a clear error message if the key is missing.
import os
os.environ['OPENAI_API_KEY'] = 'sk-proj-abc123xyz789' # simulate a set env var
api_key = os.environ.get('OPENAI_API_KEY')
if not api_key:
raise EnvironmentError(
'OPENAI_API_KEY environment variable not set. '
'Run: export OPENAI_API_KEY=your-key-here'
)
print('API key loaded from environment (never hard-code it in source)')Using python-dotenv for Local Development
During development, keep your keys in a .env file in your project root. Use the python-dotenv library to load them automatically. Add .env to your .gitignore so it never gets committed.
# .env file (never commit this!)
# OPENAI_API_KEY=sk-proj-abc123
# ANTHROPIC_API_KEY=sk-ant-xyz456
# GITHUB_TOKEN=ghp_abc789
# In your Python code:
from dotenv import load_dotenv
import os
load_dotenv() # loads .env into os.environ
openai_key = os.environ['OPENAI_API_KEY']
anthropic_key = os.environ['ANTHROPIC_API_KEY']
github_token = os.environ['GITHUB_TOKEN']
print('Keys loaded successfully')What Is OAuth 2.0?
OAuth 2.0 is a standard for delegated authorization. Instead of giving your agent a user's password, OAuth lets the user authorize your agent to act on their behalf, with a limited scope and time window. It's used by Google, GitHub, Slack, and Salesforce.
The key concept: your agent gets an access token after an authorization flow, then uses that token for API calls.
# OAuth flow overview:
#
# 1. Agent redirects user to:
# https://auth.provider.com/oauth/authorize
# ?client_id=YOUR_CLIENT_ID
# &redirect_uri=http://localhost:8080/callback
# &scope=read:repo%20write:issues
# &response_type=code
#
# 2. User logs in and grants permission
# 3. Provider redirects to your callback with ?code=AUTH_CODE
# 4. Agent exchanges code for access_token
# 5. Agent uses access_token for API calls
print('OAuth flow: authorize -> code -> token -> API calls')OAuth2 Client Credentials Flow
The client credentials flow is the simplest OAuth flow for agents — no user interaction needed. Your agent authenticates with its own client ID and secret to get a token. This is used for machine-to-machine (M2M) communication.
You POST your credentials to the token endpoint and receive a short-lived access token.
import requests
import os
client_id = os.environ['OAUTH_CLIENT_ID']
client_secret = os.environ['OAUTH_CLIENT_SECRET']
token_url = 'https://auth.example.com/oauth/token'
# Request an access token
response = requests.post(token_url, data={
'grant_type': 'client_credentials',
'client_id': client_id,
'client_secret': client_secret,
'scope': 'read:data write:tasks'
})
token_data = response.json()
access_token = token_data['access_token']
expires_in = token_data['expires_in'] # seconds
print(f'Token valid for {expires_in}s')Using OAuth Tokens in API Calls
Once you have an OAuth access token, use it exactly like an API key — in the Authorization: Bearer header. The difference is that OAuth tokens expire, so your agent must handle token refresh before making calls.
import requests
import os
import time
class OAuthClient:
def __init__(self, client_id, client_secret, token_url):
self.client_id = client_id
self.client_secret = client_secret
self.token_url = token_url
self.access_token = None
self.token_expiry = 0
def get_token(self):
if time.time() < self.token_expiry - 60: # 60s buffer
return self.access_token
r = requests.post(self.token_url, data={
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret
})
data = r.json()
self.access_token = data['access_token']
self.token_expiry = time.time() + data['expires_in']
return self.access_token
def get(self, url):
token = self.get_token()
return requests.get(url, headers={'Authorization': f'Bearer {token}'})OAuth 2.0 with google-auth Library
For Google APIs, the google-auth library handles all OAuth complexity for you. It manages token refresh automatically, reads credentials from a JSON file, and attaches tokens to requests via an AuthorizedSession.
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
# Load service account credentials from JSON file
credentials = service_account.Credentials.from_service_account_file(
'service-account.json',
scopes=[
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/calendar.events'
]
)
# AuthorizedSession auto-refreshes tokens
session = AuthorizedSession(credentials)
response = session.get(
'https://www.googleapis.com/gmail/v1/users/me/messages'
)
print(response.json())API Key Security Best Practices
Protecting API keys is critical for agent security. Follow these rules:
- Store keys in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault)
- Never log keys — mask them in output
- Rotate keys regularly and revoke compromised ones immediately
- Use the principle of least privilege — request only the scopes your agent needs
- Set IP allowlists on API keys when the provider supports it
import os
os.environ['OPENAI_API_KEY'] = 'sk-proj-abc123xyz789'
def get_key(env_var):
key = os.environ.get(env_var)
if not key:
raise EnvironmentError(f'Missing required env var: {env_var}')
return key
def mask_key(key):
if len(key) < 8:
return '***'
return key[:4] + '...' + key[-4:]
api_key = get_key('OPENAI_API_KEY')
print(f'Using key: {mask_key(api_key)}')Handling 401 Unauthorized in Your Agent
When an agent gets a 401 Unauthorized response, it should never retry blindly — that wastes rate limit quota. Instead, check whether the token has expired (try refreshing) or whether the key itself is invalid (alert immediately so a human can fix it).
import requests
import os
def call_api_with_auth_check(url, api_key):
response = requests.get(
url,
headers={'Authorization': f'Bearer {api_key}'}
)
if response.status_code == 401:
error = response.json().get('error', {})
code = error.get('code', 'unknown')
if code == 'token_expired':
print('Token expired — refresh needed')
# trigger token refresh flow
else:
raise PermissionError(
f'API key rejected: {error.get("message", "401 Unauthorized")}'
)
response.raise_for_status()
return response.json()Quick Check: API Key Storage
Test your understanding of credential management.
Authentication Recap
You've learned the two main authentication patterns for agents:
- API keys — passed in
Authorization: Bearer TOKENorX-API-Keyheader; simple and stateless - OAuth 2.0 — client credentials flow for M2M; tokens expire and must be refreshed
- Always store keys in environment variables, never in source code
- Use python-dotenv locally; environment variables or secrets managers in production
- Handle 401 responses by checking if the token expired vs the key being invalid
Solid auth handling is the foundation of every reliable agent.
Frequently asked questions
Is the “Authentication: API Keys and OAuth” lesson free?
Yes — the full text of “Authentication: API Keys and OAuth” is free to read here on the web, and the AI Agents course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Agents course, upgrade to CoddyKit PRO.
What will I learn in “Authentication: API Keys and OAuth”?
Bearer tokens, API key headers, OAuth2 flows for agent API access. You practise AI Agents with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Agents?
No prior experience is required. AI Agents on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Authentication: API Keys and OAuth” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Agents lesson?
Yes. Every AI Agents lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- REST API Fundamentals for Agent Developers
- Authentication: API Keys and OAuth
- Handling API Responses and Errors
- Rate Limiting and Retry Logic