Systems Library / AI Model Setup / How to Create AI API Keys Securely
AI Model Setup foundations

How to Create AI API Keys Securely

Store and manage AI API keys using environment variables and secret managers.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Secure AI API key management is the first thing I lock down before building anything for a client. The secure ai api key management best practices I use have prevented multiple potential leaks over the years. Leaked API keys are a real problem: someone finds your key in a GitHub repo, racks up thousands in charges, and the provider may not refund you. With five or six different AI provider keys in active use, a proper system is not optional.

The rules are simple: never hardcode keys in source code, never commit them to version control, use environment variables locally, and use a proper secret manager in production. This tutorial covers all of it.

What You Need Before Starting

Step 1: The .env File Pattern

The most common pattern for local development: store keys in a .env file, load them into your script at runtime.

Create .env in your project root:

# AI API Keys
ANTHROPIC_API_KEY=sk-ant-your-claude-key-here
OPENAI_API_KEY=sk-proj-your-openai-key-here
GROQ_API_KEY=gsk_your-groq-key-here
MISTRAL_API_KEY=your-mistral-key-here
PERPLEXITY_API_KEY=pplx-your-perplexity-key-here
GOOGLE_API_KEY=AIza-your-gemini-key-here

# Google Service Account
GOOGLE_SERVICE_ACCOUNT_PATH=/absolute/path/to/credentials.json

# Other integrations
SLACK_BOT_TOKEN=xoxb-your-slack-token

Set up .gitignore immediately:

echo ".env" >> .gitignore
echo "*.json" >> .gitignore  # For credential files

Or add to your existing .gitignore:

# Secrets - never commit these
.env
.env.local
.env.production
*.pem
*credentials*.json
*service-account*.json

Load keys in Python:

import os
from dotenv import load_dotenv

load_dotenv()  # Loads .env file from current directory

api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
    raise ValueError("OPENAI_API_KEY not found. Check your .env file.")

Step 2: Create a Centralized Key Loader

Build one module that loads and validates all your keys. Import this everywhere instead of calling load_dotenv() in every file:

# config.py
import os
from dotenv import load_dotenv
from dataclasses import dataclass
from typing import Optional


load_dotenv()


@dataclass
class AIConfig:
    """Centralized configuration for all AI API keys."""
    anthropic_api_key: str
    openai_api_key: str
    groq_api_key: Optional[str] = None
    mistral_api_key: Optional[str] = None
    perplexity_api_key: Optional[str] = None
    google_api_key: Optional[str] = None


def load_config(require: list[str] = None) -> AIConfig:
    """
    Load AI configuration from environment variables.
    
    Args:
        require: List of key names that must be present. 
                 Options: 'anthropic', 'openai', 'groq', 'mistral', 'perplexity', 'google'
    
    Returns:
        AIConfig with available keys
    
    Raises:
        ValueError if required keys are missing
    """
    config = AIConfig(
        anthropic_api_key=os.getenv("ANTHROPIC_API_KEY", ""),
        openai_api_key=os.getenv("OPENAI_API_KEY", ""),
        groq_api_key=os.getenv("GROQ_API_KEY"),
        mistral_api_key=os.getenv("MISTRAL_API_KEY"),
        perplexity_api_key=os.getenv("PERPLEXITY_API_KEY"),
        google_api_key=os.getenv("GOOGLE_API_KEY")
    )
    
    if require:
        key_map = {
            "anthropic": config.anthropic_api_key,
            "openai": config.openai_api_key,
            "groq": config.groq_api_key,
            "mistral": config.mistral_api_key,
            "perplexity": config.perplexity_api_key,
            "google": config.google_api_key
        }
        
        missing = [name for name in require if not key_map.get(name)]
        if missing:
            raise ValueError(f"Missing required API keys: {', '.join(missing).upper()}_API_KEY")
    
    return config


# Usage in any script
config = load_config(require=["anthropic", "openai"])
print("All required keys loaded successfully")

Step 3: Scan Your Codebase for Accidentally Hardcoded Keys

Run this check before every commit:

# check_for_hardcoded_keys.py
import re
import os
from pathlib import Path


# Patterns that suggest hardcoded keys
KEY_PATTERNS = [
    r'sk-ant-[a-zA-Z0-9\-]{20,}',        # Anthropic
    r'sk-proj-[a-zA-Z0-9\-]{20,}',        # OpenAI
    r'sk-[a-zA-Z0-9]{20,}',               # OpenAI (older format)
    r'gsk_[a-zA-Z0-9]{20,}',              # Groq
    r'pplx-[a-zA-Z0-9]{20,}',             # Perplexity
    r'AIza[a-zA-Z0-9\-_]{30,}',           # Google
    r'xoxb-[0-9]+-[a-zA-Z0-9]+',          # Slack bot token
]

