Core Concepts

Understand workspaces, environments, entities, roles, and the data model that underpins every FlowOS studio.

Workspaces

A workspace is the top-level isolation boundary in FlowOS. All resources — workflows, apps, tables, incidents, users — belong to a workspace. Workspaces have their own slug, branding, domain, and billing plan.

Most SaaS customers have one workspace. Enterprises with separate business units or strict data isolation requirements create multiple workspaces. The workspace slug is used in all API calls via the X-Workspace header.

bash
# Discover your workspace
GET /api/v1/workspaces/me

# List all workspaces your token has access to
GET /api/v1/workspaces

Environments

Each workspace has up to three environments: development, staging, and production. Environments isolate data and workflow state — a workflow activated in staging doesn't run in production. Environment is specified via the X-Environment header (default: production).

Resource definitions (workflow configs, table schemas, connector configs) are promoted between environments via the FlowOS UI or the /api/v1/deployments endpoint. Data records are never promoted — only config.

All production API calls must include X-Environment: production explicitly when your token was created in a non-production context. Omitting the header defaults to production, but being explicit prevents accidents.

Entities and IDs

Every FlowOS resource is an entity with a typed, lexicographically sortable ID in the formatprefix_ULID. Prefixes indicate the resource type:

FieldTypeRequiredDefaultDescription
wf_WorkflowoptionalWorkflow definition
run_WorkflowRunoptionalA single workflow execution
app_AppoptionalApp Studio application
tbl_TableoptionalDB Studio table
rec_RecordoptionalA row in a DB table
con_ConnectoroptionalIntegration connector
inc_IncidentoptionalITSM incident
chg_ChangeoptionalChange request
prb_ProblemoptionalProblem record
usr_UseroptionalPlatform user
rol_RoleoptionalRBAC role definition
ws_WorkspaceoptionalWorkspace
wh_WebhookoptionalOutbound webhook subscription
art_ArtifactoptionalSDK artifact

Roles and Permissions

FlowOS uses role-based access control (RBAC). Roles are assigned at workspace level. Every API token inherits the permissions of the user who created it, within the scopes explicitly granted.

Built-in roles

FieldTypeRequiredDefaultDescription
viewerRoleoptionalRead-only access to all resources in the workspace
operatorRoleoptionalCan view and update records; cannot create/delete resources or change configuration
developerRoleoptionalFull access to all studios; cannot manage users or billing
adminRoleoptionalFull access including user management, settings, and API key management
superadminRoleoptionalCross-workspace access; only available to instance owners

Token scopes

When creating an API token programmatically, you can restrict it to specific scopes — even if your account has a higher role. This follows the principle of least privilege.

bash
POST /api/v1/auth/tokens
{
  "name": "CI/CD deploy token",
  "expiresIn": "90d",
  "scopes": [
    "workflows:read",
    "workflows:trigger",
    "deployments:create"
  ]
}

Available scopes

  • workflows:read, workflows:write, workflows:trigger
  • apps:read, apps:write, apps:deploy
  • tables:read, tables:write, records:read, records:write
  • integrations:read, integrations:write
  • incidents:read, incidents:write, incidents:resolve
  • changes:read, changes:write, changes:approve
  • users:read, users:write
  • audit:read
  • webhooks:read, webhooks:write
  • sdk:read, sdk:write, sdk:publish

Audit Log

Every mutating action in FlowOS — whether through the UI or API — writes an immutable audit event. Audit events include the actor identity, resource type, resource ID, action, before/after state, and timestamp. They are tamper-proof and retained for the duration of your plan.

bash
# Query audit log
GET /api/v1/audit?entityType=incident&action=created&from=2026-01-01&limit=50

# Single audit event
GET /api/v1/audit/:eventId
json
{
  "id": "aud_01HZ9MNPQRSTUV",
  "action": "created",
  "entityType": "incident",
  "entityId": "inc_01HZ4KPQRSTUV",
  "entityNumber": "INC-1042",
  "actorId": "usr_01HZ4KPQ",
  "actorName": "Alice Smith",
  "actorIp": "203.0.113.1",
  "workspaceId": "ws_01HZ4KPQRSTUV",
  "environment": "production",
  "before": null,
  "after": { "title": "API latency spike", "severity": "P1", "status": "open" },
  "createdAt": "2026-06-01T09:32:11.342Z"
}

Common Response Envelope

All API responses use a consistent JSON envelope:

