Skip to main content

Multi-Tenancy & Data Model

OrcFlows is multi-tenant at the database layer: nearly every table carries a tenant_id foreign key, and every API request resolves a tenant from the caller's JWT (or, for public routes, from a workflow-scoped API key/UUID). There is no shared-data path between tenants.

Schema overview

Migrations live in internal/db/migrations/ and are applied with go run ./cmd/cli/... migrate up. Key tables:

TablePurpose
tenantsWorkspaces — slug, name, plan tier, optional organization_id.
usersPer-tenant accounts with a role (owner/admin/member/viewer).
organizationsOptional grouping of multiple tenants (Enterprise).
workflow_defsWorkflow definitions (DSL, status, enabled flag, webhook API key).
workflow_versionsHistorical versions of each definition.
workflow_templatesReusable starter workflows for the gallery.
workflow_executionsOne row per run — status, trigger data, input/output, timing.
step_executionsOne row per step per execution — status, input/output, timing.
workflow_api_keysPer-workflow public API keys (/run/{id}).
workflow_chat_deploymentsChat widget configuration per workflow.
workflow_batch_runs / workflow_batch_itemsBatch evaluation runs and per-item results.
workflow_kv_storeGeneric per-tenant key/value storage backing the store node.
trigger_poll_stateCursor/state for event pollers (email, Telegram, Slack, Airtable, etc.) so they don't reprocess items.
secretsAES-256-GCM encrypted named secrets.
oauth_connectionsAES-256-GCM encrypted OAuth tokens (Google/Notion/Slack + manual API-key connections).
connectors / connector_authsConnector metadata and per-tenant credential bindings.
knowledge_bases / kb_documents / kb_chunksRAG knowledge bases, documents, and embedded chunks (with cosine_similarity SQL function).
agent_memoriesPer-session agent conversation history.
skillsAgent skills (SKILL.md content, scripts, references).
api_keysPlatform-level API keys.
sso_configsPer-tenant SAML/OIDC configuration.
subscriptionsStripe subscription state per tenant.
audit_eventsAudit log of sensitive actions.

Tenant resolution

  • Authenticated requests carry a JWT containing tenant_id and user_id; auth.Middleware validates it and auth.TenantIDFrom(ctx) / auth.UserIDFrom(ctx) extract them for handlers.
  • Public routes (/webhooks/{workflowId}, /run/{workflowId}, file downloads) resolve the tenant indirectly via the workflow/execution UUID itself (a 128-bit random ID is treated as sufficient entropy to be unguessable) plus, where applicable, an API key check.

Foreign keys & cascades

Most tenant-scoped tables cascade-delete when their parent (tenants row, or a workflow_defs row — see migration 000014_workflow_delete_cascade.up.sql) is deleted. audit_events does not cascade from tenants — referencing rows must be deleted explicitly before a tenant can be removed (relevant for tests and any tenant-deletion tooling).

Organizations

organizations.owner_id → users.id, and tenants.organization_id → organizations.id (ON DELETE SET NULL) — deleting an organization does not delete its tenants, it just ungroups them. See Teams, Organizations & Roles.

Plan-based resource limits

internal/billing/plans.go's PlanLimits map ties a tenant's plan (free/pro/enterprise) to concrete numeric limits — active workflow count, seats, worker quota, sandbox CPU/memory. These are enforced in the handlers that create the corresponding resources, not just displayed in the UI. See Billing & Plans.

Next