Self-hosted Enterprise

On Sim Cloud, enterprise features are unlocked by an Enterprise subscription. Self-hosted deployments have no subscription, so they are unlocked by environment configuration instead.

There are two parts to getting this right, and skipping the second is the most common reason features appear to do nothing:

  1. Enable the features with ENTERPRISE_ENABLED.
  2. Give them an organization to apply to. Whitelabeling, PII redaction, permission groups, data drains, and audit scoping all read their settings from the organization that owns a workspace. A deployment where everyone works in personal workspaces has no organization for those settings to come from.

Enable the feature set

Set the master switch and its client twin. Both are required — the server value decides access, and the NEXT_PUBLIC_ value decides what the settings UI shows.

ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true

That turns on organizations, permission groups, SSO, whitelabeling, audit logs, session policies, data retention, data drains, workspace forks, the Sandbox entitlement, and the inbox. Sandboxes remain unavailable until their remote provider and dedicated Function base are configured.

Turning one feature off

Every feature keeps its own flag, and an explicitly set flag always wins over the master switch. To run the suite without data drains:

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

The individual flags also work on their own if you would rather opt in one at a time and leave the master switch unset.

FeatureServer variableClient variable
Everything belowENTERPRISE_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
Custom brandingWHITELABELING_ENABLEDNEXT_PUBLIC_WHITELABELING_ENABLED
Audit logsAUDIT_LOGS_ENABLEDNEXT_PUBLIC_AUDIT_LOGS_ENABLED
Session policiesSESSION_POLICIES_ENABLEDNEXT_PUBLIC_SESSION_POLICIES_ENABLED
Data retention deletionDATA_RETENTION_ENABLEDNEXT_PUBLIC_DATA_RETENTION_ENABLED
Data drainsDATA_DRAINS_ENABLEDNEXT_PUBLIC_DATA_DRAINS_ENABLED
Workspace forksFORKING_ENABLED
Sim Mailer inboxINBOX_ENABLEDNEXT_PUBLIC_INBOX_ENABLED
SandboxesSANDBOXES_ENABLEDNEXT_PUBLIC_SANDBOXES_ENABLED

Sandboxes also need a remote execution provider and a dedicated Function base image. Build and configure that base before enabling the UI; custom workspace sandboxes layer their packages on top of it.

For E2B:

E2B_API_KEY=... \
  bun run apps/sim/scripts/build-function-e2b-template.ts \
  --name sim-function

SANDBOX_PROVIDER=e2b
E2B_ENABLED=true
E2B_API_KEY=...
E2B_FUNCTION_TEMPLATE_ID=<sim-function-template>:<sim-function-build-id>
E2B_FUNCTION_TEMPLATE_GENERATION=<release-epoch-ms>
SANDBOXES_ENABLED=true
NEXT_PUBLIC_SANDBOXES_ENABLED=true

The builder uses E2B's maintained code-interpreter-v1 base, assigns a fresh release generation, and prints both runtime values. --generation remains available for release automation, and --base-template accepts an immutable base override when a deployment deliberately owns one.

For Daytona, use the immutable snapshot ID printed by the builder. The API key needs write:snapshots to build and write:sandboxes to execute:

DAYTONA_API_KEY=... \
  bun run apps/sim/scripts/build-function-daytona-snapshot.ts \
  --name sim-function-2026-08-03 \
  --parity-manifest /tmp/function-sandbox-manifest.json

SANDBOX_PROVIDER=daytona
DAYTONA_API_KEY=...
DAYTONA_FUNCTION_SNAPSHOT_ID=<snapshot-uuid>
SANDBOXES_ENABLED=true
NEXT_PUBLIC_SANDBOXES_ENABLED=true

SANDBOXES_ENABLED grants the server-side self-hosted entitlement. NEXT_PUBLIC_SANDBOXES_ENABLED projects provider readiness to the browser and exposes Shell plus custom Sandbox management. Set the public flag only after the selected provider has credentials and a valid immutable Function base configured. The Function language value itself is never conditioned on these flags, so a saved Python block cannot be silently serialized or executed as JavaScript.

JavaScript without import or require does not use this remote provider and continues to run in the local isolated VM when all Sandbox flags are off. Python, Shell, JavaScript with external imports, and selected custom Sandboxes fail with an explicit configuration error until the remote Function base is ready.

Mothership's function_execute and run_code tools use Mothership's separate shell image, including for JavaScript without imports. If the deployment uses Mothership code tools, also configure the image produced by the Mothership release process for the selected provider:

# E2B
MOTHERSHIP_E2B_TEMPLATE_ID=<mothership-shell-template-ref>

# Daytona
DAYTONA_SHELL_SNAPSHOT_ID=<mothership-shell-snapshot-ref>

These values are selected only for workflow Copilot and workspace Mothership code-tool calls. They never replace or act as a fallback for E2B_FUNCTION_TEMPLATE_ID or DAYTONA_FUNCTION_SNAPSHOT_ID; Function blocks and custom workspace sandboxes continue to use the dedicated Function base.

