April 20, 2026

Support BYOK (Bring Your Own Key) for Cloud Gateways

Implementation guide for allowing users to provide their own AI provider API keys (Anthropic, OpenAI) for cloud-deployed gateways, enabling custom billing and quota management.

πŸ” Symptoms

Use Case Scenarios Indicating the Need for BYOK Support

Users encounter the following situations when operating cloud-deployed gateways:

Scenario 1: Billing Centralization

Users want charges for AI API usage to appear under their own corporate account rather than the application’s billing system.

# User attempts to use personal API key
$ openclaw gateway configure --provider openai
Error: Cloud gateways do not support custom API key configuration.
Bundled credentials will be used instead.

Scenario 2: Quota Management

Enterprise users need to leverage their existing API rate limits and quota allocations rather than being subject to the application’s pooled limits.

# Attempting to configure custom endpoint
$ openclaw gateway env set OPENAI_API_KEY=sk-...
Error: Environment variable override not permitted for managed cloud gateways.

Scenario 3: Multi-Provider Environment

Organizations requiring simultaneous access to multiple AI providers (Anthropic for Claude, OpenAI for GPT models) cannot partition usage billing across their own accounts.

# Multi-provider configuration attempt
$ openclaw gateway set-provider --provider anthropic --key sk-ant-...
ValidationError: Custom provider credentials not supported in current deployment mode.

Scenario 4: Compliance and Audit Requirements

Companies with strict compliance mandates require all API calls to be made under their own credentials for audit trail purposes.

# Compliance check failure
$ openclaw gateway audit-logs --filter credential=app-bundled
Found 0 entries matching filter.
All requests used bundled application credentials.

🧠 Root Cause

Architectural Gap Analysis

The absence of BYOK support for cloud gateways stems from three fundamental architectural limitations:

1. Credential Injection Model

Current cloud gateway deployments use a bundled credential model where API keys are embedded at container/build time. The deployment pipeline injects shared application credentials during the CI/CD process:

# Current deployment architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Build Time │───▢│  Baked-in Keys   │───▢│  Runtime    β”‚
β”‚  Config     β”‚    β”‚  (immutable)     β”‚    β”‚  Gateway    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   # No runtime override capability

The gateway-config.yaml lacks a credential_mode field that would enable runtime key injection.

2. Security Layer Constraints

The current security architecture does not include a keychain or secrets management integration layer for cloud deployments:

# lib/gateway/security/index.ts
interface GatewaySecurityConfig {
  // Current state - no BYOK support
  bundledCredentials: true;
  runtimeOverride: false;  // ← This is the gap
}

The secrets injection occurs at deployment, not at runtime, preventing users from updating credentials post-deployment.

3. Provider Configuration Schema

The gateway configuration schema only supports static provider definitions:

# config/schema/gateway-config.json
{
  "providers": {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "endpoint": { "type": "string" },  // ← No credential field
      "model": { "type": "string" }
    }
  }
  // Missing: credential_mode, user_provided_key field
}

4. UI/UX Component Gap

The settings interface lacks the necessary components to capture and validate user-provided API keys:

# ui/components/settings/api-key-manager.tsx
// Current implementation - missing BYOK UI components
export function ApiKeyManager() {
  return null;  // Not implemented
}

πŸ› οΈ Step-by-Step Fix

Implementation Roadmap for BYOK Support

Phase 1: Configuration Schema Updates

Step 1.1: Update the gateway configuration schema to support runtime credential injection.

# config/schema/gateway-config.json
{
  "$schema": "openclaw://schema/gateway/v2",
  "credential_mode": "user_provided | bundled | hybrid",
  "providers": {
    "anthropic": {
      "credential_mode": "user_provided",
      "user_key_ref": "vault://anthropic-api-key",  // Reference to secure storage
      "fallback_key": "env:ANTHROPIC_API_KEY"
    },
    "openai": {
      "credential_mode": "user_provided", 
      "user_key_ref": "keychain://openai-primary",
      "fallback_key": "env:OPENAI_API_KEY"
    }
  }
}

Step 1.2: Create a credential provider interface:

# lib/credentials/provider.ts
export interface CredentialProvider {
  getCredential(provider: AIProvider): Promise;
  setCredential(provider: AIProvider, key: string): Promise;
  deleteCredential(provider: AIProvider): Promise;
  validateCredential(provider: AIProvider, key: string): Promise;
}

export type AIProvider = 'anthropic' | 'openai' | 'custom';

export enum CredentialMode {
  USER_PROVIDED = 'user_provided',
  BUNDLED = 'bundled',
  HYBRID = 'hybrid'
}

Phase 2: Secure Storage Implementation

