diff --git a/apps/sim/.env.example b/apps/sim/.env.example index f8e926f88..6c22b09ee 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -13,6 +13,7 @@ BETTER_AUTH_URL=http://localhost:3000 # NextJS (Required) NEXT_PUBLIC_APP_URL=http://localhost:3000 +# INTERNAL_API_BASE_URL=http://sim-app.default.svc.cluster.local:3000 # Optional: internal URL for server-side /api self-calls; defaults to NEXT_PUBLIC_APP_URL # Security (Required) ENCRYPTION_KEY=your_encryption_key # Use `openssl rand -hex 32` to generate, used to encrypt environment variables diff --git a/apps/sim/app/api/a2a/serve/[agentId]/utils.ts b/apps/sim/app/api/a2a/serve/[agentId]/utils.ts index 1e8f85588..f46013343 100644 --- a/apps/sim/app/api/a2a/serve/[agentId]/utils.ts +++ b/apps/sim/app/api/a2a/serve/[agentId]/utils.ts @@ -1,7 +1,7 @@ import type { Artifact, Message, PushNotificationConfig, Task, TaskState } from '@a2a-js/sdk' import { v4 as uuidv4 } from 'uuid' import { generateInternalToken } from '@/lib/auth/internal' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' /** A2A v0.3 JSON-RPC method names */ export const A2A_METHODS = { @@ -118,7 +118,7 @@ export interface ExecuteRequestResult { export async function buildExecuteRequest( config: ExecuteRequestConfig ): Promise { - const url = `${getBaseUrl()}/api/workflows/${config.workflowId}/execute` + const url = `${getInternalApiBaseUrl()}/api/workflows/${config.workflowId}/execute` const headers: Record = { 'Content-Type': 'application/json' } let useInternalAuth = false diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts index 7193af66d..aa464170a 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts @@ -18,9 +18,9 @@ describe('Copilot Checkpoints Revert API Route', () => { setupCommonApiMocks() mockCryptoUuid() - // Mock getBaseUrl to return localhost for tests vi.doMock('@/lib/core/utils/urls', () => ({ getBaseUrl: vi.fn(() => 'http://localhost:3000'), + getInternalApiBaseUrl: vi.fn(() => 'http://localhost:3000'), getBaseDomain: vi.fn(() => 'localhost:3000'), getEmailDomain: vi.fn(() => 'localhost:3000'), })) diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.ts index 72c79a262..7c58a1435 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.ts @@ -11,7 +11,7 @@ import { createRequestTracker, createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils' import { isUuidV4 } from '@/executor/constants' @@ -99,7 +99,7 @@ export async function POST(request: NextRequest) { } const stateResponse = await fetch( - `${getBaseUrl()}/api/workflows/${checkpoint.workflowId}/state`, + `${getInternalApiBaseUrl()}/api/workflows/${checkpoint.workflowId}/state`, { method: 'PUT', headers: { diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts index 976678313..95a3f89ed 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts @@ -72,6 +72,7 @@ describe('MCP Serve Route', () => { })) vi.doMock('@/lib/core/utils/urls', () => ({ getBaseUrl: () => 'http://localhost:3000', + getInternalApiBaseUrl: () => 'http://localhost:3000', })) vi.doMock('@/lib/core/execution-limits', () => ({ getMaxExecutionTimeout: () => 10_000, diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.ts index 92a77d8b9..1c694a59a 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.ts @@ -22,7 +22,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { type AuthResult, checkHybridAuth } from '@/lib/auth/hybrid' import { generateInternalToken } from '@/lib/auth/internal' import { getMaxExecutionTimeout } from '@/lib/core/execution-limits' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkflowMcpServeAPI') @@ -285,7 +285,7 @@ async function handleToolsCall( ) } - const executeUrl = `${getBaseUrl()}/api/workflows/${tool.workflowId}/execute` + const executeUrl = `${getInternalApiBaseUrl()}/api/workflows/${tool.workflowId}/execute` const headers: Record = { 'Content-Type': 'application/json' } if (publicServerOwnerId) { diff --git a/apps/sim/app/api/templates/[id]/use/route.ts b/apps/sim/app/api/templates/[id]/use/route.ts index 59c546687..b08d6dfb8 100644 --- a/apps/sim/app/api/templates/[id]/use/route.ts +++ b/apps/sim/app/api/templates/[id]/use/route.ts @@ -6,7 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { type RegenerateStateInput, regenerateWorkflowStateIds, @@ -115,15 +115,18 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ // Step 3: Save the workflow state using the existing state endpoint (like imports do) // Ensure variables in state are remapped for the new workflow as well const workflowStateWithVariables = { ...workflowState, variables: remappedVariables } - const stateResponse = await fetch(`${getBaseUrl()}/api/workflows/${newWorkflowId}/state`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - // Forward the session cookie for authentication - cookie: request.headers.get('cookie') || '', - }, - body: JSON.stringify(workflowStateWithVariables), - }) + const stateResponse = await fetch( + `${getInternalApiBaseUrl()}/api/workflows/${newWorkflowId}/state`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + // Forward the session cookie for authentication + cookie: request.headers.get('cookie') || '', + }, + body: JSON.stringify(workflowStateWithVariables), + } + ) if (!stateResponse.ok) { logger.error(`[${requestId}] Failed to save workflow state for template use`) diff --git a/apps/sim/executor/handlers/router/router-handler.ts b/apps/sim/executor/handlers/router/router-handler.ts index a42956c66..5c22a1c49 100644 --- a/apps/sim/executor/handlers/router/router-handler.ts +++ b/apps/sim/executor/handlers/router/router-handler.ts @@ -2,7 +2,7 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { generateRouterPrompt, generateRouterV2Prompt } from '@/blocks/blocks/router' import type { BlockOutput } from '@/blocks/types' @@ -79,7 +79,7 @@ export class RouterBlockHandler implements BlockHandler { const providerId = getProviderFromModel(routerConfig.model) try { - const url = new URL('/api/providers', getBaseUrl()) + const url = new URL('/api/providers', getInternalApiBaseUrl()) if (ctx.userId) url.searchParams.set('userId', ctx.userId) const messages = [{ role: 'user', content: routerConfig.prompt }] @@ -209,7 +209,7 @@ export class RouterBlockHandler implements BlockHandler { const providerId = getProviderFromModel(routerConfig.model) try { - const url = new URL('/api/providers', getBaseUrl()) + const url = new URL('/api/providers', getInternalApiBaseUrl()) if (ctx.userId) url.searchParams.set('userId', ctx.userId) const messages = [{ role: 'user', content: routerConfig.context }] diff --git a/apps/sim/executor/utils/http.ts b/apps/sim/executor/utils/http.ts index 5562e4567..ac4792dd7 100644 --- a/apps/sim/executor/utils/http.ts +++ b/apps/sim/executor/utils/http.ts @@ -1,5 +1,5 @@ import { generateInternalToken } from '@/lib/auth/internal' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getBaseUrl, getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { HTTP } from '@/executor/constants' export async function buildAuthHeaders(): Promise> { @@ -16,7 +16,8 @@ export async function buildAuthHeaders(): Promise> { } export function buildAPIUrl(path: string, params?: Record): URL { - const url = new URL(path, getBaseUrl()) + const baseUrl = path.startsWith('/api/') ? getInternalApiBaseUrl() : getBaseUrl() + const url = new URL(path, baseUrl) if (params) { for (const [key, value] of Object.entries(params)) { diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 31c9c36ad..b154fbdbb 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -220,6 +220,7 @@ export const env = createEnv({ SOCKET_SERVER_URL: z.string().url().optional(), // WebSocket server URL for real-time features SOCKET_PORT: z.number().optional(), // Port for WebSocket server PORT: z.number().optional(), // Main application port + INTERNAL_API_BASE_URL: z.string().optional(), // Optional internal base URL for server-side self-calls; must include protocol if set (e.g., http://sim-app.namespace.svc.cluster.local:3000) ALLOWED_ORIGINS: z.string().optional(), // CORS allowed origins // OAuth Integration Credentials - All optional, enables third-party integrations diff --git a/apps/sim/lib/core/utils/urls.ts b/apps/sim/lib/core/utils/urls.ts index 5021d4494..5be78eb1d 100644 --- a/apps/sim/lib/core/utils/urls.ts +++ b/apps/sim/lib/core/utils/urls.ts @@ -1,6 +1,19 @@ import { getEnv } from '@/lib/core/config/env' import { isProd } from '@/lib/core/config/feature-flags' +function hasHttpProtocol(url: string): boolean { + return /^https?:\/\//i.test(url) +} + +function normalizeBaseUrl(url: string): string { + if (hasHttpProtocol(url)) { + return url + } + + const protocol = isProd ? 'https://' : 'http://' + return `${protocol}${url}` +} + /** * Returns the base URL of the application from NEXT_PUBLIC_APP_URL * This ensures webhooks, callbacks, and other integrations always use the correct public URL @@ -8,7 +21,7 @@ import { isProd } from '@/lib/core/config/feature-flags' * @throws Error if NEXT_PUBLIC_APP_URL is not configured */ export function getBaseUrl(): string { - const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') + const baseUrl = getEnv('NEXT_PUBLIC_APP_URL')?.trim() if (!baseUrl) { throw new Error( @@ -16,12 +29,26 @@ export function getBaseUrl(): string { ) } - if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) { - return baseUrl + return normalizeBaseUrl(baseUrl) +} + +/** + * Returns the base URL used by server-side internal API calls. + * Falls back to NEXT_PUBLIC_APP_URL when INTERNAL_API_BASE_URL is not set. + */ +export function getInternalApiBaseUrl(): string { + const internalBaseUrl = getEnv('INTERNAL_API_BASE_URL')?.trim() + if (!internalBaseUrl) { + return getBaseUrl() } - const protocol = isProd ? 'https://' : 'http://' - return `${protocol}${baseUrl}` + if (!hasHttpProtocol(internalBaseUrl)) { + throw new Error( + 'INTERNAL_API_BASE_URL must include protocol (http:// or https://), e.g. http://sim-app.default.svc.cluster.local:3000' + ) + } + + return internalBaseUrl } /** diff --git a/apps/sim/lib/guardrails/validate_hallucination.ts b/apps/sim/lib/guardrails/validate_hallucination.ts index 621a7d803..658a528fc 100644 --- a/apps/sim/lib/guardrails/validate_hallucination.ts +++ b/apps/sim/lib/guardrails/validate_hallucination.ts @@ -2,7 +2,7 @@ import { db } from '@sim/db' import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { executeProviderRequest } from '@/providers' import { getProviderFromModel } from '@/providers/utils' @@ -61,7 +61,7 @@ async function queryKnowledgeBase( }) // Call the knowledge base search API directly - const searchUrl = `${getBaseUrl()}/api/knowledge/search` + const searchUrl = `${getInternalApiBaseUrl()}/api/knowledge/search` const response = await fetch(searchUrl, { method: 'POST', diff --git a/apps/sim/lib/knowledge/documents/document-processor.ts b/apps/sim/lib/knowledge/documents/document-processor.ts index 80789e81b..0185de495 100644 --- a/apps/sim/lib/knowledge/documents/document-processor.ts +++ b/apps/sim/lib/knowledge/documents/document-processor.ts @@ -539,8 +539,8 @@ async function executeMistralOCRRequest( const isInternalRoute = url.startsWith('/') if (isInternalRoute) { - const { getBaseUrl } = await import('@/lib/core/utils/urls') - url = `${getBaseUrl()}${url}` + const { getInternalApiBaseUrl } = await import('@/lib/core/utils/urls') + url = `${getInternalApiBaseUrl()}${url}` } let headers = diff --git a/apps/sim/lib/webhooks/gmail-polling-service.ts b/apps/sim/lib/webhooks/gmail-polling-service.ts index 7e3bcca5d..9b391002e 100644 --- a/apps/sim/lib/webhooks/gmail-polling-service.ts +++ b/apps/sim/lib/webhooks/gmail-polling-service.ts @@ -11,7 +11,7 @@ import { and, eq, isNull, or, sql } from 'drizzle-orm' import { nanoid } from 'nanoid' import { isOrganizationOnTeamOrEnterprisePlan } from '@/lib/billing' import { pollingIdempotency } from '@/lib/core/idempotency/service' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { getOAuthToken, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { GmailAttachment } from '@/tools/gmail/types' import { downloadAttachments, extractAttachmentInfo } from '@/tools/gmail/utils' @@ -691,7 +691,7 @@ async function processEmails( `[${requestId}] Sending ${config.includeRawEmail ? 'simplified + raw' : 'simplified'} email payload for ${email.id}` ) - const webhookUrl = `${getBaseUrl()}/api/webhooks/trigger/${webhookData.path}` + const webhookUrl = `${getInternalApiBaseUrl()}/api/webhooks/trigger/${webhookData.path}` const response = await fetch(webhookUrl, { method: 'POST', diff --git a/apps/sim/lib/webhooks/imap-polling-service.ts b/apps/sim/lib/webhooks/imap-polling-service.ts index 9d664531f..37fc4c621 100644 --- a/apps/sim/lib/webhooks/imap-polling-service.ts +++ b/apps/sim/lib/webhooks/imap-polling-service.ts @@ -7,7 +7,7 @@ import type { FetchMessageObject, MailboxLockObject } from 'imapflow' import { ImapFlow } from 'imapflow' import { nanoid } from 'nanoid' import { pollingIdempotency } from '@/lib/core/idempotency/service' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { MAX_CONSECUTIVE_FAILURES } from '@/triggers/constants' const logger = createLogger('ImapPollingService') @@ -639,7 +639,7 @@ async function processEmails( timestamp: new Date().toISOString(), } - const webhookUrl = `${getBaseUrl()}/api/webhooks/trigger/${webhookData.path}` + const webhookUrl = `${getInternalApiBaseUrl()}/api/webhooks/trigger/${webhookData.path}` const response = await fetch(webhookUrl, { method: 'POST', diff --git a/apps/sim/lib/webhooks/outlook-polling-service.ts b/apps/sim/lib/webhooks/outlook-polling-service.ts index 1f1b48e0c..19a807928 100644 --- a/apps/sim/lib/webhooks/outlook-polling-service.ts +++ b/apps/sim/lib/webhooks/outlook-polling-service.ts @@ -12,7 +12,7 @@ import { htmlToText } from 'html-to-text' import { nanoid } from 'nanoid' import { isOrganizationOnTeamOrEnterprisePlan } from '@/lib/billing' import { pollingIdempotency } from '@/lib/core/idempotency' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { getOAuthToken, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { MAX_CONSECUTIVE_FAILURES } from '@/triggers/constants' @@ -601,7 +601,7 @@ async function processOutlookEmails( `[${requestId}] Processing email: ${email.subject} from ${email.from?.emailAddress?.address}` ) - const webhookUrl = `${getBaseUrl()}/api/webhooks/trigger/${webhookData.path}` + const webhookUrl = `${getInternalApiBaseUrl()}/api/webhooks/trigger/${webhookData.path}` const response = await fetch(webhookUrl, { method: 'POST', diff --git a/apps/sim/lib/webhooks/rss-polling-service.ts b/apps/sim/lib/webhooks/rss-polling-service.ts index 5fbdeaba3..d75daa62f 100644 --- a/apps/sim/lib/webhooks/rss-polling-service.ts +++ b/apps/sim/lib/webhooks/rss-polling-service.ts @@ -9,7 +9,7 @@ import { secureFetchWithPinnedIP, validateUrlWithDNS, } from '@/lib/core/security/input-validation.server' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { MAX_CONSECUTIVE_FAILURES } from '@/triggers/constants' const logger = createLogger('RssPollingService') @@ -376,7 +376,7 @@ async function processRssItems( timestamp: new Date().toISOString(), } - const webhookUrl = `${getBaseUrl()}/api/webhooks/trigger/${webhookData.path}` + const webhookUrl = `${getInternalApiBaseUrl()}/api/webhooks/trigger/${webhookData.path}` const response = await fetch(webhookUrl, { method: 'POST', diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index 040a40a27..9af514aeb 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -6,7 +6,7 @@ import { validateUrlWithDNS, } from '@/lib/core/security/input-validation.server' import { generateRequestId } from '@/lib/core/utils/request' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getBaseUrl, getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { parseMcpToolId } from '@/lib/mcp/utils' import { isCustomTool, isMcpTool } from '@/executor/constants' import { resolveSkillContent } from '@/executor/handlers/agent/skills-resolver' @@ -285,7 +285,7 @@ export async function executeTool( `[${requestId}] Tool ${toolId} needs access token for credential: ${contextParams.credential}` ) try { - const baseUrl = getBaseUrl() + const baseUrl = getInternalApiBaseUrl() const workflowId = contextParams._context?.workflowId const userId = contextParams._context?.userId @@ -597,12 +597,12 @@ async function executeToolRequest( const requestParams = formatRequestParams(tool, params) try { - const baseUrl = getBaseUrl() const endpointUrl = typeof tool.request.url === 'function' ? tool.request.url(params) : tool.request.url + const isInternalRoute = endpointUrl.startsWith('/api/') + const baseUrl = isInternalRoute ? getInternalApiBaseUrl() : getBaseUrl() const fullUrlObj = new URL(endpointUrl, baseUrl) - const isInternalRoute = endpointUrl.startsWith('/api/') if (isInternalRoute) { const workflowId = params._context?.workflowId @@ -922,7 +922,7 @@ async function executeMcpTool( const { serverId, toolName } = parseMcpToolId(toolId) - const baseUrl = getBaseUrl() + const baseUrl = getInternalApiBaseUrl() const headers: Record = { 'Content-Type': 'application/json' } diff --git a/apps/sim/tools/openai/image.ts b/apps/sim/tools/openai/image.ts index 3d9f1be5a..2e857d153 100644 --- a/apps/sim/tools/openai/image.ts +++ b/apps/sim/tools/openai/image.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import type { BaseImageRequestBody } from '@/tools/openai/types' import type { ToolConfig } from '@/tools/types' @@ -122,7 +122,7 @@ export const imageTool: ToolConfig = { if (imageUrl && !base64Image) { try { logger.info('Fetching image from URL via proxy...') - const baseUrl = getBaseUrl() + const baseUrl = getInternalApiBaseUrl() const proxyUrl = new URL('/api/tools/image', baseUrl) proxyUrl.searchParams.append('url', imageUrl) diff --git a/apps/sim/tools/utils.ts b/apps/sim/tools/utils.ts index 0a7b635fa..e783217a6 100644 --- a/apps/sim/tools/utils.ts +++ b/apps/sim/tools/utils.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { getMaxExecutionTimeout } from '@/lib/core/execution-limits' -import { getBaseUrl } from '@/lib/core/utils/urls' +import { getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { AGENT, isCustomTool } from '@/executor/constants' import { getCustomTool } from '@/hooks/queries/custom-tools' import { useEnvironmentStore } from '@/stores/settings/environment' @@ -373,7 +373,7 @@ async function fetchCustomToolFromAPI( const identifier = customToolId.replace('custom_', '') try { - const baseUrl = getBaseUrl() + const baseUrl = getInternalApiBaseUrl() const url = new URL('/api/tools/custom', baseUrl) if (workflowId) { diff --git a/helm/sim/values.schema.json b/helm/sim/values.schema.json index 13a6a8142..3aeef472e 100644 --- a/helm/sim/values.schema.json +++ b/helm/sim/values.schema.json @@ -120,6 +120,18 @@ "format": "uri", "description": "Public application URL" }, + "INTERNAL_API_BASE_URL": { + "type": "string", + "anyOf": [ + { + "format": "uri" + }, + { + "const": "" + } + ], + "description": "Optional server-side internal base URL for internal /api self-calls (must include http:// or https://); defaults to NEXT_PUBLIC_APP_URL when unset" + }, "BETTER_AUTH_URL": { "type": "string", "format": "uri", diff --git a/helm/sim/values.yaml b/helm/sim/values.yaml index a5fa37d51..9ac47e95e 100644 --- a/helm/sim/values.yaml +++ b/helm/sim/values.yaml @@ -70,6 +70,7 @@ app: # Application URLs NEXT_PUBLIC_APP_URL: "http://localhost:3000" BETTER_AUTH_URL: "http://localhost:3000" + INTERNAL_API_BASE_URL: "" # Optional server-side internal base URL for /api self-calls (include http:// or https://); falls back to NEXT_PUBLIC_APP_URL when empty # SOCKET_SERVER_URL: Auto-detected when realtime.enabled=true (uses internal service) # Only set this if using an external WebSocket service with realtime.enabled=false NEXT_PUBLIC_SOCKET_URL: "http://localhost:3002" # Public WebSocket URL for browsers