EXCLUDE_DIRS = {'.git', 'node_modules', '__pycache__', '.venv', 'venv'}
EXCLUDE_FILES = {'.env', '.env.local', '.env.example'}


def scan_for_keys(directory: str = ".") -> list[dict]:
    """Scan a directory for potentially hardcoded API keys."""
    findings = []
    
    for file_path in Path(directory).rglob("*"):
        # Skip excluded directories and files
        if any(excluded in file_path.parts for excluded in EXCLUDE_DIRS):
            continue
        if file_path.name in EXCLUDE_FILES:
            continue
        if not file_path.is_file():
            continue
        
        # Only scan text files
        try:
            content = file_path.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            continue
        
        for pattern in KEY_PATTERNS:
            matches = re.finditer(pattern, content)
            for match in matches:
                line_num = content[:match.start()].count('\n') + 1
                findings.append({
                    "file": str(file_path),
                    "line": line_num,
                    "match": match.group()[:20] + "...",  # Show partial key only
                    "pattern": pattern
                })
    
    return findings


if __name__ == "__main__":
    print("Scanning for hardcoded API keys...\n")
    findings = scan_for_keys(".")
    
    if findings:
        print(f"ALERT: Found {len(findings)} potential hardcoded key(s):\n")
        for f in findings:
            print(f"  {f['file']}:{f['line']} - {f['match']}")
        exit(1)
    else:
        print("Clean. No hardcoded keys found.")
        exit(0)

Add this as a pre-commit hook:

# .git/hooks/pre-commit
#!/bin/bash
python check_for_hardcoded_keys.py
if [ $? -ne 0 ]; then
    echo "Commit blocked: hardcoded API keys detected."
    exit 1
fi
chmod +x .git/hooks/pre-commit

Step 4: Use Environment Variables in Production

For production deployments, never use .env files. Use your platform's native secret management:

Linux VPS with systemd:

# /etc/systemd/system/my-ai-app.service
[Service]
Environment="ANTHROPIC_API_KEY=sk-ant-your-key"
Environment="OPENAI_API_KEY=sk-proj-your-key"

Or use a separate environment file:

# /etc/my-ai-app/env (readable only by root and app user)
ANTHROPIC_API_KEY=sk-ant-your-key
OPENAI_API_KEY=sk-proj-your-key
[Service]
EnvironmentFile=/etc/my-ai-app/env

Heroku:

heroku config:set ANTHROPIC_API_KEY=sk-ant-your-key
heroku config:set OPENAI_API_KEY=sk-proj-your-key

Railway, Render, Fly.io: Each has a "Environment Variables" or "Secrets" section in their dashboard. Set them there. They inject as environment variables at runtime.

Step 5: Rotate Keys and Set Spending Limits

Key rotation is what most developers skip. Do it on a schedule:

# key_health_check.py
import os
import anthropic
import openai
from dotenv import load_dotenv

load_dotenv()


def check_anthropic_key() -> dict:
    """Verify Anthropic key is valid."""
    try:
        client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
        # Make a minimal API call
        client.messages.create(
            model="claude-haiku-20240307",
            max_tokens=10,
            messages=[{"role": "user", "content": "hi"}]
        )
        return {"provider": "anthropic", "status": "valid"}
    except anthropic.AuthenticationError:
        return {"provider": "anthropic", "status": "INVALID - rotate immediately"}
    except Exception as e:
        return {"provider": "anthropic", "status": f"error: {e}"}


def check_openai_key() -> dict:
    """Verify OpenAI key is valid."""
    try:
        client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
        client.models.list()
        return {"provider": "openai", "status": "valid"}
    except openai.AuthenticationError:
        return {"provider": "openai", "status": "INVALID - rotate immediately"}
    except Exception as e:
        return {"provider": "openai", "status": f"error: {e}"}


if __name__ == "__main__":
    print("Checking API key health...\n")
    checks = [check_anthropic_key(), check_openai_key()]
    for check in checks:
        status_icon = "OK" if check["status"] == "valid" else "ALERT"
        print(f"[{status_icon}] {check['provider']}: {check['status']}")

Set spending limits in each provider's dashboard:

Set a hard monthly cap. If a key leaks, you will get alerted when spend spikes before the damage is catastrophic.

What to Build Next

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems