diff --git a/apps/docs/content/docs/en/enterprise/index.mdx b/apps/docs/content/docs/en/enterprise/index.mdx
index 3e5acdf5e2..9c4c937cfe 100644
--- a/apps/docs/content/docs/en/enterprise/index.mdx
+++ b/apps/docs/content/docs/en/enterprise/index.mdx
@@ -31,33 +31,6 @@ Define permission groups to control what features and integrations team members
---
-## Bring Your Own Key (BYOK)
-
-Use your own API keys for AI model providers instead of Sim Studio's hosted keys.
-
-### Supported Providers
-
-| Provider | Usage |
-|----------|-------|
-| OpenAI | Knowledge Base embeddings, Agent block |
-| Anthropic | Agent block |
-| Google | Agent block |
-| Mistral | Knowledge Base OCR |
-
-### Setup
-
-1. Navigate to **Settings** → **BYOK** in your workspace
-2. Click **Add Key** for your provider
-3. Enter your API key and save
-
-
- BYOK keys are encrypted at rest. Only organization admins and owners can manage keys.
-
-
-When configured, workflows use your key instead of Sim Studio's hosted keys. If removed, workflows automatically fall back to hosted keys.
-
----
-
## Single Sign-On (SSO)
Enterprise authentication with SAML 2.0 and OIDC support for centralized identity management.
@@ -117,4 +90,3 @@ curl -X POST https://your-instance/api/v1/admin/organizations/{orgId}/members \
### Notes
- Enabling `ACCESS_CONTROL_ENABLED` automatically enables organizations, as access control requires organization membership.
-- BYOK is only available on hosted Sim Studio. Self-hosted deployments configure AI provider keys directly via environment variables.
diff --git a/apps/docs/content/docs/en/execution/costs.mdx b/apps/docs/content/docs/en/execution/costs.mdx
index dce00ace96..a376a97bb7 100644
--- a/apps/docs/content/docs/en/execution/costs.mdx
+++ b/apps/docs/content/docs/en/execution/costs.mdx
@@ -106,7 +106,28 @@ The model breakdown shows:
## Bring Your Own Key (BYOK)
-You can use your own API keys for hosted models (OpenAI, Anthropic, Google, Mistral) in **Settings → BYOK** to pay base prices. Keys are encrypted and apply workspace-wide.
+Use your own API keys for AI model providers instead of Sim Studio's hosted keys to pay base prices with no markup.
+
+### Supported Providers
+
+| Provider | Usage |
+|----------|-------|
+| OpenAI | Knowledge Base embeddings, Agent block |
+| Anthropic | Agent block |
+| Google | Agent block |
+| Mistral | Knowledge Base OCR |
+
+### Setup
+
+1. Navigate to **Settings** → **BYOK** in your workspace
+2. Click **Add Key** for your provider
+3. Enter your API key and save
+
+
+ BYOK keys are encrypted at rest. Only workspace admins can manage keys.
+
+
+When configured, workflows use your key instead of Sim Studio's hosted keys. If removed, workflows automatically fall back to hosted keys with the multiplier.
## Cost Optimization Strategies
diff --git a/apps/sim/app/api/v1/admin/byok/route.ts b/apps/sim/app/api/v1/admin/byok/route.ts
deleted file mode 100644
index 8144993122..0000000000
--- a/apps/sim/app/api/v1/admin/byok/route.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-/**
- * Admin BYOK Keys API
- *
- * GET /api/v1/admin/byok
- * List all BYOK keys with optional filtering.
- *
- * Query Parameters:
- * - organizationId?: string - Filter by organization ID (finds all workspaces billed to this org)
- * - workspaceId?: string - Filter by specific workspace ID
- *
- * Response: { data: AdminBYOKKey[], pagination: PaginationMeta }
- *
- * DELETE /api/v1/admin/byok
- * Delete BYOK keys for an organization or workspace.
- * Used when an enterprise plan churns to clean up BYOK keys.
- *
- * Query Parameters:
- * - organizationId: string - Delete all BYOK keys for workspaces billed to this org
- * - workspaceId?: string - Delete keys for a specific workspace only (optional)
- *
- * Response: { success: true, deletedCount: number, workspacesAffected: string[] }
- */
-
-import { db } from '@sim/db'
-import { user, workspace, workspaceBYOKKeys } from '@sim/db/schema'
-import { createLogger } from '@sim/logger'
-import { eq, inArray, sql } from 'drizzle-orm'
-import { withAdminAuth } from '@/app/api/v1/admin/middleware'
-import {
- badRequestResponse,
- internalErrorResponse,
- singleResponse,
-} from '@/app/api/v1/admin/responses'
-
-const logger = createLogger('AdminBYOKAPI')
-
-export interface AdminBYOKKey {
- id: string
- workspaceId: string
- workspaceName: string
- organizationId: string
- providerId: string
- createdAt: string
- createdByUserId: string | null
- createdByEmail: string | null
-}
-
-export const GET = withAdminAuth(async (request) => {
- const url = new URL(request.url)
- const organizationId = url.searchParams.get('organizationId')
- const workspaceId = url.searchParams.get('workspaceId')
-
- try {
- let workspaceIds: string[] = []
-
- if (workspaceId) {
- workspaceIds = [workspaceId]
- } else if (organizationId) {
- const workspaces = await db
- .select({ id: workspace.id })
- .from(workspace)
- .where(eq(workspace.billedAccountUserId, organizationId))
-
- workspaceIds = workspaces.map((w) => w.id)
- }
-
- const query = db
- .select({
- id: workspaceBYOKKeys.id,
- workspaceId: workspaceBYOKKeys.workspaceId,
- workspaceName: workspace.name,
- organizationId: workspace.billedAccountUserId,
- providerId: workspaceBYOKKeys.providerId,
- createdAt: workspaceBYOKKeys.createdAt,
- createdByUserId: workspaceBYOKKeys.createdBy,
- createdByEmail: user.email,
- })
- .from(workspaceBYOKKeys)
- .innerJoin(workspace, eq(workspaceBYOKKeys.workspaceId, workspace.id))
- .leftJoin(user, eq(workspaceBYOKKeys.createdBy, user.id))
-
- let keys
- if (workspaceIds.length > 0) {
- keys = await query.where(inArray(workspaceBYOKKeys.workspaceId, workspaceIds))
- } else {
- keys = await query
- }
-
- const formattedKeys: AdminBYOKKey[] = keys.map((k) => ({
- id: k.id,
- workspaceId: k.workspaceId,
- workspaceName: k.workspaceName,
- organizationId: k.organizationId,
- providerId: k.providerId,
- createdAt: k.createdAt.toISOString(),
- createdByUserId: k.createdByUserId,
- createdByEmail: k.createdByEmail,
- }))
-
- logger.info('Admin API: Listed BYOK keys', {
- organizationId,
- workspaceId,
- count: formattedKeys.length,
- })
-
- return singleResponse({
- data: formattedKeys,
- pagination: {
- total: formattedKeys.length,
- limit: formattedKeys.length,
- offset: 0,
- hasMore: false,
- },
- })
- } catch (error) {
- logger.error('Admin API: Failed to list BYOK keys', { error, organizationId, workspaceId })
- return internalErrorResponse('Failed to list BYOK keys')
- }
-})
-
-export const DELETE = withAdminAuth(async (request) => {
- const url = new URL(request.url)
- const organizationId = url.searchParams.get('organizationId')
- const workspaceId = url.searchParams.get('workspaceId')
- const reason = url.searchParams.get('reason') || 'Enterprise plan churn cleanup'
-
- if (!organizationId && !workspaceId) {
- return badRequestResponse('Either organizationId or workspaceId is required')
- }
-
- try {
- let workspaceIds: string[] = []
-
- if (workspaceId) {
- workspaceIds = [workspaceId]
- } else if (organizationId) {
- const workspaces = await db
- .select({ id: workspace.id })
- .from(workspace)
- .where(eq(workspace.billedAccountUserId, organizationId))
-
- workspaceIds = workspaces.map((w) => w.id)
- }
-
- if (workspaceIds.length === 0) {
- logger.info('Admin API: No workspaces found for BYOK cleanup', {
- organizationId,
- workspaceId,
- })
- return singleResponse({
- success: true,
- deletedCount: 0,
- workspacesAffected: [],
- message: 'No workspaces found for the given organization/workspace ID',
- })
- }
-
- const countResult = await db
- .select({ count: sql`count(*)` })
- .from(workspaceBYOKKeys)
- .where(inArray(workspaceBYOKKeys.workspaceId, workspaceIds))
-
- const totalToDelete = Number(countResult[0]?.count ?? 0)
-
- if (totalToDelete === 0) {
- logger.info('Admin API: No BYOK keys to delete', {
- organizationId,
- workspaceId,
- workspaceIds,
- })
- return singleResponse({
- success: true,
- deletedCount: 0,
- workspacesAffected: [],
- message: 'No BYOK keys found for the specified workspaces',
- })
- }
-
- await db.delete(workspaceBYOKKeys).where(inArray(workspaceBYOKKeys.workspaceId, workspaceIds))
-
- logger.info('Admin API: Deleted BYOK keys', {
- organizationId,
- workspaceId,
- workspaceIds,
- deletedCount: totalToDelete,
- reason,
- })
-
- return singleResponse({
- success: true,
- deletedCount: totalToDelete,
- workspacesAffected: workspaceIds,
- reason,
- })
- } catch (error) {
- logger.error('Admin API: Failed to delete BYOK keys', { error, organizationId, workspaceId })
- return internalErrorResponse('Failed to delete BYOK keys')
- }
-})
diff --git a/apps/sim/app/api/v1/admin/index.ts b/apps/sim/app/api/v1/admin/index.ts
index ad91e0c447..82e60e0eab 100644
--- a/apps/sim/app/api/v1/admin/index.ts
+++ b/apps/sim/app/api/v1/admin/index.ts
@@ -53,10 +53,6 @@
* GET /api/v1/admin/subscriptions/:id - Get subscription details
* DELETE /api/v1/admin/subscriptions/:id - Cancel subscription (?atPeriodEnd=true for scheduled)
*
- * BYOK Keys:
- * GET /api/v1/admin/byok - List BYOK keys (?organizationId=X or ?workspaceId=X)
- * DELETE /api/v1/admin/byok - Delete BYOK keys for org/workspace
- *
* Access Control (Permission Groups):
* GET /api/v1/admin/access-control - List permission groups (?organizationId=X)
* DELETE /api/v1/admin/access-control - Delete permission groups for org (?organizationId=X)
diff --git a/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts
index 84be273d12..246cc6b245 100644
--- a/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts
+++ b/apps/sim/app/api/workspaces/[id]/byok-keys/route.ts
@@ -6,8 +6,6 @@ import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
-import { isEnterpriseOrgAdminOrOwner } from '@/lib/billing/core/subscription'
-import { isHosted } from '@/lib/core/config/feature-flags'
import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption'
import { generateRequestId } from '@/lib/core/utils/request'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -58,15 +56,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
- let byokEnabled = true
- if (isHosted) {
- byokEnabled = await isEnterpriseOrgAdminOrOwner(userId)
- }
-
- if (!byokEnabled) {
- return NextResponse.json({ keys: [], byokEnabled: false })
- }
-
const byokKeys = await db
.select({
id: workspaceBYOKKeys.id,
@@ -108,7 +97,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
})
)
- return NextResponse.json({ keys: formattedKeys, byokEnabled: true })
+ return NextResponse.json({ keys: formattedKeys })
} catch (error: unknown) {
logger.error(`[${requestId}] BYOK keys GET error`, error)
return NextResponse.json(
@@ -131,20 +120,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
const userId = session.user.id
- if (isHosted) {
- const canManageBYOK = await isEnterpriseOrgAdminOrOwner(userId)
- if (!canManageBYOK) {
- logger.warn(`[${requestId}] User not authorized to manage BYOK keys`, { userId })
- return NextResponse.json(
- {
- error:
- 'BYOK is an Enterprise-only feature. Only organization admins and owners can manage API keys.',
- },
- { status: 403 }
- )
- }
- }
-
const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId)
if (permission !== 'admin') {
return NextResponse.json(
@@ -245,20 +220,6 @@ export async function DELETE(
const userId = session.user.id
- if (isHosted) {
- const canManageBYOK = await isEnterpriseOrgAdminOrOwner(userId)
- if (!canManageBYOK) {
- logger.warn(`[${requestId}] User not authorized to manage BYOK keys`, { userId })
- return NextResponse.json(
- {
- error:
- 'BYOK is an Enterprise-only feature. Only organization admins and owners can manage API keys.',
- },
- { status: 403 }
- )
- }
- }
-
const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId)
if (permission !== 'admin') {
return NextResponse.json(
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/byok/byok.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/byok/byok.tsx
index e06ab18c3b..867c128f60 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/byok/byok.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/byok/byok.tsx
@@ -2,7 +2,7 @@
import { useState } from 'react'
import { createLogger } from '@sim/logger'
-import { Crown, Eye, EyeOff } from 'lucide-react'
+import { Eye, EyeOff } from 'lucide-react'
import { useParams } from 'next/navigation'
import {
Button,
@@ -83,7 +83,6 @@ export function BYOK() {
const { data, isLoading } = useBYOKKeys(workspaceId)
const keys = data?.keys ?? []
- const byokEnabled = data?.byokEnabled ?? true
const upsertKey = useUpsertBYOKKey()
const deleteKey = useDeleteBYOKKey()
@@ -98,31 +97,6 @@ export function BYOK() {
return keys.find((k) => k.providerId === providerId)
}
- // Show enterprise-only gate if BYOK is not enabled
- if (!isLoading && !byokEnabled) {
- return (
-
-
-
-
-
-
Enterprise Feature
-
- Bring Your Own Key (BYOK) is available exclusively on the Enterprise plan. Upgrade to
- use your own API keys and eliminate the 2x cost multiplier.
-
-
-
-
- )
- }
-
const handleSave = async () => {
if (!editingProvider || !apiKeyInput.trim()) return
@@ -340,7 +314,7 @@ export function BYOK() {
{PROVIDERS.find((p) => p.id === deleteConfirmProvider)?.name}
{' '}
- API key? This workspace will revert to using platform keys with the 2x multiplier.
+ API key? This workspace will revert to using platform hosted keys.
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx
index a22df0a777..bc610fe74e 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx
@@ -152,9 +152,8 @@ const allNavigationItems: NavigationItem[] = [
id: 'byok',
label: 'BYOK',
icon: KeySquare,
- section: 'enterprise',
+ section: 'system',
requiresHosted: true,
- requiresEnterprise: true,
},
{
id: 'copilot',
diff --git a/apps/sim/hooks/queries/byok-keys.ts b/apps/sim/hooks/queries/byok-keys.ts
index 36ec66827c..26d348d5a7 100644
--- a/apps/sim/hooks/queries/byok-keys.ts
+++ b/apps/sim/hooks/queries/byok-keys.ts
@@ -17,7 +17,6 @@ export interface BYOKKey {
export interface BYOKKeysResponse {
keys: BYOKKey[]
- byokEnabled: boolean
}
export const byokKeysKeys = {
@@ -33,7 +32,6 @@ async function fetchBYOKKeys(workspaceId: string): Promise {
const data = await response.json()
return {
keys: data.keys ?? [],
- byokEnabled: data.byokEnabled ?? true,
}
}
diff --git a/apps/sim/lib/api-key/byok.ts b/apps/sim/lib/api-key/byok.ts
index 34c589c21a..04a35adb42 100644
--- a/apps/sim/lib/api-key/byok.ts
+++ b/apps/sim/lib/api-key/byok.ts
@@ -2,7 +2,6 @@ import { db } from '@sim/db'
import { workspaceBYOKKeys } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
-import { isWorkspaceOnEnterprisePlan } from '@/lib/billing'
import { getRotatingApiKey } from '@/lib/core/config/api-keys'
import { isHosted } from '@/lib/core/config/feature-flags'
import { decryptSecret } from '@/lib/core/security/encryption'
@@ -91,18 +90,12 @@ export async function getApiKeyWithBYOK(
logger.debug('BYOK check', { provider, model, workspaceId, isHosted, isModelHosted })
if (isModelHosted || isMistralModel) {
- const hasEnterprise = await isWorkspaceOnEnterprisePlan(workspaceId)
-
- if (hasEnterprise) {
- const byokResult = await getBYOKKey(workspaceId, byokProviderId)
- if (byokResult) {
- logger.info('Using BYOK key', { provider, model, workspaceId })
- return byokResult
- }
- logger.debug('No BYOK key found, falling back', { provider, model, workspaceId })
- } else {
- logger.debug('Workspace not on enterprise plan, skipping BYOK', { workspaceId })
+ const byokResult = await getBYOKKey(workspaceId, byokProviderId)
+ if (byokResult) {
+ logger.info('Using BYOK key', { provider, model, workspaceId })
+ return byokResult
}
+ logger.debug('No BYOK key found, falling back', { provider, model, workspaceId })
if (isModelHosted) {
try {
diff --git a/apps/sim/lib/billing/core/subscription.ts b/apps/sim/lib/billing/core/subscription.ts
index d5721b7ebd..2b287da4a8 100644
--- a/apps/sim/lib/billing/core/subscription.ts
+++ b/apps/sim/lib/billing/core/subscription.ts
@@ -1,5 +1,5 @@
import { db } from '@sim/db'
-import { member, subscription, user, userStats, workspace } from '@sim/db/schema'
+import { member, subscription, user, userStats } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { getHighestPrioritySubscription } from '@/lib/billing/core/plan'
@@ -218,34 +218,6 @@ export async function isTeamOrgAdminOrOwner(userId: string): Promise {
}
}
-/**
- * Check if a workspace has access to enterprise features (BYOK)
- * Used at execution time to determine if BYOK keys should be used
- * Returns true if workspace's billed account is on enterprise plan
- */
-export async function isWorkspaceOnEnterprisePlan(workspaceId: string): Promise {
- try {
- if (!isProd) {
- return true
- }
-
- const [ws] = await db
- .select({ billedAccountUserId: workspace.billedAccountUserId })
- .from(workspace)
- .where(eq(workspace.id, workspaceId))
- .limit(1)
-
- if (!ws) {
- return false
- }
-
- return isEnterprisePlan(ws.billedAccountUserId)
- } catch (error) {
- logger.error('Error checking workspace enterprise status', { error, workspaceId })
- return false
- }
-}
-
/**
* Check if an organization has team or enterprise plan
* Used at execution time (e.g., polling services) to check org billing directly
diff --git a/apps/sim/lib/billing/index.ts b/apps/sim/lib/billing/index.ts
index ddd4b8d1c5..9ec6f9cd6b 100644
--- a/apps/sim/lib/billing/index.ts
+++ b/apps/sim/lib/billing/index.ts
@@ -20,7 +20,6 @@ export {
isProPlan as hasProPlan,
isTeamOrgAdminOrOwner,
isTeamPlan as hasTeamPlan,
- isWorkspaceOnEnterprisePlan,
sendPlanWelcomeEmail,
} from '@/lib/billing/core/subscription'
export * from '@/lib/billing/core/usage'