Files
moreminimore-marketing/docs/API_KEY_MANAGEMENT_ARCHITECTURE.md
Kunthawat Greethong c35fa52117 Base code
2026-01-08 22:39:53 +07:00

9.2 KiB

API Key Management Architecture

Overview

ALwrity supports two deployment modes with different API key management strategies:

  1. Local Development: API keys stored in .env files for convenience
  2. Production (Vercel + Render): User-specific API keys stored in database with full user isolation

Architecture

🏠 Local Development Mode

Detection:

  • DEBUG=true in environment variables, OR
  • DEPLOY_ENV is not set

API Key Storage:

  • Backend: backend/.env file
  • Frontend: frontend/.env file
  • Database: Also saved for consistency

Flow:

User completes onboarding
    ↓
API keys saved to database (user-isolated)
    ↓
API keys ALSO saved to .env files (for convenience)
    ↓
Backend services read from .env file
    ↓
Single developer, single set of keys

Advantages:

  • Quick setup for developers
  • No need to configure environment for every user
  • Keys persist across server restarts

🌐 Production Mode (Vercel + Render)

Detection:

  • DEBUG=false or not set, AND
  • DEPLOY_ENV is set (e.g., DEPLOY_ENV=render)

API Key Storage:

  • Backend: PostgreSQL database (user-isolated)
  • Frontend: localStorage (runtime only)
  • NOT in .env files

Flow:

Alpha Tester A completes onboarding
    ↓
API keys saved to database with user_id_A
    ↓
Backend services fetch keys from database when user_id_A makes requests
    ↓
Multiple users, each with their own keys
    ↓
Alpha Tester B completes onboarding
    ↓
API keys saved to database with user_id_B
    ↓
Backend services fetch keys from database when user_id_B makes requests

Advantages:

  • Complete user isolation - User A's keys never conflict with User B's keys
  • Zero cost for you - Each alpha tester uses their own API keys
  • Secure - Keys stored encrypted in database
  • Scalable - Unlimited alpha testers, each with their own keys

Implementation

1. Backend: User API Key Context

The UserAPIKeyContext class provides user-specific API keys to backend services:

from services.user_api_key_context import user_api_keys

# In your backend service
async def generate_content(user_id: str, prompt: str):
    # Get user-specific API keys
    with user_api_keys(user_id) as keys:
        gemini_key = keys.get('gemini')
        exa_key = keys.get('exa')
        
        # Use keys for this specific user
        response = await call_gemini_api(gemini_key, prompt)
        return response

How it works:

  • Development: Reads from backend/.env
  • Production: Fetches from database for the specific user_id

2. Frontend: CopilotKit Key Management

// Frontend automatically handles this:
// 1. Saves to localStorage (for runtime use)
// 2. In dev: Also saves to frontend/.env
// 3. In prod: Only uses localStorage

const copilotApiKey = localStorage.getItem('copilotkit_api_key');

3. Environment Variable Detection

Backend (backend/.env):

# Development
DEBUG=true

# Production
DEBUG=false
DEPLOY_ENV=render  # or 'railway', 'heroku', etc.

Render Dashboard:

DEBUG=false
DEPLOY_ENV=render

Vercel Dashboard:

REACT_APP_API_URL=https://alwrity.onrender.com
REACT_APP_BACKEND_URL=https://alwrity.onrender.com

Use Cases

Use Case 1: You (Developer) - Local Development

Setup:

# backend/.env
DEBUG=true
GEMINI_API_KEY=your_personal_key
EXA_API_KEY=your_personal_key
COPILOTKIT_API_KEY=your_personal_key

Behavior:

  • You complete onboarding once
  • Keys saved to both database AND .env files
  • All your local testing uses these keys
  • No need to re-enter keys

Use Case 2: Alpha Tester A - Production

Setup:

  • Alpha Tester A visits https://alwrity-ai.vercel.app
  • Goes through onboarding
  • Enters their own API keys:
    • GEMINI_API_KEY=tester_a_gemini_key
    • EXA_API_KEY=tester_a_exa_key
    • COPILOTKIT_API_KEY=tester_a_copilot_key

Behavior:

  • Keys saved to database with user_id=tester_a_clerk_id
  • When Tester A generates content:
    • Backend fetches tester_a_gemini_key from database
    • Uses Tester A's Gemini quota
    • All costs charged to Tester A's Gemini account

Use Case 3: Alpha Tester B - Production (Same Time)

Setup:

  • Alpha Tester B visits https://alwrity-ai.vercel.app
  • Goes through onboarding
  • Enters their own API keys:
    • GEMINI_API_KEY=tester_b_gemini_key
    • EXA_API_KEY=tester_b_exa_key
    • COPILOTKIT_API_KEY=tester_b_copilot_key