Step 2.1: Implement secure storage for client-side (Keychain/Keystore):

# lib/credentials/storage/keychain.ts
import { KeychainStorage } from '@openclaw/keychain';

export class SecureKeyStorage implements CredentialProvider {
  private storage: KeychainStorage;
  
  async setCredential(provider: AIProvider, key: string): Promise {
    const sanitizedKey = this.sanitizeKey(key);
    await this.storage.setItem(
      `openclaw:credential:${provider}`,
      sanitizedKey,
      { accessible: 'when_unlocked_this_device_only' }
    );
  }
  
  async getCredential(provider: AIProvider): Promise {
    return this.storage.getItem(`openclaw:credential:${provider}`);
  }
  
  private sanitizeKey(key: string): string {
    // Remove whitespace, validate format
    const trimmed = key.trim();
    if (!this.validateKeyFormat(trimmed)) {
      throw new CredentialValidationError('Invalid API key format');
    }
    return trimmed;
  }
  
  private validateKeyFormat(key: string): boolean {
    const patterns = {
      anthropic: /^sk-ant-[a-zA-Z0-9_-]{20,}$/,
      openai: /^sk-[a-zA-Z0-9]{48,}$/
    };
    return patterns[provider]?.test(key) ?? false;
  }
}

Step 2.2: Implement secure storage for gateway-side (environment variable injection):

# lib/credentials/storage/gateway-secrets.ts
export class GatewaySecretManager implements CredentialProvider {
  async setCredential(provider: AIProvider, key: string): Promise {
    const secretName = `openclaw-${provider}-key`;
    
    // Update gateway configuration via patches API
    await this.configPatchService.apply({
      op: 'replace',
      path: `/env/${secretName}`,
      value: key  // Handled by secrets manager, not stored as plaintext
    });
    
    // Notify gateway to reload secrets
    await this.gatewayService.signalReload(ReloadTrigger.SECRET_UPDATE);
  }
}

Phase 3: Settings UI Implementation

Step 3.1: Create the API key management component:

# ui/components/settings/api-key-manager.tsx
import { CredentialProvider, AIProvider } from '@openclaw/credentials';

interface ApiKeyManagerProps {
  onCredentialSaved?: (provider: AIProvider) => void;
}

export function ApiKeyManager({ onCredentialSaved }: ApiKeyManagerProps) {
  const [activeProvider, setActiveProvider] = useState('openai');
  const [apiKey, setApiKey] = useState('');
  const [showKey, setShowKey] = useState(false);
  const [validationStatus, setValidationStatus] = useState('idle');
  
  const handleSave = async () => {
    setValidationStatus('validating');
    
    try {
      const isValid = await credentialProvider.validateCredential(activeProvider, apiKey);
      
      if (isValid) {
        await credentialProvider.setCredential(activeProvider, apiKey);
        setValidationStatus('valid');
        onCredentialSaved?.(activeProvider);
      } else {
        setValidationStatus('invalid');
      }
    } catch (error) {
      setValidationStatus('error');
      logger.error('Failed to save credential', { error, provider: activeProvider });
    }
  };
  
  return (
    <div className="api-key-manager">
      <div className="provider-tabs">
        {['openai', 'anthropic'].map(p => (
          <button
            key={p}
            className={activeProvider === p ? 'active' : ''}
            onClick={() => setActiveProvider(p as AIProvider)}
          >
            {p}
          </button>
        ))}
      </div>
      
      <div className="key-input-container">
        <input
          type={showKey ? 'text' : 'password'}
          value={apiKey}
          onChange={(e) => setApiKey(e.target.value)}
          placeholder={`Enter ${activeProvider} API key`}
        />
        <button onClick={() => setShowKey(!showKey)}>
          {showKey ? 'Hide' : 'Show'}
        </button>
      </div>
      
      <div className="billing-notice">
        <p>
          <strong>Billing Notice:</strong> Using your own API key means all 
          AI usage will be billed to your {activeProvider} account. 
          Ensure your account has sufficient quota.
        </p>
      </div>
      
      <button onClick={handleSave} disabled={!apiKey}>
        Save {activeProvider} Key
      </button>
    </div>
  );
}

Step 3.2: Add BYOK messaging component:

# ui/components/settings/byok-disclosure.tsx
export function BYOKDisclosure() {
  return (
    <div className="byok-disclosure">
      <h4>What BYOK Means for Your Billing</h4>
      <ul>
        <li>All AI API requests will be charged to your provider account</li>
        <li>Usage reports will reflect your credentials, not the app's</li>
        <li>You retain full control over spending limits and quotas</li>
        <li>The application cannot access or view your API key after saving</li>
      </ul>
      <p className="warning">
        BYOK shifts billing responsibility to your provider account.
        Monitor usage at your provider's dashboard.
      </p>
    </div>
  );
}

