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:
| Table | Purpose |
|---|---|
tenants | Workspaces — slug, name, plan tier, optional organization_id. |
users | Per-tenant accounts with a role (owner/admin/member/viewer). |
organizations | Optional grouping of multiple tenants (Enterprise). |
workflow_defs | Workflow definitions (DSL, status, enabled flag, webhook API key). |
workflow_versions | Historical versions of each definition. |
workflow_templates | Reusable starter workflows for the gallery. |
workflow_executions | One row per run — status, trigger data, input/output, timing. |
step_executions | One row per step per execution — status, input/output, timing. |
workflow_api_keys | Per-workflow public API keys (/run/{id}). |
workflow_chat_deployments | Chat widget configuration per workflow. |
workflow_batch_runs / workflow_batch_items | Batch evaluation runs and per-item results. |
workflow_kv_store | Generic per-tenant key/value storage backing the store node. |
trigger_poll_state | Cursor/state for event pollers (email, Telegram, Slack, Airtable, etc.) so they don't reprocess items. |
secrets | AES-256-GCM encrypted named secrets. |
oauth_connections | AES-256-GCM encrypted OAuth tokens (Google/Notion/Slack + manual API-key connections). |
connectors / connector_auths | Connector metadata and per-tenant credential bindings. |
knowledge_bases / kb_documents / kb_chunks | RAG knowledge bases, documents, and embedded chunks (with cosine_similarity SQL function). |
agent_memories | Per-session agent conversation history. |
skills | Agent skills (SKILL.md content, scripts, references). |
api_keys | Platform-level API keys. |
sso_configs | Per-tenant SAML/OIDC configuration. |
subscriptions | Stripe subscription state per tenant. |
audit_events | Audit log of sensitive actions. |
Tenant resolution
- Authenticated requests carry a JWT containing
tenant_idanduser_id;auth.Middlewarevalidates it andauth.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.