This page documents the internal REST API for workflows (/rest/workflows/*), the business logic layer that backs it, and the public API (/api/v1/workflows/*). It covers workflow CRUD, activation/deactivation, archiving, manual execution triggering, sharing, and the persistence layer.
For the actual execution lifecycle of a running workflow, see Workflow Execution Lifecycle. For credential management endpoints, see Credentials API and Security. For project-based authorization and role scopes, see Project-Based Authorization and Sharing.
Component Interaction Diagram
Sources: packages/cli/src/workflows/workflows.controller.ts75-101 packages/cli/src/workflows/workflow.service.ts70-96 packages/cli/src/workflows/workflow-execution.service.ts39-53 packages/cli/src/workflows/workflow.service.ee.ts42-56
WorkflowsController is declared with @RestController('/workflows') and lives at packages/cli/src/workflows/workflows.controller.ts
| Method | Path | Handler | Scope | Notes |
|---|---|---|---|---|
POST | /workflows | create | workflow:create (project) | Creates new workflow, always inactive |
GET | /workflows | getAll | workflow:read | Uses listQueryMiddleware for filtering/pagination |
GET | /workflows/new | getNewName | workflow:create | Returns unique name |
GET | /workflows/from-url | getFromUrl | workflow:create | Fetches external JSON |
GET | /workflows/:id | getWorkflow | workflow:read | Returns scopes + checksum |
GET | /workflows/:id/exists | exists | (none) | ID existence check only, no scope check |
PATCH | /workflows/:id | update | workflow:update | Validates write lock, calls WorkflowService.update |
GET | /workflows/:id/collaboration/write-lock | getWriteLock | workflow:read | Collaboration lock state |
DELETE | /workflows/:id | delete | workflow:delete | Validates write lock |
POST | /workflows/:id/archive | archive | workflow:delete | Deactivates if needed |
POST | /workflows/:id/unarchive | unarchive | workflow:delete | ā |
POST | /workflows/:id/activate | activate | workflow:publish | Validates write lock |
POST | /workflows/:id/deactivate | deactivate | workflow:unpublish | Validates write lock |
POST | /workflows/:id/run | runManually | workflow:execute | Always loads from DB |
PUT | /workflows/:id/share | share | workflow:share / workflow:unshare | @Licensed('feat:sharing') |
PUT | /workflows/:id/transfer | transfer | workflow:move | ā |
GET | /workflows/:id/executions/last-successful | getLastSuccessfulExecution | workflow:read | ā |
POST | /workflows/with-node-types | getWorkflowsWithNodesIncluded | Owner/Admin only | Searches by node type |
Sources: packages/cli/src/workflows/workflows.controller.ts103-846
Every mutating endpoint follows this pattern:
CollaborationService.validateWriteLock().WorkflowService or EnterpriseWorkflowService.WorkflowService.getWorkflowScopes().calculateWorkflowChecksum() from n8n-workflow.CollaborationService.broadcastWorkflowUpdate().{ ...workflow, scopes, checksum }.The POST /workflows/:id/run endpoint is notable: it explicitly re-fetches workflowData from WorkflowRepository regardless of what the client sends, preventing execution of arbitrary workflow definitions.
packages/cli/src/workflows/workflows.controller.ts668-706
WorkflowService (decorated @Service()) contains the main CRUD and lifecycle business logic.
getMany()packages/cli/src/workflows/workflow.service.ts98-201
Assembles a role-filtered, paginated list of workflows:
RoleService.rolesWithScope() to get relevant DB roles.WorkflowRepository.getManyAndCountWithSharingSubquery() (or the folder variant).OwnershipService.addOwnedByAndSharedWith().RoleService.addScopes().shared field before returning.update()packages/cli/src/workflows/workflow.service.ts288-510
update(user, workflowUpdateData, workflowId, options) ā WorkflowEntity
Key steps:
| Step | Detail |
|---|---|
| Permission check | WorkflowFinderService.findWorkflowForUser(..., ['workflow:update']) |
| Archived guard | Throws BadRequestError if workflow.isArchived |
| Conflict detection | _detectConflicts(workflow, expectedChecksum) if checksum provided and forceSave = false |
| Version bump | New versionId generated if nodes or connections changed |
| Credential fixup | WorkflowHelpers.replaceInvalidCredentials() + addNodeIds() |
| Redaction policy | Stripped if user lacks workflow:updateRedactionSetting scope |
| Settings merge | Partial update: { ...workflow.settings, ...incoming.settings } |
| History save | WorkflowHistoryService.saveVersion() when version bumped |
| DB write | WorkflowRepository.update() with explicit field list (never writes active/activeVersionId) |
| Tag update | WorkflowTagMappingRepository.overwriteTaggings() |
| Re-activation | If settings changed and activeVersionId exists, calls activateWorkflow() |
| Event | EventService.emit('workflow-saved', ...) |
activateWorkflow()packages/cli/src/workflows/workflow.service.ts608-736
activateWorkflow(user, workflowId, options?, publicApi?) ā WorkflowEntity
Activation Flow Diagram
deactivateWorkflow()packages/cli/src/workflows/workflow.service.ts745-805
workflow:unpublish scope.activeVersionId === null.ActiveWorkflowManager.remove(workflowId).active = false, activeVersionId = null.WorkflowPublishHistoryRepository.workflow-deactivated.archive() / unarchive() / delete()| Method | Requires | Key Side Effects |
|---|---|---|
archive() | workflow:delete scope | Deactivates if active, saves history version, emits workflow-archived |
unarchive() | workflow:delete scope | Saves history version, emits workflow-unarchived |
delete() | workflow:delete scope, must be archived (unless force = true) | Deactivates if active, deletes binary data via BinaryDataService, emits workflow-deleted |
packages/cli/src/workflows/workflow.service.ts814-934
The _detectConflicts() method computes calculateWorkflowChecksum(storedWorkflow) and compares it against the expectedChecksum from the client. A mismatch throws ConflictError, preventing a stale-client overwrite.
WorkflowExecutionService at packages/cli/src/workflows/workflow-execution.service.ts is responsible for constructing IWorkflowExecutionDataProcess payloads and handing them off to WorkflowRunner.
executeManually()packages/cli/src/workflows/workflow-execution.service.ts102-225
Called from WorkflowsController.runManually(). Handles three distinct execution cases:
Manual Execution Decision Diagram
The function returns either { executionId: string } (execution started) or { waitingForWebhook: boolean } (execution paused awaiting a test webhook registration).
runChatTrigger()packages/cli/src/workflows/workflow-execution.service.ts227-327
Constructs an execution payload for ChatTrigger nodes. Sets executionMode: 'chat', includes chatSessionId and chatMessage, then calls WorkflowRunner.run().
runErrorWorkflow()packages/cli/src/workflows/workflow-execution.service.ts329-420
Triggered when a production workflow fails and has an errorWorkflow setting. Fetches the error workflow from the DB, builds a payload with the error context as input data, and calls WorkflowRunner.run().
WorkflowRepository at packages/@n8n/db/src/repositories/workflow.repository.ts extends TypeORM's Repository<WorkflowEntity>.
| Method | Returns | Purpose |
|---|---|---|
get(where, options?) | WorkflowEntity | null | Single workflow by condition |
getActiveIds({ maxResults? }) | string[] | IDs of all workflows with activeVersionId != null |
getActiveCount() | number | Count of active workflows |
findById(workflowId) | WorkflowEntity | null | With shared.project and activeVersion relations |
findByIds(ids, { fields? }) | WorkflowEntity[] | Batch fetch |
getManyAndCountWithSharingSubquery(user, sharingOptions, queryOptions) | { workflows, count } | Paginated list with RBAC filtering |
getWorkflowsAndFoldersWithCountWithSharingSubquery(user, sharingOptions, options) | [WorkflowFolderUnionFull[], number] | Combined workflow+folder listing |
findWorkflowsWithNodeType(nodeTypes, includeNodes) | WorkflowEntity[] | Search by node type in JSON column |
isActive(id) | boolean | Checks activeVersionId IS NOT NULL |
updateWorkflowTriggerCount(id, count) | UpdateResult | Updates trigger count for billing |
The subquery-based list methods (getManyAndCountWithSharingSubquery) use a correlated subquery on the SharedWorkflow table to filter results by the requesting user's project memberships and role slugs, avoiding N+1 queries.
packages/@n8n/db/src/repositories/workflow.repository.ts57-400
EnterpriseWorkflowService at packages/cli/src/workflows/workflow.service.ee.ts provides enterprise-only operations gated behind the feat:sharing license feature.
| Method | Purpose |
|---|---|
addOwnerAndSharings(workflow) | Attaches homeProject, sharedWithProjects to a workflow for the API response |
addCredentialsToWorkflow(workflow, user) | Resolves credential names/types visible to the user |
validateCredentialPermissionsToUser(workflow, credentials) | Ensures all workflow credentials are accessible to the creator |
preventTampering(updateData, workflowId, user) | On update, re-validates that all credentials are still accessible |
shareWithProjects(workflowId, projectIds, trx) | Creates SharedWorkflow entries with workflow:editor role |
transferWorkflow(user, workflowId, destinationProjectId, shareCredentials?, destinationParentFolderId?) | Moves ownership, optionally shares credentials |
getWorkflowIdsWithResolvableCredentials(workflowIds) | Returns IDs of workflows referencing dynamic (external secret) credentials |
packages/cli/src/workflows/workflow.service.ee.ts42-400
WorkflowFinderService is a shared helper used across the controller and service layer. Its primary method:
findWorkflowForUser(workflowId, user, requiredScopes, options?) ā WorkflowEntity | null
It queries SharedWorkflowRepository to confirm the user holds one of the requiredScopes on the workflow (via project or workflow-level role) before returning the entity. Returns null if access is denied, which callers convert to NotFoundError or ForbiddenError.
packages/cli/src/workflows/workflows.controller.ts376-433 packages/cli/src/workflows/workflow.service.ts314-327
The public API workflow handler at packages/cli/src/public-api/v1/handlers/workflows/workflows.handler.ts provides OpenAPI-documented endpoints at /api/v1/workflows/*. It reuses WorkflowService and WorkflowExecutionService but:
CollaborationService write-lock checks (no push-ref concept for API clients).scopes or checksum.publicApi: true to WorkflowService.update() and events so telemetry can distinguish the call source.Operations available: list, get, create, update, delete, activate, deactivate, execute.
calculateWorkflowChecksum(workflow) (from n8n-workflow) produces a deterministic hash of the workflow's nodes, connections, settings, and versionId. The client sends expectedChecksum on mutations; a mismatch aborts the operation with ConflictError (409). This prevents concurrent-editor overwrites.
packages/cli/src/workflows/workflows.controller.ts496-497 packages/cli/src/workflows/workflow.service.ts332-334
CollaborationService.validateWriteLock(userId, clientId, workflowId, operation) is called before every mutating endpoint (update, delete, archive, activate, deactivate). If another editor holds the write lock, the operation is rejected.
packages/cli/src/workflows/workflows.controller.ts459 packages/cli/src/workflows/workflows.controller.ts518-519
Every significant lifecycle transition emits a typed event via EventService:
| Event | Emitted By |
|---|---|
workflow-created | WorkflowsController.create |
workflow-saved | WorkflowService.update |
workflow-activated | WorkflowService._addToActiveWorkflowManager |
workflow-deactivated | WorkflowService.deactivateWorkflow |
workflow-archived | WorkflowService.archive |
workflow-unarchived | WorkflowService.unarchive |
workflow-deleted | WorkflowService.delete |
workflow-sharing-updated | WorkflowsController.share |
workflow-executed | WorkflowsController.runManually |
These events are consumed by TelemetryEventRelay and LogStreamingEventRelay. See Observability and Telemetry.
Sources: packages/cli/src/events/maps/relay.event-map.ts71-168 packages/cli/src/workflows/workflow.service.ts490-497
ExternalHooks.run() is called at several points to allow host-side customization:
| Hook | When |
|---|---|
workflow.create | Before DB insert |
workflow.afterCreate | After DB insert |
workflow.update | Before DB update |
workflow.afterUpdate | After DB update |
workflow.activate | Before ActiveWorkflowManager.add() |
workflow.delete | Before DB delete |
workflow.afterDelete | After DB delete |
packages/cli/src/workflows/workflow.service.ts386 packages/cli/src/workflows/workflow.service.ts483 packages/cli/src/workflows/workflow.service.ts521-522
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.