Free lesson

Secure API key management

You can store LLM API keys safely using .env files + python-dotenv + .gitignore patterns, ensuring secrets never enter version control.

~21 min read · Free to read — no subscription required.

Secure API key management for agent projects

Introduction

When you hardcode an API key into source and push it to GitHub, automated scanners typically find it in under 30 seconds — and a single leaked LLM key has produced five-figure bills before the developer noticed. Agent code is especially exposed because it usually carries multiple provider keys (OpenAI, Anthropic, Google) at once. By the end of this lesson you'll be able to keep those keys out of source control, load them safely at runtime with python-dotenv, and fail fast at startup when one is missing.

Key Terminology

  • .env file — a plain-text file of KEY=value pairs holding real secrets; it lives on the developer's machine and is excluded from Git, which is what keeps the keys private.
  • .env.example — a committed template of the same keys with placeholder values; it documents which variables the agent needs without exposing any actual credentials.
  • python-dotenv — the library that reads a .env file at startup and injects its values into os.environ so application code can read them with os.getenv().
  • Startup validation — an explicit check at program launch that every required variable is present; it converts a silent misconfiguration into an immediate, debuggable error.
  • Pydantic BaseSettings — a typed configuration container that loads variables from .env, coerces them to the declared types, and rejects missing or malformed values at instantiation.

Concepts

Why hardcoded keys fail

Public Git history is permanent — even a deleted commit remains reachable via reflog and forks. Scanner bots watch GitHub's event firehose and try every leaked key within seconds. For agents that hold multiple provider keys, one leak compromises every provider at once.

Loading diagram...

The .env / .env.example split

Two files, one purpose: .env.example is committed and lists every variable the agent needs with placeholder values; .env is gitignored and holds the real secrets. New contributors copy the template, fill in their own keys, and git check-ignore .env confirms the file is excluded before any commit.

Load once, validate at startup

python-dotenv reads .env into os.environ so the rest of the code only sees environment variables — the same code path works locally, in CI, and in production. Pairing the load with an explicit required-variable check turns missing configuration into a loud failure at boot rather than a confusing 401 mid-request (see Code Walkthrough).

Typed settings with Pydantic

For larger agents, pydantic_settings.BaseSettings adds type coercion (int, float, bool), required-field enforcement, and a single cached Settings object. It replaces scattered os.getenv() calls with one validated object that fails at startup if any required key is absent or malformed.

Code Walkthrough

This walkthrough combines the three runtime concepts — loading .env, validating required keys, and exposing a typed settings object — into one cohesive pattern your agent's entry point can import.

Code snippetpython
1import os 2from functools import lru_cache 3from dotenv import load_dotenv, find_dotenv 4from pydantic_settings import BaseSettings 5from pydantic import Field 6 7load_dotenv(find_dotenv()) 8 9def validate_environment() -> None: 10 required = ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"] 11 missing = [v for v in required if not os.getenv(v)] 12 if missing: 13 raise EnvironmentError( 14 f"Missing required environment variables: {', '.join(missing)}. " 15 f"Copy .env.example to .env and fill in your values." 16 ) 17 18class Settings(BaseSettings): 19 openai_api_key: str = Field(..., alias="OPENAI_API_KEY") 20 anthropic_api_key: str = Field(..., alias="ANTHROPIC_API_KEY") 21 default_model: str = Field("gpt-4o", alias="DEFAULT_MODEL") 22 max_iterations: int = Field(10, alias="AGENT_MAX_ITERATIONS") 23 24 class Config: 25 env_file = ".env" 26 case_sensitive = False 27 28@lru_cache() 29def get_settings() -> Settings: 30 return Settings() 31 32if __name__ == "__main__": 33 validate_environment() 34 settings = get_settings() 35 print(f"Loaded config, default model: {settings.default_model}")
  • load_dotenv(find_dotenv()) walks up from the current directory until it finds .env, so the same code works from any working directory.
  • validate_environment() runs before any API call; a missing key raises immediately with a fix-it hint instead of failing later as a 401.
  • Settings declares each variable with its type and required/optional status — Pydantic coerces AGENT_MAX_ITERATIONS="10" to int automatically and refuses to instantiate if a required field is absent.
  • @lru_cache() on get_settings() gives the rest of the codebase a single shared Settings instance.

You'll know it works when running the script with a complete .env prints the default model, and running it with OPENAI_API_KEY removed exits immediately with the "Missing required environment variables" error — not a runtime API failure.

Do's and Don'ts

Do's

  1. Do commit .env.example and gitignore .env — the template documents required variables without ever putting real keys into Git history.
  2. Do validate required variables at startup — surface missing configuration as an immediate, named error rather than a downstream 401.
  3. Do rotate any key that touches a commit — even a force-pushed leak is reachable via reflog and forks; assume scanners already have it.

Don'ts

  1. Don't hardcode keys "just for a quick test" — scanners find leaked keys in seconds, and reverting the commit does not un-leak them.
  2. Don't read os.getenv() ad-hoc throughout the code — a single typed settings object prevents typos and centralises validation.
  3. Don't share a .env over Slack or email — use a secrets manager (1Password, Vault, GCP Secret Manager) so access can be revoked per user.

Keep going with GenAI Agent Engineering

Create a free account to track your progress and open this lesson in the full learning view. Subscribe to unlock the entire path — every goal, the hands-on labs, quizzes, and your verifiable skill graph — from . Cancel anytime.