Self-hosted enterprise

On a self-hosted deployment with billing disabled, enable enterprise features through environment variables. Then create an organization and attach its workspaces: branding, PII redaction, permission groups, custom blocks, data drains, and organization audit scope depend on the organization that owns a workspace.

This guide covers the standard billing-disabled configuration. If billing is enabled, subscription gates apply and instance-organization variables are ignored.

Enable the feature set

Set both the server flag and its browser twin, then restart the application:

ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true

The server flag controls access and the server-resolved workspace settings. The browser twin also enables controls that read browser configuration directly, including SSO on the login page and the Function block's Sandbox selector. On a Compose install or source checkout, bunx sim-setup doctor reports mismatched twins. See Verify your install.

Individual flags override the master switch. For example, to enable the suite with data drains disabled:

ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
DATA_DRAINS_ENABLED=false
NEXT_PUBLIC_DATA_DRAINS_ENABLED=false

You can also leave the master switch unset and enable individual features. The feature flag reference lists them all.

Choose an organization model

One organization for the instance

Use this when everyone on the deployment belongs to the same company. Set the instance organization's name:

INSTANCE_ORG_NAME="Acme Inc"

New accounts join it at signup, and their workspaces are created as organization-owned. Optionally set its slug and an existing account to own it:

INSTANCE_ORG_SLUG=acme-inc
INSTANCE_ORG_OWNER_EMAIL=admin@acme.com

The organization is created on the first signup after enabling this mode. If the owner email is unset or does not match an existing account, the account that triggers creation becomes the owner. Transfer ownership later through the Admin API if needed. Creation is idempotent across replicas.

Backfill existing accounts and workspaces

Setting INSTANCE_ORG_NAME does not move existing accounts or workspaces. Run the consolidation script from the repository root. If the organization does not exist yet, provide an owner email that matches an existing Sim account:

# Preview the changes
DATABASE_URL=... \
INSTANCE_ORG_NAME="Acme Inc" \
INSTANCE_ORG_OWNER_EMAIL=admin@acme.com \
  bun run apps/sim/scripts/consolidate-users-into-organization.ts

# Apply the same changes
DATABASE_URL=... \
INSTANCE_ORG_NAME="Acme Inc" \
INSTANCE_ORG_OWNER_EMAIL=admin@acme.com \
  bun run apps/sim/scripts/consolidate-users-into-organization.ts --apply

The script adds accounts to the organization and attaches their workspaces. It is a dry run unless you pass --apply, and is safe to rerun. Accounts already in another organization are reported and skipped because an account can belong to only one organization. When the instance organization already exists, the script uses its current owner.

Separate organizations managed through the Admin API

Leave INSTANCE_ORG_NAME unset when one deployment serves multiple teams. Create organizations, add members, and attach workspaces through the Admin API.

Generate an admin key once:

openssl rand -hex 32

Persist that value as ADMIN_API_KEY in the application's environment, then restart the application. Use the same value in the shell or client issuing the following requests:

ADMIN_API_KEY='<the-same-key-configured-on-the-application>'

Generating or assigning a value in the caller's shell does not configure the running application. Requests use the exact x-admin-key header.

Create an organization

The owner account must exist and must not belong to another organization.

curl -X POST https://sim.example.com/api/v1/admin/organizations \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Inc", "ownerId": "user_123", "slug": "acme-inc"}'

Use the returned organization ID as ORG_ID in subsequent commands. Creating the organization does not attach a workspace.

Add members

curl -X POST https://sim.example.com/api/v1/admin/organizations/$ORG_ID/members \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"userId": "user_456", "role": "member"}'

Attach a workspace

Enterprise settings apply only to workspaces the organization owns. Generate a fresh UUID for each new move. From the repository root:

OPERATION_ID=$(bun -e 'import { generateId } from "@sim/utils/id"; process.stdout.write(generateId())')

curl -X POST https://sim.example.com/api/v1/admin/dashboard/workspaces/$WORKSPACE_ID/move \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"operationId\": \"$OPERATION_ID\", \"destinationOrganizationId\": \"$ORG_ID\"}"

