RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Eruo Fredoline
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /AI-Powered SaaS Products
  6. /Ch. 9
AI-Powered SaaS Products

09. API Key Management

Chapter 9 of 24 · 15 min
KEY INSIGHT

API keys are the primary authentication mechanism for programmatic access—treat them like passwords, implement rotation, and provide fine-grained access controls. API key management in multi-tenant SaaS requires balancing security with developer experience. Keys should be easy to create and rotate but difficult to compromise and easy to revoke. Key scopes limit what an API key can access. A key for development might only access test models; a production key might access all models but have IP restrictions. ```python from sqlalchemy import Column, String, Integer, Boolean, DateTime, JSON from datetime import datetime, timedelta class ApiKey(Base): __tablename__ = "api_keys" id = Column(String(50), primary_key=True) workspace_id = Column(String(36), ForeignKey("workspaces.id"), nullable=False) name = Column(String(255), nullable=False) key_hash = Column(String(64), nullable=False) # Scoping allowed_models = Column(JSON, nullable=True) # None = all models allowed_ips = Column(JSON, nullable=True) # None = any IP max_requests_per_day = Column(Integer, nullable=True) # Status is_active = Column(Boolean, default=True) expires_at = Column(DateTime, nullable=True) last_used_at = Column(DateTime, nullable=True) # Rate tracking total_usage_kobo = Column(Integer, default=0) total_tokens = Column(Integer, default=0) workspace = relationship("Workspace", back_populates="api_keys") class ApiKeyManager: def __init__(self, db: Session): self.db = db self.token_tracker = TokenTracker() def create_key( self, workspace_id: str, name: str, allowed_models: list[str] | None = None, allowed_ips: list[str] | None = None, expires_in_days: int | None = 365 ) -> tuple[str, ApiKey]: """Create a new API key. Returns (full_key, database_record). The full_key is shown ONLY once—after this, only the hash exists. """ key_id = f"sk_{secrets.token_urlsafe(12)}" secret = secrets.token_urlsafe(32) key_hash = hashlib.sha256(secret.encode()).hexdigest() expires_at = None if expires_in_days: expires_at = datetime.utcnow() + timedelta(days=expires_in_days) api_key = ApiKey( id=key_id, workspace_id=workspace_id, name=name, key_hash=key_hash, allowed_models=allowed_models, allowed_ips=allowed_ips, expires_at=expires_at ) self.db.add(api_key) self.db.commit() # Return full key for user to copy return f"{key_id}_{secret}", api_key def validate_key( self, full_key: str, client_ip: str | None = None ) -> ApiKey: """Validate API key and check scope restrictions.""" key_id, secret = full_key.split("_", 1) secret_hash = hashlib.sha256(secret.encode()).hexdigest() api_key = self.db.query(ApiKey).filter_by( id=key_id, key_hash=secret_hash ).first() if not api_key or not api_key.is_active: raise ValueError("Invalid or inactive API key") if api_key.expires_at and api_key.expires_at < datetime.utcnow(): raise ValueError("API key has expired") if api_key.allowed_ips and client_ip not in api_key.allowed_ips: raise ValueError(f"IP {client_ip} is not allowed for this key") # Update last used timestamp api_key.last_used_at = datetime.utcnow() self.db.commit() return api_key ``` A failure scenario: keys created for testing in development environments getting used in production. If allowed_models isn't set, any model is accessible. Consider requiring explicit model whitelisting for production workspaces.

EXERCISE

Implement key rotation with a grace period. When a user requests rotation, generate a new key and mark the old key as "rotating." During the rotation period (configurable, default 24 hours), accept both keys. After the grace period, automatically deactivate the old key.

← Chapter 8
Token Tracking
Chapter 10 →
Subscription Billing