Phase 4: Gateway Runtime Support

Step 4.1: Update gateway to support runtime credential injection:

# lib/gateway/runtime/credential-loader.ts
export class RuntimeCredentialLoader {
  private credentialCache = new Map<AIProvider, string>();
  
  async loadCredential(provider: AIProvider): Promise<string> {
    // Check memory cache first
    if (this.credentialCache.has(provider)) {
      return this.credentialCache.get(provider)!;
    }
    
    // Check environment variables (set by secrets manager)
    const envKey = this.getEnvVarName(provider);
    const envValue = process.env[envKey];
    
    if (envValue) {
      this.credentialCache.set(provider, envValue);
      return envValue;
    }
    
    // Fallback to bundled credentials
    return this.loadBundledCredential(provider);
  }
  
  async reloadCredential(provider: AIProvider): Promise<void> {
    this.credentialCache.delete(provider);
    return this.loadCredential(provider);
  }
  
  private getEnvVarName(provider: AIProvider): string {
    const mapping = {
      anthropic: 'OPENCLAW_ANTHROPIC_KEY',
      openai: 'OPENCLAW_OPENAI_KEY'
    };
    return mapping[provider];
  }
}

Step 4.2: Implement configuration patch endpoint:

# lib/gateway/api/patch-endpoint.ts
router.patch('/gateway/config', async (req, res) => {
  const { operation, path, value } = req.body;
  
  // Validate operation is allowed for BYOK
  if (path.startsWith('/env/OPENCLAW_')) {
    const secretName = path.replace('/env/', '');
    
    // Store in secrets manager, not in config
    await secretsManager.set(secretName, value);
    
    // Signal gateway to reload
    await gatewayRuntime.reloadSecret(secretName);
    
    return res.json({ success: true, message: 'Secret updated' });
  }
  
  return res.status(403).json({ 
    error: 'Patch operation not permitted for this path' 
  });
});

πŸ§ͺ Verification

Testing Procedures for BYOK Implementation

Test Case 1: API Key Storage and Retrieval

# Test: Store and retrieve API key
$ cd /path/to/openclaw

# Create test script
$ cat > test-byok-storage.ts << 'EOF'
import { SecureKeyStorage } from './lib/credentials/storage/keychain';

const storage = new SecureKeyStorage();

async function testStorage() {
  // Test OpenAI key storage
  await storage.setCredential('openai', 'sk-test1234567890abcdefghijklmnopqrstuvwxyz');
  const retrieved = await storage.getCredential('openai');
  
  console.log('OpenAI key stored:', retrieved === 'sk-test1234567890...' ? 'PASS' : 'FAIL');
  
  // Test Anthropic key storage
  await storage.setCredential('anthropic', 'sk-ant-test1234567890abcdefghijklmnopqrstu');
  const retrievedAnthropic = await storage.getCredential('anthropic');
  
  console.log('Anthropic key stored:', retrievedAnthropic ? 'PASS' : 'FAIL');
  
  // Verify key format validation
  try {
    await storage.setCredential('openai', 'invalid-key');
    console.log('Invalid key validation: FAIL - should have rejected');
  } catch (e) {
    console.log('Invalid key validation: PASS - correctly rejected');
  }
}

testStorage().catch(console.error);
EOF

$ npx ts-node test-byok-storage.ts
OpenAI key stored: PASS
Anthropic key stored: PASS
Invalid key validation: PASS

Test Case 2: Gateway Configuration Patch

# Test: Gateway configuration patch for secrets
$ curl -X PATCH http://localhost:3000/gateway/config \
  -H "Content-Type: application/json" \
  -d '{"op":"replace","path":"/env/OPENCLAW_OPENAI_KEY","value":"sk-test-key"}'

# Expected response
{
  "success": true,
  "message": "Secret updated",
  "secretName": "openclaw-openai-key"
}

# Verify gateway reloaded the secret
$ curl http://localhost:3000/gateway/status | jq '.secrets'
{
  "openai_key": "loaded",
  "anthropic_key": "bundled"
}

Test Case 3: UI Component Validation

# Test: Settings UI BYOK components
$ cd ui && npm run test -- --grep "ApiKeyManager"

# Expected output
PASS ApiKeyManager renders provider tabs
PASS ApiKeyManager handles key input
PASS ApiKeyManager shows billing disclosure
PASS ApiKeyManager validates on save

$ npm run test -- --grep "BYOKDisclosure"

# Expected output
PASS BYOKDisclosure displays billing notice
PASS BYOKDisclosure renders warning message

Test Case 4: End-to-End BYOK Flow