Keep the same OPERATION_ID when retrying this move; generate a new one only for a new operation. Inspect data.followUpJobs in the response for pending or failed follow-up work. Check it again with:

curl "https://sim.example.com/api/v1/admin/dashboard/workspaces/$WORKSPACE_ID/move-operations/$OPERATION_ID?destinationOrganizationId=$ORG_ID" \
  -H "x-admin-key: $ADMIN_API_KEY"

Configure organization settings

Use the Settings UI or these API requests:

curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/whitelabel \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"brandName": "Acme AI", "hidePoweredBySim": true}'
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/data-retention \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"logRetentionHours": 2160}'
curl -X PATCH https://sim.example.com/api/v1/admin/organizations/$ORG_ID/session-policy \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"maxSessionHours": 168, "idleTimeoutHours": 48}'

When invitations are disabled with DISABLE_INVITATIONS and NEXT_PUBLIC_DISABLE_INVITATIONS, use the Admin API to manage organization membership and workspace access. Internal members join the organization. External workspace members receive access to a specific workspace without joining the organization.

Read audit logs and permission groups

Admin-key reads do not require an organization or Enterprise subscription:

# Deployment audit entries, with filters also available in the UI
curl "https://sim.example.com/api/v1/admin/audit-logs?limit=50&workspaceId=$WORKSPACE_ID" \
  -H "x-admin-key: $ADMIN_API_KEY"

# One audit entry
curl "https://sim.example.com/api/v1/admin/audit-logs/$AUDIT_LOG_ID" \
  -H "x-admin-key: $ADMIN_API_KEY"

# Permission groups, optionally scoped to an organization
curl "https://sim.example.com/api/v1/admin/access-control?organizationId=$ORG_ID" \
  -H "x-admin-key: $ADMIN_API_KEY"

The organization-scoped /api/v1/audit-logs endpoint uses an API key whose owner is an organization admin or owner. Use the admin endpoint when no organization exists yet. See Audit logs.

Delete an organization

Deleting an organization removes its memberships, invitations, permission groups, and organization settings. Its workspaces are detached and survive. A non-terminal subscription prevents deletion; cancel it first.

Confirm the target by passing its exact slug:

curl -X DELETE "https://sim.example.com/api/v1/admin/organizations/$ORG_ID?confirmSlug=acme-inc" \
  -H "x-admin-key: $ADMIN_API_KEY"

Schedule the background jobs

Configure a scheduler to call the maintenance endpoints. Generate a cron secret:

openssl rand -hex 32

Persist the same value as CRON_SECRET on the application and its scheduler. Each endpoint requires Authorization: Bearer <CRON_SECRET> and returns 401 if the secret is unset or does not match.

FeatureEndpointSuggested scheduleIncluded in Helm and production Compose schedules
Data drainsGET /api/cron/run-data-drainsHourlyYes
SCIM reconciliationGET /api/cron/scim-reconcileHourlyYes
Retention: logsGET /api/logs/cleanupDailyNo
Retention: soft deletesGET /api/cron/cleanup-soft-deletesDailyNo
Retention: Chat tasksGET /api/cron/cleanup-tasksDailyNo
Retention: file versionsGET /api/cron/cleanup-file-versionsDailyNo
OAuth token cleanupGET /api/cron/cleanup-oauth-tokensHourlyYes

Included schedules still require the cron service to be configured and running. For an external scheduler, a request looks like:

curl -H "Authorization: Bearer $CRON_SECRET" \
  https://sim.example.com/api/logs/cleanup

Configuring retention windows and enabling deletion are separate actions. Set DATA_RETENTION_ENABLED=true to allow retention cleanup, and schedule the four retention endpoints yourself, for example through Helm's cronjobs.jobs. Neither the flag alone nor the shipped cron schedules expire retained product data. Billing-disabled deployments use only explicitly configured windows, without hosted-plan defaults.

Retention and data-drain work use the configured job backend. Without Trigger.dev, they have in-process execution paths; with Trigger.dev configured, external workers run the jobs. SCIM reconciliation runs directly through its authenticated cron endpoint and does not require Trigger.dev. See Background jobs for operational setup and Data retention for effective policy.

