Skip to content
API Key Management for AI Agent Platforms: Generation, Rotation, and Revocation
Learn Agentic AI14 min read7 views

API Key Management for AI Agent Platforms: Generation, Rotation, and Revocation

Build a production-grade API key management system for AI agent platforms. Covers key generation, secure hashing, scoping, rate limiting, rotation strategies, and revocation with FastAPI.

Why API Keys Still Matter

Despite OAuth2 and JWTs dominating modern authentication, API keys remain the most common way developers interact with AI platforms. OpenAI, Anthropic, Google, and every major AI provider use API keys as the primary access mechanism. The reason is simplicity — a developer copies a key, sets it in an environment variable, and starts making requests. No redirect flows, no browser required.

For AI agent platforms, API keys serve a dual purpose: they authenticate programmatic access from scripts, SDKs, and CI/CD pipelines, and they provide a natural unit for rate limiting, billing, and usage tracking. Getting key management right is critical for both security and developer experience.

Designing the Key Format

A well-designed API key should be immediately identifiable, sufficiently random, and structured for efficient validation. Follow the pattern used by major providers:

flowchart LR
    CLIENT(["Client SDK"])
    GW["API Gateway<br/>auth plus rate limit"]
    APP["FastAPI app<br/>handlers and DI"]
    VAL["Pydantic validation"]
    SVC["Service layer<br/>business logic"]
    DB[(Database)]
    QUEUE[(Background queue)]
    OBS[(Tracing)]
    CLIENT --> GW --> APP --> VAL --> SVC
    SVC --> DB
    SVC --> QUEUE
    SVC --> OBS
    SVC --> CLIENT
    style GW fill:#4f46e5,stroke:#4338ca,color:#fff
    style APP fill:#f59e0b,stroke:#d97706,color:#1f2937
    style DB fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
csa_live_7f3k9m2x4p8q1w6y0t5r
└──┘ └──┘ └──────────────────┘
prefix env    random component

The prefix csa_ (CallSphere Agent) immediately identifies the key source. The environment segment distinguishes live from test keys. The random component provides 128+ bits of entropy.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
# auth/api_keys.py
import secrets
import hashlib
from datetime import datetime, timezone

def generate_api_key(environment: str = "live") -> tuple[str, str]:
    """Generate an API key and its hash. Returns (plain_key, key_hash)."""
    random_part = secrets.token_urlsafe(24)  # 192 bits of entropy
    prefix = f"csa_{environment}_"
    plain_key = f"{prefix}{random_part}"

    # Only store the hash — never the plain key
    key_hash = hashlib.sha256(plain_key.encode()).hexdigest()
    return plain_key, key_hash

def hash_api_key(plain_key: str) -> str:
    """Hash a key for lookup. Same algorithm as generation."""
    return hashlib.sha256(plain_key.encode()).hexdigest()

The critical principle: never store the plain-text key. Show it to the user exactly once at creation time, store only the SHA-256 hash, and use the hash for all lookups. This mirrors how password hashing works — if the database is compromised, the attacker gets hashes, not usable keys.

Database Schema for Key Management

Store keys with their metadata, scopes, and rate limit configuration:

from sqlalchemy import Column, String, DateTime, Integer, Boolean, JSON
from sqlalchemy.dialects.postgresql import UUID
import uuid

class APIKey(Base):
    __tablename__ = "api_keys"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    user_id = Column(String, nullable=False, index=True)
    org_id = Column(String, nullable=False, index=True)
    key_hash = Column(String(64), unique=True, nullable=False, index=True)
    key_prefix = Column(String(20), nullable=False)  # For display: "csa_live_7f3k..."
    name = Column(String(100), nullable=False)        # Human-readable label
    scopes = Column(JSON, default=list)               # ["agents:read", "agents:execute"]
    rate_limit_rpm = Column(Integer, default=60)      # Requests per minute
    is_active = Column(Boolean, default=True)
    last_used_at = Column(DateTime(timezone=True), nullable=True)
    expires_at = Column(DateTime(timezone=True), nullable=True)
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    revoked_at = Column(DateTime(timezone=True), nullable=True)

Key Validation Middleware

Build a FastAPI dependency that extracts the API key from the header, hashes it, looks it up, and enforces rate limits:

from fastapi import Depends, HTTPException, Security, status
from fastapi.security import APIKeyHeader
import time

api_key_header = APIKeyHeader(name="X-API-Key")

# Simple in-memory rate limiter (use Redis in production)
rate_limit_store: dict[str, list[float]] = {}

async def validate_api_key(
    key: str = Security(api_key_header),
) -> APIKey:
    key_hash = hash_api_key(key)
    api_key = await db.get_by_hash(key_hash)

    if not api_key or not api_key.is_active:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or revoked API key",
        )

    if api_key.expires_at and api_key.expires_at < datetime.now(timezone.utc):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="API key has expired",
        )

    # Rate limiting check
    now = time.time()
    window = rate_limit_store.setdefault(key_hash, [])
    window[:] = [t for t in window if now - t < 60]  # 1-minute window
    if len(window) >= api_key.rate_limit_rpm:
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail="Rate limit exceeded",
        )
    window.append(now)

    # Update last_used_at asynchronously
    await db.update_last_used(api_key.id)
    return api_key

Key Rotation Without Downtime

Rotation is essential — keys get leaked in logs, screenshots, and shared repositories. Support overlap periods where both old and new keys work:

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

@router.post("/api-keys/{key_id}/rotate")
async def rotate_key(key_id: str, user=Depends(get_current_user)):
    old_key = await db.get_key(key_id)
    if not old_key or old_key.user_id != user.sub:
        raise HTTPException(status_code=404, detail="Key not found")

    # Generate new key
    plain_key, key_hash = generate_api_key()

    # Create new key with same scopes and limits
    new_key = await db.create_key(
        user_id=user.sub, org_id=old_key.org_id,
        key_hash=key_hash, key_prefix=plain_key[:16] + "...",
        name=f"{old_key.name} (rotated)",
        scopes=old_key.scopes, rate_limit_rpm=old_key.rate_limit_rpm,
    )

    # Schedule old key deactivation (grace period)
    await db.schedule_revocation(
        old_key.id,
        revoke_at=datetime.now(timezone.utc) + timedelta(hours=24),
    )

    return {
        "new_key": plain_key,  # Show once
        "old_key_expires": "24 hours",
        "message": "Update your systems, then the old key will auto-expire",
    }

Revocation

Immediate revocation should be a single database update that sets is_active = False and records the revocation timestamp. The validation middleware already checks is_active on every request, so the key becomes unusable immediately.

FAQ

Why hash API keys with SHA-256 instead of bcrypt?

API keys are high-entropy random strings, not human-chosen passwords. They do not need the slow hashing that bcrypt provides to resist dictionary attacks. SHA-256 is fast enough for per-request validation while being irreversible — if the database leaks, an attacker cannot recover the original key from the hash. Bcrypt would add significant latency to every API call.

How should I scope API keys for different agent capabilities?

Design scopes around your resource model: agents:read, agents:execute, tools:invoke, logs:read. Let users select scopes during key creation. Enforce scopes in your middleware the same way you enforce JWT scopes. The principle of least privilege applies — a key for reading logs should never be able to execute agents.

For production AI agent platforms, require keys to expire within 90 days. Send email notifications at 30, 14, and 7 days before expiry. Provide a rotation endpoint that creates a new key and gives a 24-hour grace period for the old one. Keys used in CI/CD pipelines should have shorter lifetimes and be rotated automatically by the pipeline tooling.


#APIKeys #Security #FastAPI #AIAgents #RateLimiting #KeyManagement #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Agents

Personal AI Assistant: How to Pick One for Business in 2026

A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.

AI Agents

Free AI Agents in 2026: When Free Wins and When It Costs You

A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.

Agentic AI

Graphiti: How Temporal Knowledge Graphs Give AI Voice Agents Persistent Memory (2026 Guide)

Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.

AI Agents

Chatbot App vs ChatGPT: What's the Difference, and Which Do I Need?

Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.

HVAC

Building an HVAC After-Hours Emergency Escalation System: A Complete Engineering Guide

How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.

Enterprise AI

OpenAI Frontier vs Anthropic Managed Agents: 2026 Comparison

Head-to-head: OpenAI Frontier and Anthropic's managed agent stack — strengths, fit, and what each means for enterprise AI voice and chat deployment.