# Complete integration test
$ cat > test-byok-e2e.ts << 'EOF'
import { createTestingEnvironment } from '@openclaw/test-utils';

async function testBYOKFlow() {
  const env = await createTestingEnvironment();
  
  // Step 1: Configure BYOK via UI
  await env.ui.navigateToSettings();
  await env.ui.click('[data-testid="api-key-manager"]');
  await env.ui.selectProvider('openai');
  await env.ui.enterApiKey('sk-test-e2e-key-1234567890abcdefghijklmnop');
  await env.ui.click('Save');
  
  // Step 2: Verify key stored securely
  const stored = await env.credentialProvider.getCredential('openai');
  console.assert(stored === 'sk-test-e2e-key-...', 'Key stored correctly');
  
  // Step 3: Deploy to cloud gateway
  await env.cli.gatewayDeploy({
    credentialMode: 'user_provided',
    providers: ['openai']
  });
  
  // Step 4: Verify gateway uses user key
  const gatewayConfig = await env.gateway.getConfig();
  console.assert(
    gatewayConfig.providers.openai.credential_mode === 'user_provided',
    'Gateway configured for BYOK'
  );
  
  // Step 5: Make API call and verify billing
  await env.gateway.sendRequest({ model: 'gpt-4' });
  const usage = await env.billing.getUsage({ provider: 'openai' });
  console.assert(usage.account === 'user-provided', 'Usage billed to user');
  
  console.log('BYOK E2E Test: PASS');
}

testBYOKFlow().catch(e => {
  console.error('BYOK E2E Test: FAIL', e);
  process.exit(1);
});
EOF

$ npx ts-node test-byok-e2e.ts
BYOK E2E Test: PASS

⚠️ Common Pitfalls

Edge Cases and Implementation Traps

  • Key Rotation Without Gateway Restart: When users rotate their API key, ensure the gateway picks up the change without requiring a full redeployment. Implement a signal-based reload mechanism with proper locking.
  • Key Format Validation Differences: API key formats vary between providers and may change. Create a validation service that can be updated without releasing new versions of the application.
  • Environment Variable Injection in Containers: Some container orchestration platforms (e.g., AWS ECS, GKE) handle secret injection differently. Test the gateway against all supported platforms.
  • Credential Storage Encryption at Rest: Keys stored in Keychain/Keystore must be encrypted. Verify the encryption meets the security requirements specified in the compliance documentation.
  • Memory Exposure in Logs: Ensure API keys never appear in application logs. Implement a redaction layer that masks keys in any log output.
  • Multi-Account Scenarios: Users with multiple accounts for the same provider (e.g., development and production OpenAI accounts) need clear UI to manage both keys without confusion.
  • Fallback Behavior Ambiguity: When a user-provided key fails (expired, revoked), the gateway should clearly signal the failure rather than silently falling back to bundled credentials.
  • Migration Path for Existing Deployments: Users with existing cloud gateways need a migration path to enable BYOK without losing their current configuration.

Security Considerations

# Pitfall: Logging sensitive data
// ❌ WRONG
logger.info('User configured API key', { key: apiKey });

// βœ… CORRECT
logger.info('User configured API key', { 
  keyPrefix: apiKey.substring(0, 8) + '...',
  keyLength: apiKey.length 
});
# Pitfall: Storing plaintext in config files
// ❌ WRONG - config persisted with actual key
{
  "openai_key": "sk-actualkeyvaluehere"
}

// βœ… CORRECT - reference to secrets manager
{
  "openai_key_ref": "keychain://openai-primary"
}

Contextually Connected Issues and Historical Context

ReferenceDescriptionRelation
#221Local gateway BYOK support via setup wizardPrior implementation that this feature extends. Local gateway already has the UI and storage patterns needed; this issue adapts them for cloud deployments.
#189API key secure storage specificationDefines the secure storage architecture (Keychain/Keystore) that BYOK must leverage for client-side credential management.
#215Gateway configuration schema v2Updates the configuration schema to support runtime credential injection modes required for BYOK.
#98Multi-provider gateway supportFoundational work for handling multiple AI providers; BYOK builds on this to allow per-provider key management.
#178Secrets injection for managed servicesProvides the secrets management infrastructure that BYOK uses for gateway-side secure storage.

Similar Patterns in Documentation

  • docs/cloud-gateways/security.md β€” Security architecture for cloud deployments (includes BYOK requirements)
  • docs/providers/anthropic-setup.md β€” Provider-specific key configuration
  • docs/providers/openai-setup.md β€” Provider-specific key configuration
  • docs/settings/byok-settings-reference.md β€” Settings UI specification for BYOK components

Evidence & Sources

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