Use E2B as the release baseline before building or promoting Daytona:

# 1. Verify the exact E2B Function build and capture its accepted package/runtime surface.
E2B_ENABLED=true \
E2B_API_KEY=... \
E2B_FUNCTION_TEMPLATE_ID=<sim-function-template>:<sim-function-build-id> \
E2B_FUNCTION_TEMPLATE_GENERATION=<release-epoch-ms> \
SANDBOX_PARITY_MANIFEST_OUT=/tmp/function-sandbox-manifest.json \
  bun run apps/sim/scripts/verify-sandbox-parity.ts

# 2. Pin Daytona's reconstructed packages to that accepted E2B manifest.
DAYTONA_API_KEY=... \
  bun run apps/sim/scripts/build-function-daytona-snapshot.ts \
  --name sim-function-2026-08-03 \
  --parity-manifest /tmp/function-sandbox-manifest.json

# 3. Verify the immutable Daytona snapshot against the same baseline before promotion.
SANDBOX_PROVIDER=daytona \
DAYTONA_API_KEY=... \
DAYTONA_FUNCTION_SNAPSHOT_ID=<snapshot-uuid> \
SANDBOX_PARITY_MANIFEST_BASELINE=/tmp/function-sandbox-manifest.json \
  bun run apps/sim/scripts/verify-sandbox-parity.ts

E2B_FUNCTION_TEMPLATE_ID and DAYTONA_FUNCTION_SNAPSHOT_ID fail closed when unset or mutable. The E2B value must be an exact <template>:<build-id> ref, and E2B_FUNCTION_TEMPLATE_GENERATION must be the monotonic value printed by the same build. The Daytona value must be a snapshot ID rather than a name. Sim does not fall back to MOTHERSHIP_E2B_TEMPLATE_ID or DAYTONA_SHELL_SNAPSHOT_ID; Mothership, Function, document, and Pi images have separate package contracts and release cadences.

Assign every promoted E2B Function base a generation greater than every prior deployment. A rollback is a new promotion and therefore also needs a new, higher generation; do not reuse the generation from the older release.

Data retention is the one feature that deletes data. Its flag controls the cleanup pass, not the settings screen — retention windows are always configurable. Nothing is ever deleted until you enable it, and even then only against windows you configured explicitly. Sim never applies the hosted plan defaults to a self-hosted deployment.

Choose an organization model

Pattern 1: one organization for the whole instance

Best when everyone on the deployment belongs to the same company. Set a name and every user joins that organization automatically at signup, with their workspaces created org-owned.

INSTANCE_ORG_NAME="Acme Inc"

Optionally pin the slug and the owner:

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

The organization is created the first time a user signs up. If INSTANCE_ORG_OWNER_EMAIL is not set, or names a user who does not exist yet, the first user to sign up becomes the owner; move ownership later with the Admin API. Provisioning is idempotent and safe across multiple replicas.

Instance-organization mode only applies when billing is disabled. With billing enabled, organizations are created through the normal subscription flow and these variables are ignored.

Existing deployments

Users and workspaces created before you set INSTANCE_ORG_NAME stay where they are. Move them across once with the backfill script, which adds every user to the organization and attaches their workspaces:

# Preview
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
  bun run apps/sim/scripts/consolidate-users-into-organization.ts

# Apply
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
  bun run apps/sim/scripts/consolidate-users-into-organization.ts --apply

It is a dry run unless you pass --apply, and it is safe to re-run. Users who already belong to a different organization are reported and skipped, since a user can only belong to one.

Pattern 2: many organizations you manage yourself

Best when one deployment serves several teams that should not see each other's data. Leave INSTANCE_ORG_NAME unset and provision organizations through the Admin API.

Set an admin key first:

ADMIN_API_KEY=$(openssl rand -hex 32)

Create an organization

The owner must not already 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"}'

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"}'

Move a workspace into the organization

Organization-scoped features only apply to workspaces the organization owns.

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 "{\"destinationOrganizationId\": \"$ORG_ID\"}"

Configure organization settings

Branding, retention, and session policies can be set from the API instead of the UI.

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}'

Deleting an organization requires echoing its slug, because the delete cascades to members, invitations, and permission groups, and detaches its workspaces:

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

Verifying it worked

If a feature is enabled but nothing appears, check these in order.

The settings section is missing. The NEXT_PUBLIC_ twin is not set, or the app was not restarted after adding it. Client variables are read at build and boot.

The section appears but the API returns 403. The server-side variable is missing while its client twin is set. Set both.

The feature is on but has no effect inside a workspace. The workspace is not owned by an organization. Check workspace_mode and organization_id:

SELECT id, name, workspace_mode, organization_id FROM workspace;

A workspace showing personal or a null organization_id will not pick up branding, PII redaction, permission groups, or drains. Use the backfill script or the workspace move endpoint.

Retention is configured but nothing is deleted. DATA_RETENTION_ENABLED is unset. Configuring windows and running the cleanup pass are separate switches by design.

On this page