Skip to content
Open
Next Next commit
feat: add ANTHROPIC_BASE_URL and USE_SERVER_KEYS support
- ANTHROPIC_BASE_URL: allows custom Anthropic API proxy endpoint
- USE_SERVER_KEYS / NEXT_PUBLIC_USE_SERVER_KEYS: enables server-configured
  rotating API keys for self-hosted deployments, removing the need for
  users to enter API keys in the UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  • Loading branch information
wickedev and claude committed Feb 8, 2026
commit f907d400b0b78518f0efd774783f834dbfaaac85
4 changes: 2 additions & 2 deletions apps/sim/blocks/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isHosted } from '@/lib/core/config/feature-flags'
import { isHosted, isServerKeysEnabled } from '@/lib/core/config/feature-flags'
import type { BlockOutput, OutputFieldDefinition, SubBlockConfig } from '@/blocks/types'
import { getHostedModels, getProviderFromModel, providers } from '@/providers/utils'
import { useProvidersStore } from '@/stores/providers/store'
Expand Down Expand Up @@ -64,7 +64,7 @@ function shouldRequireApiKeyForModel(model: string): boolean {
const isHostedModel = hostedModels.some(
(hostedModel) => hostedModel.toLowerCase() === normalizedModel
)
if (isHosted && isHostedModel) return false
if ((isHosted || isServerKeysEnabled) && isHostedModel) return false

if (normalizedModel.startsWith('vertex/') || normalizedModel.startsWith('bedrock/')) {
return false
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/api-key/byok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { workspaceBYOKKeys } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { getRotatingApiKey } from '@/lib/core/config/api-keys'
import { isHosted } from '@/lib/core/config/feature-flags'
import { isHosted, isServerKeysEnabled } from '@/lib/core/config/feature-flags'
import { decryptSecret } from '@/lib/core/security/encryption'
import { getHostedModels } from '@/providers/models'
import { useProvidersStore } from '@/stores/providers/store'
Expand Down Expand Up @@ -80,7 +80,7 @@ export async function getApiKeyWithBYOK(
const byokProviderId = isGeminiModel ? 'google' : (provider as BYOKProviderId)

if (
isHosted &&
(isHosted || isServerKeysEnabled) &&
workspaceId &&
(isOpenAIModel || isClaudeModel || isGeminiModel || isMistralModel)
) {
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/lib/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const env = createEnv({
GEMINI_API_KEY_2: z.string().min(1).optional(), // Additional Gemini API key for load balancing
GEMINI_API_KEY_3: z.string().min(1).optional(), // Additional Gemini API key for load balancing
OLLAMA_URL: z.string().url().optional(), // Ollama local LLM server URL
ANTHROPIC_BASE_URL: z.string().url().optional(), // Custom Anthropic API base URL (e.g., for proxy endpoints)
VLLM_BASE_URL: z.string().url().optional(), // vLLM self-hosted base URL (OpenAI-compatible)
VLLM_API_KEY: z.string().optional(), // Optional bearer token for vLLM
ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for text-to-speech in deployed chat
Expand Down Expand Up @@ -292,6 +293,9 @@ export const env = createEnv({
// Invitations - for self-hosted deployments
DISABLE_INVITATIONS: z.boolean().optional(), // Disable workspace invitations globally (for self-hosted deployments)

// Server-managed API Keys (for self-hosted deployments)
USE_SERVER_KEYS: z.boolean().optional(), // Use server-configured rotating API keys for all users (self-hosted)

// Development Tools
REACT_GRAB_ENABLED: z.boolean().optional(), // Enable React Grab for UI element debugging in Cursor/AI agents (dev only)
REACT_SCAN_ENABLED: z.boolean().optional(), // Enable React Scan for performance debugging (dev only)
Expand Down Expand Up @@ -356,6 +360,7 @@ export const env = createEnv({
NEXT_PUBLIC_CUSTOM_CSS_URL: z.string().url().optional(), // Custom CSS stylesheet URL
NEXT_PUBLIC_SUPPORT_EMAIL: z.string().email().optional(), // Custom support email

NEXT_PUBLIC_USE_SERVER_KEYS: z.boolean().optional(), // Client-side flag to hide API key fields when server keys are enabled
NEXT_PUBLIC_E2B_ENABLED: z.string().optional(),
NEXT_PUBLIC_COPILOT_TRAINING_ENABLED: z.string().optional(),
NEXT_PUBLIC_ENABLE_PLAYGROUND: z.string().optional(), // Enable component playground at /playground
Expand Down Expand Up @@ -408,6 +413,7 @@ export const env = createEnv({
NEXT_PUBLIC_ORGANIZATIONS_ENABLED: process.env.NEXT_PUBLIC_ORGANIZATIONS_ENABLED,
NEXT_PUBLIC_DISABLE_INVITATIONS: process.env.NEXT_PUBLIC_DISABLE_INVITATIONS,
NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED: process.env.NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED,
NEXT_PUBLIC_USE_SERVER_KEYS: process.env.NEXT_PUBLIC_USE_SERVER_KEYS,
NEXT_PUBLIC_E2B_ENABLED: process.env.NEXT_PUBLIC_E2B_ENABLED,
NEXT_PUBLIC_COPILOT_TRAINING_ENABLED: process.env.NEXT_PUBLIC_COPILOT_TRAINING_ENABLED,
NEXT_PUBLIC_ENABLE_PLAYGROUND: process.env.NEXT_PUBLIC_ENABLE_PLAYGROUND,
Expand Down
9 changes: 9 additions & 0 deletions apps/sim/lib/core/config/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ export const isHosted =
getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.sim.ai' ||
getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.staging.sim.ai'

/**
* Are server-managed API keys enabled for self-hosted deployments.
* When true, server-configured rotating API keys (e.g., ANTHROPIC_API_KEY_1)
* are used for all users without requiring UI input.
* This flag is blocked when isHosted is true (hosted environment manages its own keys).
*/
export const isServerKeysEnabled =
(isTruthy(env.USE_SERVER_KEYS) || isTruthy(getEnv('NEXT_PUBLIC_USE_SERVER_KEYS'))) && !isHosted

/**
* Is billing enforcement enabled
*/
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/providers/anthropic/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Anthropic from '@anthropic-ai/sdk'
import { createLogger } from '@sim/logger'
import type { StreamingExecution } from '@/executor/types'
import { env } from '@/lib/core/config/env'
import { executeAnthropicProviderRequest } from '@/providers/anthropic/core'
import { getProviderDefaultModel, getProviderModels } from '@/providers/models'
import type { ProviderConfig, ProviderRequest, ProviderResponse } from '@/providers/types'
Expand All @@ -24,6 +25,7 @@ export const anthropicProvider: ProviderConfig = {
createClient: (apiKey, useNativeStructuredOutputs) =>
new Anthropic({
apiKey,
baseURL: env.ANTHROPIC_BASE_URL?.replace(/\/$/, '') || undefined,
defaultHeaders: useNativeStructuredOutputs
? { 'anthropic-beta': 'structured-outputs-2025-11-13' }
: undefined,
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/providers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type OpenAI from 'openai'
import type { ChatCompletionChunk } from 'openai/resources/chat/completions'
import type { CompletionUsage } from 'openai/resources/completions'
import { env } from '@/lib/core/config/env'
import { isHosted } from '@/lib/core/config/feature-flags'
import { isHosted, isServerKeysEnabled } from '@/lib/core/config/feature-flags'
import { isCustomTool } from '@/executor/constants'
import {
getComputerUseModels,
Expand Down Expand Up @@ -663,7 +663,7 @@ export function getApiKey(provider: string, model: string, userProvidedKey?: str
const isClaudeModel = provider === 'anthropic'
const isGeminiModel = provider === 'google'

if (isHosted && (isOpenAIModel || isClaudeModel || isGeminiModel)) {
if ((isHosted || isServerKeysEnabled) && (isOpenAIModel || isClaudeModel || isGeminiModel)) {
const hostedModels = getHostedModels()
const isModelHosted = hostedModels.some((m) => m.toLowerCase() === model.toLowerCase())

Expand Down