OAuth token cleanup removes expired and revoked credentials independently of sign-in activity. See Sign in with Sim for provider configuration.

Feature flag reference

Explicit individual flags override the master switch, including when set to false. With billing disabled and no enterprise flags set, custom branding, session policies, and the Sim Mailer inbox remain enabled by default. Other features require opt-in.

FeatureServer variableBrowser variable
Enterprise suiteENTERPRISE_ENABLEDNEXT_PUBLIC_ENTERPRISE_ENABLED
OrganizationsORGANIZATIONS_ENABLEDNEXT_PUBLIC_ORGANIZATIONS_ENABLED
Permission groupsACCESS_CONTROL_ENABLEDNEXT_PUBLIC_ACCESS_CONTROL_ENABLED
SAML and OIDC sign-inSSO_ENABLEDNEXT_PUBLIC_SSO_ENABLED
Directory provisioningSCIM_ENABLEDNEXT_PUBLIC_SCIM_ENABLED
Custom branding — enabled by defaultWHITELABELING_ENABLEDNEXT_PUBLIC_WHITELABELING_ENABLED
Audit logsAUDIT_LOGS_ENABLEDNEXT_PUBLIC_AUDIT_LOGS_ENABLED
InsightsUSAGE_MONITORING_ENABLEDNEXT_PUBLIC_USAGE_MONITORING_ENABLED
Custom blocksCUSTOM_BLOCKS_ENABLEDNEXT_PUBLIC_CUSTOM_BLOCKS_ENABLED
Session policies — enabled by defaultSESSION_POLICIES_ENABLEDNEXT_PUBLIC_SESSION_POLICIES_ENABLED
Retention deletionDATA_RETENTION_ENABLEDNEXT_PUBLIC_DATA_RETENTION_ENABLED
Data drainsDATA_DRAINS_ENABLEDNEXT_PUBLIC_DATA_DRAINS_ENABLED
Credential groupsCREDENTIAL_GROUPS—
Workspace forksFORKING_ENABLEDNEXT_PUBLIC_FORKING_ENABLED
Sim Mailer inbox — enabled by defaultINBOX_ENABLEDNEXT_PUBLIC_INBOX_ENABLED
SandboxesSANDBOXES_ENABLEDNEXT_PUBLIC_SANDBOXES_ENABLED

Sandbox readiness

A Sandbox entitlement also requires a remote execution provider with credentials and an immutable Function base image. Until the server verifies readiness, the Function block offers no Shell language, and Settings → Sandboxes shows an upgrade notice instead of a sandbox list.

NEXT_PUBLIC_SANDBOXES_ENABLED exposes the Function block's Sandbox selector; it asserts readiness rather than checking it. Set it only after configuring the selected provider and Function base. bunx sim-setup doctor compares it with provider readiness.

JavaScript without import, require, a selected sandbox, or a sandbox file-path reference runs in local Node child workers in the app container when Sandbox flags are off. Python, Shell, JavaScript with external imports, and selected custom Sandboxes require the remote Function base and fail with a configuration error until it is ready. See Sandboxes.

Verify the setup

SymptomCheck
A settings section is missingCheck its server flag and restart the app after changing environment variables. These values load at boot; a rebuild is not needed.
The login page has no SSO option, or Function has no Sandbox selectorSet the matching NEXT_PUBLIC_ flag. Run bunx sim-setup doctor to check twins and Sandbox readiness.
Settings → Sandboxes shows an upgrade noticeCheck Sandbox entitlement, provider credentials, and the immutable Function base.
An enabled feature has no effect in a workspaceConfirm that the workspace belongs to an organization. Use the backfill or workspace move procedure if needed.
Retention deletes nothingCheck DATA_RETENTION_ENABLED, explicit windows, scheduler requests, and CRON_SECRET.

To inspect workspace ownership:

SELECT id, name, workspace_mode, organization_id FROM workspace;

A workspace with personal mode or a null organization_id does not inherit organization branding, PII redaction, permission groups, or drains.