Behavior:

  • Keys saved to database with user_id=tester_b_clerk_id
  • When Tester B generates content:
    • Backend fetches tester_b_gemini_key from database
    • Uses Tester B's Gemini quota
    • All costs charged to Tester B's Gemini account
  • Tester A and Tester B completely isolated

Database Schema

-- OnboardingSession: One per user
CREATE TABLE onboarding_sessions (
    id SERIAL PRIMARY KEY,
    user_id VARCHAR(255) UNIQUE NOT NULL,  -- Clerk user ID
    current_step INTEGER DEFAULT 1,
    progress FLOAT DEFAULT 0.0,
    started_at TIMESTAMP DEFAULT NOW(),
    completed_at TIMESTAMP
);

-- APIKey: Multiple per user (one per provider)
CREATE TABLE api_keys (
    id SERIAL PRIMARY KEY,
    session_id INTEGER REFERENCES onboarding_sessions(id),
    provider VARCHAR(50) NOT NULL,  -- 'gemini', 'exa', 'copilotkit'
    key TEXT NOT NULL,              -- Encrypted in production
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    UNIQUE(session_id, provider)    -- One key per provider per user
);

Isolation:

  • Each user has their own onboarding_session
  • Each session has its own set of api_keys
  • Query: SELECT key FROM api_keys WHERE session_id = (SELECT id FROM onboarding_sessions WHERE user_id = ?)

Migration Path

Current State:

  • All users' keys overwrite the same .env file
  • Last user's keys are used for all users

New State:

  • Development: .env file for convenience
  • Production: Database per user
  • Complete user isolation

Code Changes Required:

Before (BAD - uses global .env):

import os

def generate_content(prompt: str):
    gemini_key = os.getenv('GEMINI_API_KEY')  # Same for all users!
    response = call_gemini_api(gemini_key, prompt)
    return response

After (GOOD - uses user-specific keys):

from services.user_api_key_context import user_api_keys

def generate_content(user_id: str, prompt: str):
    with user_api_keys(user_id) as keys:
        gemini_key = keys.get('gemini')  # User-specific key!
        response = call_gemini_api(gemini_key, prompt)
        return response

Testing

Test Local Development:

  1. Set DEBUG=true in backend/.env
  2. Complete onboarding with test keys
  3. Check backend/.env - should contain keys
  4. Generate content - should use keys from .env

Test Production:

  1. Set DEBUG=false and DEPLOY_ENV=render on Render
  2. User A completes onboarding with keys A
  3. User B completes onboarding with keys B
  4. User A generates content - uses keys A
  5. User B generates content - uses keys B
  6. Check database:
    SELECT user_id, provider, key FROM api_keys 
    JOIN onboarding_sessions ON api_keys.session_id = onboarding_sessions.id;
    
    Should show separate keys for User A and User B

Security Considerations

Production Enhancements (Future):

  1. Encrypt API keys in database using application secret
  2. Rate limiting per user to prevent abuse
  3. Key validation before saving
  4. Audit logging of API key usage
  5. Key rotation support

Current Implementation:

  • Keys stored in database (not in code)
  • User isolation via user_id
  • HTTPS encryption in transit
  • ⚠️ Keys not encrypted at rest (TODO)

Troubleshooting

Issue: "No API key found"

  • Development: Check backend/.env file exists and has keys
  • Production: Check database has keys for this user:
    SELECT * FROM api_keys 
    WHERE session_id = (SELECT id FROM onboarding_sessions WHERE user_id = 'user_xxx');
    

Issue: "Wrong user's keys being used"

  • Cause: Service not using UserAPIKeyContext
  • Fix: Update service to use user_api_keys(user_id) context manager

Issue: "Keys not saving to .env in development"

  • Cause: DEBUG not set to true
  • Fix: Set DEBUG=true in backend/.env

Summary

Feature Local Development Production
Key Storage .env files + Database Database only
User Isolation Not needed (single user) Full isolation
Cost Your API keys Each user's API keys
Convenience High (keys persist) Medium (enter once)
Scalability 1 developer Unlimited users
Detection DEBUG=true DEPLOY_ENV set

Bottom Line:

  • 🏠 Local: Quick setup, your keys, .env convenience
  • 🌐 Production: User isolation, their keys, zero cost for you

This architecture ensures:

  1. You can develop locally with convenience
  2. Alpha testers use their own keys (no cost to you)
  3. Complete user isolation in production
  4. Seamless transition between environments