json
// Single resource
{
  "data": { ...resource },
  "meta": { "requestId": "req_01HZ..." }
}

// List resource
{
  "data": [ ...resources ],
  "meta": {
    "total": 142,
    "page": 1,
    "pageSize": 20,
    "nextCursor": "cursor_abc123",
    "hasMore": true,
    "requestId": "req_01HZ..."
  }
}

// Error
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "title is required",
    "statusCode": 422,
    "fields": { "title": "required" },
    "requestId": "req_01HZ..."
  }
}

Timestamps and Timezones

All timestamps in the API are in ISO 8601 UTC format: 2026-06-01T09:32:11.342Z. Store and process them as UTC in your application. The FlowOS UI converts to local timezone for display. Filter parameters accept both full ISO 8601 and YYYY-MM-DD date strings.

Idempotency

POST and PATCH requests accept an Idempotency-Key header. Repeat requests with the same key within 24 hours return the original response without re-executing. Use this to safely retry failed requests.

bash
curl -X POST https://acme.flowos.io/api/v1/incidents \
  -H "Authorization: Bearer $FLOWOS_TOKEN" \
  -H "Idempotency-Key: client-side-uuid-here" \
  -H "Content-Type: application/json" \
  -d '{ "title": "API latency spike", "severity": "P1" }'

Workspace Provisioning Sequence

When a new tenant or workspace is created, the platform runs an automatic provisioning sequence that seeds all the baseline data required for the workspace to be operational. Understanding this sequence matters when debugging a newly created workspace or writing platform tests.

1. Tenant creation

When a tenant is first created (POST /api/v1/tenants), the platform immediately:

  • Creates the tenant record in root_tenants.
  • Calls seedCoreSystemDictionary(tenantId) — seeds the foundation field definitions (domains, choices, user_preferences, system_properties, feature_flags) into root_<tenantId>_<workspaceId>_sys_dictionaries.
  • Calls seedIntegrationDictionary(tenantId) — seeds integration-domain table definitions.
  • Calls seedUIConfigDictionary(tenantId) — seeds UI config table definitions.
  • Creates the default workspace (ws_default) if none is specified.

2. Workspace creation

When a workspace is provisioned inside an existing tenant, the platform:

  • Creates the workspace record scoped to the tenant.
  • Calls seedSystemDictionaryForPackages(tenantId, packages) — seeds field definitions for all enabled package domains (foundation, itsm_core, cmdb, etc.) into the workspace's sys_dictionaries collection.
  • Seeds default roles (admin, agent, end_user, readonly) into root_<tenantId>_<workspaceId>_roles.
  • Seeds default system_properties and feature_flags.
  • Creates the root domain node (global) in root_<tenantId>_<workspaceId>_domains.
  • The sys_counters collection is NOT pre-populated — counter records are created lazily on the first ticket of each type.

3. Collection creation

No MongoDB collections are created during provisioning. Collections are created lazily on the first write to each table. The sys_db_objects registry is updated whenever a collection is first accessed, so DB Studio's schema view reflects the live state.

The 1,151 table definitions in the seed dictionary define the schema — they do not pre-create collections. A workspace that has never had an incident has noroot_<tenantId>_<workspaceId>_incidents collection at all. Queries against empty or non-existent collections return an empty result, not an error.

4. Model factory pattern

All Mongoose models are created at query time using one of two factory functions insrc/modules/feature-registry/model-factory.ts:

typescript
// Workspace-scoped — collection: root_<tenantId>_<workspaceId>_<tableName>
// Use for all standard tables and custom tables
getWorkspaceModel<T>(tenantId, workspaceId, tableName, schema)

// Tenant-scoped (legacy/core) — collection: root_<tenantId>_<tableName>
// Use only for tables that must be shared across all workspaces of a tenant
// (e.g. core users, tenants themselves)
getCoreModel<T>(tenantId, tableName, schema, { workspaceId? })

Both functions cache the Mongoose model by collection name. Calling them repeatedly with the same arguments returns the cached model with no overhead. TheenforceTenantWorkspaceEnvelope helper (called by both factories in strict mode) automatically adds tenantId and workspaceId fields to the schema if they are not already present, and adds a compound index on them.

Always use getWorkspaceModel for new tables. getCoreModel exists only for legacy compatibility with a handful of early tables. New modules and plugins must never create bare Mongoose models with hardcoded collection names — thecollection-guard.ts write guard will throw at runtime.