April 22, 2026 β€’ Version: v1.2.0+

Support BYOK (Bring Your Own Key) for Cloud Gateways

Implementation guide for enabling custom AI provider API key management in cloud-deployed OpenClaw gateways with secure storage and billing transparency.

πŸ” Overview

This guide covers the implementation requirements for adding Bring Your Own Key (BYOK) support to cloud-deployed OpenClaw gateways. Users must be able to provide their own AI provider API keys (OpenAI, Anthropic, Google AI, etc.) instead of relying on the application’s bundled credentials.

Feature Scope

The BYOK system for cloud gateways requires implementation across three layers:

  • Client-Side UI: Settings interface for entering, viewing, and managing API keys per provider
  • Secure Storage: Keychain integration on desktop clients, encrypted vault on mobile
  • Gateway Configuration: Environment variable injection mechanism for cloud-deployed gateways

Existing Implementation Reference

Local gateways already implement BYOK via the setup wizard (see PR #221). The cloud gateway implementation should align with the established patterns while addressing cloud-specific considerations:


Local Gateway BYOK Architecture:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Setup Wizard│────▢│ Secure Storage│────▢│ .env.local  β”‚
β”‚   (UI)      β”‚     β”‚   (Keychain)  β”‚     β”‚   (File)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Cloud Gateway BYOK Architecture:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Settings UI │────▢│ Secure Vault │────▢│ Config API Patch │────▢│ Env Var     β”‚
β”‚   (Cloud)   β”‚     β”‚  (Encrypted) β”‚     β”‚   (Gateway)      β”‚     β”‚ Injection   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧠 Technical Requirements

Architecture Components

1. API Key Provider Registry

The system must maintain a registry of supported AI providers with their configuration requirements:


// src/providers/registry.ts
export interface ProviderConfig {
  id: string;
  name: string;
  apiKeyEnvVar: string;
  endpoint?: string;
  requiresOrgId?: boolean;
  documentationUrl: string;
}

export const AI_PROVIDERS: Record<string, ProviderConfig> = {
  openai: {
    id: 'openai',
    name: 'OpenAI',
    apiKeyEnvVar: 'OPENAI_API_KEY',
    endpoint: 'https://api.openai.com/v1',
    documentationUrl: 'https://platform.openai.com/docs/api-keys'
  },
  anthropic: {
    id: 'anthropic',
    name: 'Anthropic',
    apiKeyEnvVar: 'ANTHROPIC_API_KEY',
    documentationUrl: 'https://docs.anthropic.com/en/api/getting-started'
  },
  google: {
    id: 'google',
    name: 'Google AI',
    apiKeyEnvVar: 'GOOGLE_API_KEY',
    requiresOrgId: true,
    documentationUrl: 'https://ai.google.dev/tutorials/setup'
  }
};

2. Secure Storage Specification

Client-Side Storage (Keychain/Secure Enclave)

// src/storage/secure-keychain.ts
interface SecureKeyStorage {
  // Store API key with provider identifier
  setApiKey(provider: string, key: string): Promise<boolean>;
  
  // Retrieve API key (returns null if not found)
  getApiKey(provider: string): Promise<string | null>;
  
  // List configured providers (without exposing keys)
  listProviders(): Promise<string[]>;
  
  // Remove API key
  deleteApiKey(provider: string): Promise<boolean>;
  
  // Validate key format before storage
  validateKeyFormat(provider: string, key: string): ValidationResult;
}

interface ValidationResult {
  valid: boolean;
  error?: string;
  maskedKey?: string; // e.g., "sk-...xyz"
}
Key Format Validation Patterns

// Validation patterns by provider
const KEY_PATTERNS = {
  openai: /^sk-[A-Za-z0-9_-]{20,}$/,
  anthropic: /^sk-ant-[A-Za-z0-9_-]{20,}$/,
  google: /^[A-Za-z0-9_-]{39}$/,
  azure: /^[A-Za-z0-9]{32}$/
};

3. Gateway Configuration API

The cloud gateway requires a secure configuration patch mechanism:


// Gateway Config API Endpoint
// POST /api/v1/gateway/config/patch

interface ConfigPatchRequest {
  operation: 'set' | 'remove';
  target: 'env' | 'secret';
  key: string;
  value?: string; // Required for 'set' operation
  metadata?: {
    provider?: string;
    createdAt: string;
    expiresAt?: string;
  };
}

interface ConfigPatchResponse {
  success: boolean;
  appliedAt: string;
  restartRequired: boolean;
  error?: string;
}

πŸ› οΈ Implementation Steps

Phase 1: Settings UI Components

Step 1.1: Create API Key Management Panel


// src/components/Settings/ApiKeyManager.tsx

import { useState } from 'react';
import { KeychainStorage } from '@/storage/secure-keychain';
import { AI_PROVIDERS } from '@/providers/registry';
import { BillingDisclaimer } from './BillingDisclaimer';

export function ApiKeyManager() {
  const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
  const [apiKey, setApiKey] = useState('');
  const [isValidating, setIsValidating] = useState(false);
  const [configuredProviders, setConfiguredProviders] = useState<string[]>([]);
  
  // Load configured providers on mount
  useEffect(() => {
    KeychainStorage.listProviders().then(setConfiguredProviders);
  }, []);

  const handleSaveKey = async () => {
    if (!selectedProvider || !apiKey) return;
    
    setIsValidating(true);
    const validation = KeychainStorage.validateKeyFormat(selectedProvider, apiKey);
    
    if (!validation.valid) {
      showError(validation.error);
      setIsValidating(false);
      return;
    }

    // Store securely
    await KeychainStorage.setApiKey(selectedProvider, apiKey);
    
    // Sync to cloud gateway
    await syncKeyToGateway(selectedProvider, apiKey);
    
    // Clear input and refresh list
    setApiKey('');
    setConfiguredProviders(await KeychainStorage.listProviders());
    setIsValidating(false);
  };

  return (
    <div className="api-key-manager">
      <BillingDisclaimer />
      
      <div className="provider-grid">
        {Object.values(AI_PROVIDERS).map(provider => (
          <ProviderCard
            key={provider.id}
            provider={provider}
            isConfigured={configuredProviders.includes(provider.id)}
            onSelect={() => setSelectedProvider(provider.id)}
          />
        ))}
      </div>

      {selectedProvider && (
        <ApiKeyInputForm
          provider={AI_PROVIDERS[selectedProvider]}
          value={apiKey}
          onChange={setApiKey}
          onSubmit={handleSaveKey}
          isLoading={isValidating}
        />
      )}
    </div>
  );
}

Step 1.2: Create Billing Disclaimer Component


// src/components/Settings/BillingDisclaimer.tsx

export function BillingDisclaimer() {
  return (
    <div className="billing-disclaimer">
      <div className="disclaimer-icon">πŸ’³</div>
      <div className="disclaimer-content">
        <h4>Billing Responsibility</h4>
        <p>
          When you provide your own API key, all usage costs are billed directly 
          to your account with the AI provider. OpenClaw does not process, markup, 
          or have visibility into your API usage or billing.
        </p>
        <ul>
          <li>You're responsible for your provider's usage limits and quotas</li>
          <li>API keys are transmitted securely and never stored in plaintext on servers</li>
          <li>You can revoke access at any time from your provider's dashboard</li>
        </ul>
        <a href="../../docs/byok/billing-faq" target="_blank">
          Learn more about BYOK billing β†’
        </a>
      </div>
    </div>
  );
}

Phase 2: Secure Storage Implementation

Step 2.1: Keychain Storage Service


// src/storage/secure-keychain.ts

export class KeychainStorage implements SecureKeyStorage {
  private static readonly SERVICE_PREFIX = 'openclaw.byok';
  
  static async setApiKey(provider: string, key: string): Promise<boolean> {
    const service = `${this.SERVICE_PREFIX}.${provider}`;
    
    // Platform-specific implementation
    if (Platform.OS === 'ios' || Platform.OS === 'android') {
      return this.setSecureItem(service, key);
    }
    
    // Desktop: Use electron-store with encryption or native Keychain
    if (process.env.ELECTRON === 'true') {
      return this.setElectronKeychain(service, key);
    }
    
    throw new Error(`Platform ${Platform.OS} does not support secure storage`);
  }

  static async getApiKey(provider: string): Promise<string | null> {
    const service = `${this.SERVICE_PREFIX}.${provider}`;
    
    if (Platform.OS === 'ios') {
      return this.getIOSKeychain(service);
    }
    if (Platform.OS === 'android') {
      return this.getAndroidKeystore(service);
    }
    if (process.env.ELECTRON === 'true') {
      return this.getElectronKeychain(service);
    }
    
    return null;
  }

  static async listProviders(): Promise<string[]> {
    // Return list of providers with stored keys (without exposing the keys)
    const prefix = `${this.SERVICE_PREFIX}.`;
    const services = await this.listSecureServices(prefix);
    return services.map(s => s.replace(prefix, ''));
  }

  static validateKeyFormat(provider: string, key: string): ValidationResult {
    const pattern = KEY_PATTERNS[provider];
    
    if (!pattern) {
      return { valid: false, error: `Unknown provider: ${provider}` };
    }

    const trimmedKey = key.trim();
    
    if (!pattern.test(trimmedKey)) {
      return {
        valid: false,
        error: `Invalid key format for ${provider}. Expected format: ${pattern.description}`
      };
    }

    return {
      valid: true,
      maskedKey: this.maskKey(trimmedKey)
    };
  }

  private static maskKey(key: string): string {
    // Show first 3 and last 4 characters
    if (key.length <= 10) return '***';
    return `${key.slice(0, 3)}...${key.slice(-4)}`;
  }
}

Phase 3: Gateway Configuration Sync

Step 3.1: Cloud Gateway API Client


// src/services/gateway-config-sync.ts

export class GatewayConfigSync {
  private readonly gatewayApiBase: string;

  constructor(gatewayId: string) {
    this.gatewayApiBase = `https://${gatewayId}.gateways.openclaw.io`;
  }

  async syncApiKey(provider: string, apiKey: string): Promise<ConfigPatchResponse> {
    const providerConfig = AI_PROVIDERS[provider];
    
    if (!providerConfig) {
      throw new Error(`Unknown provider: ${provider}`);
    }

    const response = await fetch(`${this.gatewayApiBase}/api/v1/config/patch`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${await this.getGatewayToken()}`
      },
      body: JSON.stringify({
        operation: 'set',
        target: 'secret', // Use secret, not env, for API keys
        key: providerConfig.apiKeyEnvVar,
        value: apiKey,
        metadata: {
          provider,
          createdAt: new Date().toISOString()
        }
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Failed to sync API key: ${error.message}`);
    }

    return response.json();
  }

  async removeApiKey(provider: string): Promise<ConfigPatchResponse> {
    const providerConfig = AI_PROVIDERS[provider];
    
    const response = await fetch(`${this.gatewayApiBase}/api/v1/config/patch`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${await this.getGatewayToken()}`
      },
      body: JSON.stringify({
        operation: 'remove',
        target: 'secret',
        key: providerConfig.apiKeyEnvVar
      })
    });

    return response.json();
  }
}

Step 3.2: Gateway-Side Secret Management

On the cloud gateway side, implement secret rotation and secure injection:


# gateway/src/middleware/secrets-injector.ts

import { SecretManager } from '@gateway/secrets';

export class SecretsInjector {
  private secrets: Map<string, string> = new Map();
  private secretManager: SecretManager;

  constructor() {
    this.secretManager = new SecretManager();
  }

  async loadSecrets(): Promise<void> {
    // Load all BYOK secrets from secure storage
    const userSecrets = await this.secretManager.listUserSecrets();
    
    for (const secret of userSecrets) {
      this.secrets.set(secret.key, secret.value);
    }
  }

  getSecret(key: string): string | undefined {
    return this.secrets.get(key);
  }

  // Called when AI provider is invoked
  injectProviderCredentials(provider: string): Record<string, string> {
    const envVars: Record<string, string> = {};
    
    switch (provider) {
      case 'openai':
        envVars.OPENAI_API_KEY = this.getSecret('OPENAI_API_KEY') ?? '';
        break;
      case 'anthropic':
        envVars.ANTHROPIC_API_KEY = this.getSecret('ANTHROPIC_API_KEY') ?? '';
        break;
      case 'google':
        envVars.GOOGLE_API_KEY = this.getSecret('GOOGLE_API_KEY') ?? '';
        break;
    }

    return envVars;
  }
}

Phase 4: Configuration Migration

Before (Using Bundled Credentials)


# gateway/.env (managed by OpenClaw)
AI_PROVIDER=openai
OPENAI_API_KEY=sk-org-managed-key-12345
ANTHROPIC_API_KEY=sk-ant-org-managed-key-67890

After (User BYOK with Fallback)


# gateway/.env (partial, non-sensitive)
AI_PROVIDER=openai
USE_BUNDLED_CREDENTIALS=false
BYOK_ENABLED=true

# Secrets stored separately (never committed to repo)
# Loaded from secure secret manager at runtime
# OPENAI_API_KEY=sk-user-provided-key (injected from secrets)

πŸ§ͺ Verification

Verification Checklist

Execute the following tests to validate the BYOK implementation:

Test 1: Key Storage Validation


// Unit test: Keychain storage operations
describe('KeychainStorage', () => {
  it('should store and retrieve API key', async () => {
    const testKey = 'sk-test-1234567890abcdefghijklmnop';
    await KeychainStorage.setApiKey('openai', testKey);
    const retrieved = await KeychainStorage.getApiKey('openai');
    expect(retrieved).toBe(testKey);
  });

  it('should reject invalid key format', async () => {
    const result = KeychainStorage.validateKeyFormat('openai', 'invalid-key');
    expect(result.valid).toBe(false);
    expect(result.error).toContain('Invalid key format');
  });

  it('should mask keys correctly', async () => {
    const result = KeychainStorage.validateKeyFormat(
      'openai', 
      'sk-1234567890abcdefghijklmnopqrstuvwxyz'
    );
    expect(result.maskedKey).toBe('sk-...qrst');
  });
});

Test 2: Gateway Config Sync


// Integration test: Gateway configuration sync
describe('GatewayConfigSync', () => {
  it('should sync API key to cloud gateway', async () => {
    const sync = new GatewayConfigSync('test-gateway-123');
    
    const response = await sync.syncApiKey('openai', 'sk-test-key');
    
    expect(response.success).toBe(true);
    expect(response.restartRequired).toBe(true);
    expect(response.appliedAt).toBeDefined();
  });

  it('should handle unauthorized gateway access', async () => {
    // Set up with invalid token
    const sync = new GatewayConfigSync('unauthorized-gateway');
    
    await expect(sync.syncApiKey('openai', 'sk-test'))
      .rejects.toThrow('Failed to sync API key');
  });
});

Test 3: End-to-End BYOK Flow


# E2E Test Script
#!/bin/bash

GATEWAY_ID="test-e2e-gateway"
PROVIDER="openai"
TEST_KEY="sk-test-$(date +%s)"

echo "=== BYOK End-to-End Test ==="

# 1. Store key locally
echo "1. Storing key in Keychain..."
# Client-side operation (pseudocode)
client.setApiKey --provider $PROVIDER --key $TEST_KEY

# 2. Sync to gateway
echo "2. Syncing to cloud gateway..."
curl -X POST "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/config/patch" \
  -H "Authorization: Bearer $GATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "set",
    "target": "secret",
    "key": "OPENAI_API_KEY",
    "value": "'"$TEST_KEY"'"
  }'

# 3. Verify gateway has key
echo "3. Verifying gateway configuration..."
RESPONSE=$(curl -s "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/config/status" \
  -H "Authorization: Bearer $GATEWAY_TOKEN")

echo $RESPONSE | jq '.secrets.OPENAI_API_KEY.configured'
# Expected: true

# 4. Test AI request uses user key
echo "4. Testing AI request with BYOK credentials..."
curl -X POST "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-4", "messages": [{"role": "user", "content": "test"}]}'

# Verify X-Billing-Mode header indicates BYOK
# Expected: X-Billing-Mode: byok

echo "=== Test Complete ==="

Expected Test Outputs

TestExpected Result
Key StorageKey retrievable, masked display shows correct format
Format ValidationInvalid keys rejected with descriptive error
Gateway Sync200 OK, restartRequired: true
AI RequestRequest succeeds with user-provided key

⚠️ Common Pitfalls

Security Pitfalls

  • Logging Secrets: Never log API keys, even partially. Ensure all logging statements redact sensitive values.
    // ❌ Incorrect
    logger.info(`Using API key: ${apiKey}`);
    

    // βœ… Correct logger.debug(Using API key for provider: ${provider});

  • Error Message Leakage: Validation errors may expose key structure. Mask keys in all error responses.
    // ❌ Incorrect
    throw new Error(`Invalid key: ${providedKey}`);
    

    // βœ… Correct throw new Error(Invalid key format. Key must match pattern for ${provider});

  • Memory Retention: API keys in memory should be cleared after use when possible. Consider using secure string patterns.

Platform-Specific Issues

PlatformIssueSolution
macOSKeychain access deniedRequest entitlement in provisioning profile
WindowsCredential Manager fallbackEnsure Electron’s safeStorage API is available
Linuxlibsecret unavailableImplement fallback with encrypted file storage
Mobile (iOS)Secure Enclave limitationsUse ASAuthorizationManager for keychain access
Mobile (Android)Keystore encryptionRequire API 23+ for hardware-backed storage

UX Pitfalls

  • Unclear Billing Responsibility: Users must understand they're billed directly by the AI provider. The billing disclaimer is mandatory.
  • No Key Rotation Warning: Warn users that changing API keys requires gateway restart.
  • Missing Key Expiration Handling: Some providers (Azure) have key expiration. Implement proactive warnings.

Configuration Pitfalls

// ❌ Pitfall: Overwriting bundled credentials
// If gateway has bundled credentials AND BYOK, which takes precedence?

// βœ… Solution: Explicit priority
const getEffectiveApiKey = (provider, byokKey, bundledKey) => {
  if (byokKey) {
    return { source: 'byok', key: byokKey };
  }
  if (bundledKey) {
    return { source: 'bundled', key: bundledKey };
  }
  throw new Error('No API key configured');
};
  • #221 - Local Gateway BYOK: Existing implementation of BYOK via setup wizard. Cloud gateway BYOK should share core components while handling cloud-specific security requirements.
  • Secret Rotation System: Planned enhancement for automated API key rotation (see roadmap).
  • Multi-Provider Fallback: Allow configuring multiple providers with automatic failover.
SettingDescriptionDefault
AI_PROVIDERActive AI provideropenai
USE_BUNDLED_CREDENTIALSFall back to bundled keystrue
BYOK_ENABLEDEnable BYOK feature flagtrue
KEYCHAIN_SERVICEKeychain service identifieropenclaw.byok

Future Considerations

  • Scoped Keys: Support for provider-scoped keys (e.g., Azure endpoint-specific keys) with improved validation.
  • Team BYOK: Enterprise feature for team-shared API key management with audit logging.
  • Cost Estimation: Integration with provider APIs to show usage estimates before requests.

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.