Workflows API
Die Workflows API ermöglicht die Automatisierung von Geschäftsprozessen mit visuellen Workflows, Multi-Step-Genehmigungen, Timer-Events, Parallel-Branches, Wiederholung fehlgeschlagener Schritte, Pausieren und 7 integrierten Actions (E-Mail, Webhooks, Tickets, etc.). Templates unterstützen Versioning mit Rollback und Publishing.
Endpoints Übersicht
Workflow Instances
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/workflow/instances | Alle Instanzen abrufen (mit Filtering) |
GET | /api/workflow/instances/:id | Einzelne Instanz abrufen (mit Steps & Activities) |
GET | /api/workflow/instances/stats | Zähler im eigenen Sichtbarkeits-Scope + offene eigene Aufgaben |
POST | /api/workflow/instances/:id/steps/:stepId/complete | Step abschließen (Genehmigungsschritte laufen über /approve) |
POST | /api/workflow/instances/:id/steps/:stepId/approve | Approval-Entscheidung treffen |
POST | /api/workflow/instances/:id/cancel | Workflow abbrechen (202, ohne Body) |
POST | /api/workflow/instances/:id/pause | Workflow pausieren; bis zum Fortsetzen werden keine Schritte ausgeführt (202, ohne Body) |
POST | /api/workflow/instances/:id/resume | Workflow fortsetzen (202, ohne Body) |
POST | /api/workflow/instances/:id/steps/:stepId/retry | Fehlgeschlagenen Step wiederholen |
POST | /api/workflow/instances/:id/steps/:stepId/reassign | Step einem anderen Benutzer zuweisen |
Gestartet werden Workflows über einen der beiden Start-Pfade — Benutzer über POST /api/workflow/catalog/:templateId/start, externe Systeme per API-Key über POST /api/workflow/api/trigger/:templateId. Beide antworten 202 Accepted; die Ausführung übernimmt die workflow-engine asynchron. Ebenso cancel/pause/resume: 202 ohne Body.
Templates (CRUD & Publishing)
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/workflow/templates | Alle Templates (Filter + Pagination; ?includeDeleted=true zeigt den Papierkorb) |
GET | /api/workflow/templates/statistics | Zähler über alle Vorlagen: total, published, draft, archived, inactive (?includeDeleted) |
GET | /api/workflow/templates/categories | Distinkte Kategorien aller Vorlagen ({data: string[]}) — Quelle der Kategorie-Filter |
GET | /api/workflow/templates/:id | Einzelnes Template mit Steps |
POST | /api/workflow/templates | Template erstellen (Drag-&-Drop-Editor) |
PUT | /api/workflow/templates/:id | Template aktualisieren |
DELETE | /api/workflow/templates/:id | Template löschen (Soft-Delete) |
POST | /api/workflow/templates/:id/restore | Template wiederherstellen (verlangt restoreTemplates UND viewDeletedTemplates) |
POST | /api/workflow/templates/:id/publish | Template veröffentlichen |
POST | /api/workflow/templates/:id/unpublish | Template zurückziehen |
PUT | /api/workflow/templates/:id/permissions | Template-Berechtigungen setzen |
Versioning
| Method | Endpoint | Beschreibung |
|---|---|---|
POST | /api/workflow/templates/:id/versions | Neue Version erstellen (Semver, Changelog) |
POST | /api/workflow/templates/:name/rollback | Auf frühere Version zurücksetzen |
POST | /api/workflow/templates/:id/publish · /unpublish | Veröffentlichen / zurückziehen |
Meine Tasks
Die offenen Workflow-Schritte eines Benutzers liefert
GET /api/my-tasks?types=WORKFLOW_STEP. Instanz-Statistiken liefertGET /api/workflow/instances/stats.
Activity & Audit
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/workflow/activity/history | Globale Workflow-History |
GET | /api/workflow/activity/statistics | Workflow-Statistiken |
| Activities einer einzelnen Instanz kommen eingebettet aus GET /api/workflow/instances/:id. | ||
Workflow Catalog
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/workflow/catalog/list | Startbare Workflows: flache Liste ({data, pagination}, Search & Kategorie-Filter) |
POST | /api/workflow/catalog/:templateId/start | Workflow aus Catalog starten (202) |
API-Trigger (Externe Systeme)
| Method | Endpoint | Beschreibung |
|---|---|---|
POST | /api/workflow/api/trigger/:templateId | Workflow via API-Key triggern |
Workflow-Konzepte
Templates vs. Instances
WorkflowTemplate: ├─ Blueprint/Definition (wiederverwendbar) ├─ Enthält Step-Definitionen (Array von Nodes) ├─ Versioniert (v1, v2, v3...) └─ Kann published/unpublished sein WorkflowInstance: ├─ Konkrete Ausführung eines Templates ├─ Hat eigene Runtime-Daten (data, variables) ├─ Erzeugt StepExecutions (eine pro Step) ├─ Status: DRAFT → RUNNING → COMPLETED/FAILED/CANCELLED └─ Hat Activities (Audit-Trail)
Workflow-Status
| Status | Beschreibung |
|---|---|
DRAFT | Erstellt, aber noch nicht gestartet |
RUNNING | Aktiv laufend, Steps werden ausgeführt |
PAUSED | Temporär pausiert, kann fortgesetzt werden |
COMPLETED | Erfolgreich abgeschlossen |
CANCELLED | Vom Benutzer abgebrochen |
FAILED | Fehlgeschlagen (z.B. Approval ohne Eskalation rejected) |
Step-Status
| Status | Beschreibung |
|---|---|
PENDING | Noch nicht erreicht |
ASSIGNED | Zugewiesen, wartet auf Benutzer-Aktion |
IN_PROGRESS | Wird gerade bearbeitet |
COMPLETED | Abgeschlossen |
REJECTED | Abgelehnt (nur bei Approval) |
FAILED | Fehlgeschlagen (z.B. Webhook-Error) |
SKIPPED | Übersprungen (z.B. nicht gewählter Conditional Branch) |
8 Node-Typen (Step-Typen)
1. MANUAL_TASK
Manuelle Aufgabe, die einem Benutzer oder einer Gruppe zugewiesen wird. Der Benutzer muss die Aufgabe manuell abschließen.
Eigenschaften: • Assignment: User, Group oder Role-basiert • Deadlines: Optional (z.B. "in 24 Stunden") • Notifications: Automatisch via Unified System • Instructions: Anweisungen für den Benutzer Ablauf: 1. Step is set to ASSIGNED 2. User receives notification 3. User completes task (POST .../complete) 4. Step changes to COMPLETED 5. Next step is triggered
2. APPROVAL
Genehmigungsschritt mit Auto-Approve/Reject-Logik, Eskalations-Support und manueller Entscheidung.
Features: • Auto-Approve: Field-basiert (z.B. "wenn Betrag < 5000") • Auto-Reject: Hat höhere Priorität als Auto-Approve • Eskalation: Bei jeder Ablehnung zum konfigurierten Step (escalationStepId) • Manual Approval: Wenn keine Auto-Regel greift • failOnReject: Workflow FAILED bei Ablehnung — Eskalation geht vor Ablauf (Auto-Reject): 1. Check auto-reject condition (FIRST priority) 2. If TRUE: Reject + escalation (if configured) 3. If escalation missing: Workflow FAILED Ablauf (Auto-Approve): 1. Check auto-reject condition (FALSE) 2. Check auto-approve condition (SECOND priority) 3. If TRUE: Immediately APPROVED + next step Ablauf (Manual): 1. No auto-rule applies 2. Step is set to ASSIGNED 3. Approver receives notification 4. Approver decides (POST .../approve) 5. On APPROVED: Next step 6. On REJECTED: Escalation or Workflow FAILED
3. AUTOMATED_ACTION
Automatisierte Aktion - führt eine von 7 integrierten Actions aus.
7 Action-Typen:
1. send_email = E-Mail senden (via Notification-Worker)
2. webhook = HTTP-Request (POST/GET/PUT), SSRF-Protected
3. create_ticket = Neues Ticket erstellen
4. update_ticket = Ticket aktualisieren (Status, Priority, Assignee)
5. add_ticket_comment = Kommentar zu Ticket hinzufügen
6. update_field = Workflow-Feld aktualisieren (data)
7. assign_ticket = Ticket automatisch zuweisen (via Assignment-Engine)
Sicherheit:
• SSRF-Protection: Webhooks blockieren private IPs (127.0.0.1, 10.x, 192.168.x)
• Timeout: Default 10s, konfigurierbar bis 60s
• Redirects: Deaktiviert (redirect: "error")
• Variable Replacement: {{"{{"}}field{{"}}"}} wird durch workflow.data.field ersetzt
Ablauf:
1. Step changes to IN_PROGRESS
2. Action is executed (with timeout protection)
3. On success: COMPLETED + outputData saved
4. On error: FAILED + errorMessage
5. Next step is triggered (only on success)
4. NOTIFICATION
Sendet Benachrichtigungen an Benutzer oder Gruppen (via Unified Notification System).
Empfänger-Typen:
• userIds: Array von User-IDs
• groupId: Alle Mitglieder einer Gruppe
• roleName: Alle User mit dieser Rolle
• notifyInitiator: Workflow-Initiator benachrichtigen
• recipientField: Dynamisch aus workflow.data lesen
Notification-Typen:
• task: Allgemeine Task-Notification (Standard)
• reminder: Reminder-Notification (z.B. "Deadline in 2h")
• escalation: Eskalations-Notification
Ablauf:
1. Recipients are resolved (deduplicated)
2. For each recipient: Notification via unified system
3. Step completes immediately (no waiting)
4. outputData: { notificationsSent: X, totalRecipients: Y }
5. PARALLEL_GATEWAY
Startet mehrere Workflow-Branches parallel. Alle Branches werden gleichzeitig ausgeführt.
Eigenschaften: • nextSteps: Array von Step-IDs (Parallel-Branches) • Keine Synchronisation am Ende (kein "Join") • Workflow ist COMPLETED wenn ALLE Branches completed sind Ablauf: 1. Gateway step changes to COMPLETED 2. WorkflowEngine starts ALL nextSteps simultaneously 3. Each branch runs independently 4. Workflow status stays RUNNING until all branches done Beispiel: Gateway → [Branch A: Legal Approval, Branch B: Finance Approval] Both approvals run in parallel, independently of each other
6. CONDITIONAL_BRANCH
Bedingte Verzweigung - wählt einen von mehreren Pfaden basierend auf Conditions.
18 unterstützte Operatoren:
Gleichheit: equals, notEquals
Numerisch: greaterThan, lessThan, greaterOrEqual, lessOrEqual, between
Text: contains, notContains, startsWith, endsWith, matches (regex)
Liste: in, notIn
Leer/Null: isNull, isNotNull, isEmpty, isNotEmpty
in und notIn erwarten eine nicht-leere Werte-Liste, between genau zwei Grenzwerte;die vier Leer/Null-Operatoren lesen value nicht. Mit valueType (string,number, boolean) werden beide Seiten vor dem Vergleich umgewandelt — ohnediese Angabe vergleichen equals/notEquals streng ("5" ist nicht 5).
Komplexe Conditions (Nested):
{
"type": "AND",
"conditions": [
{ "type": "field", "field": "trigger.priority", "operator": "equals", "value": "HIGH" },
{ "type": "field", "field": "trigger.amount", "operator": "greaterThan", "value": 10000 }
]
}
Ablauf:
1. Conditions are evaluated in order
2. First match = Selected branch
3. No match = Default branch (if configured)
4. No match + no default = FAILED
5. Selected branch is triggered (overrideNextSteps)
Sicherheit:
• Max Recursion Depth: 10 (prevents stack overflow)
• Max Field Depth: 10 (prevents DoS via "a.b.c.d.e...")
• Dangerous keys blocked: __proto__, constructor, prototype
• Regex limit: 500 characters
7. DATA_COLLECTION
Formular-Schritt - präsentiert ein Formular und sammelt Benutzer-Input.
Eigenschaften:
• formSchema: JSON-Schema für Formular-Felder
• prefillFields: Felder vorausfüllen aus workflow.data
• Validierung: serverseitig beim Abschließen, gegen das formSchema
Ablauf:
1. Step is set to ASSIGNED
2. User receives notification
3. User fills out form
4. POST .../complete with data
5. Backend validates data against formSchema
6. On success: the validated values land in data.stepOutputs[stepName]
7. Step COMPLETED + next step
Beispiel formSchema:
{
"requestReason": {
"type": "textarea",
"label": "Reason",
"required": true,
"minLength": 50
},
"urgency": {
"type": "select",
"label": "Urgency",
"options": ["LOW", "MEDIUM", "HIGH"]
}
}
8. TIMER_EVENT
Timer-Schritt - verzögert den Workflow um eine feste Zeit oder wartet bis zu einem bestimmten Zeitpunkt.
3 Timer-Typen (timerType):
1. duration = Relative Verzögerung ab Start des Schritts (z.B. "3 Stunden")
• days: 0
• hours: 3
• minutes: 30
• seconds: 0
2. datetime = Absoluter Zeitpunkt, ISO-8601 mit Zeitzone (Z oder ±HH:MM)
• datetime: "2026-02-01T09:00:00Z"
3. expression = Zeitpunkt aus den Workflow-Daten; der aufgelöste Wert muss eine Zeitzone tragen
• expression: "{{trigger.dueDate}}"
Ablauf:
1. Step changes to IN_PROGRESS
2. TimerJob is created (persistent in DB)
3. Step waits (shouldComplete = false)
4. TimerChecker service checks every 30s for due timers
5. When executeAt reached: Step is set to COMPLETED
6. Next step is triggered
Besonderheit:
• Delay = 0 or timestamp already past → Immediate completion
• No CRON support (use the CronJob system for that)
API-Beispiele
Workflow starten
POST /api/workflow/catalog/:templateId/start
{
"data": {
"requestType": "Hardware",
"amount": 12500,
"justification": "New laptops for development team"
}
}
data wird gegen das triggerSchema der Vorlage validiert (Pflichtfelder → 400 FORM_VALIDATION_FAILED) und landet auf der Instanz unter data.trigger. Geprüft werden außerdem die Start-Berechtigung des Templates, das Limit gleichzeitiger Instanzen pro Benutzer und die Workflow-Daten-Limits.
Response (202 Accepted)
{
"message": "Workflow started successfully",
"templateId": "clx...",
"templateName": "Hardware Purchase Request"
}
Der Start läuft asynchron über die workflow-engine — die Antwort trägt daher noch keine Instanz-ID. Die entstandene Instanz erscheint in GET /api/workflow/instances.
Manual Task abschließen
POST /api/workflow/instances/:instanceId/steps/:stepId/complete
{
"outcome": "COMPLETED",
"data": {
"budgetLineItem": "IT-Equipment-2026",
"approvalCode": "FIN-2026-045"
}
}
outcome ∈ APPROVED | REJECTED | COMPLETED | FAILED (optional). data trägt die Eingaben des Schritts — bei DATA_COLLECTION die Formularfelder, die gegen das formSchema validiert werden. Antwort: die abgeschlossene StepExecution ({id, status, completedAt}).
Ein APPROVAL-Schritt wird über /approve entschieden, nicht über diesen Endpoint (400 APPROVAL_STEP_REQUIRES_DECISION). Damit nimmt jede Genehmigungs-Entscheidung denselben Weg — mit derselben Rechteprüfung, demselben Kommentar-Zwang und demselben Verhalten bei Ablehnung.
Approval-Entscheidung
POST /api/workflow/instances/:instanceId/steps/:stepId/approve
{
"decision": "APPROVED",
"comment": "Approved based on business justification and available budget"
}
// OR
{
"decision": "REJECTED",
"comment": "Budget exceeded for Q1, resubmit in Q2"
}
decision ist genau APPROVED oder REJECTED (400 INVALID_APPROVAL_DECISION). Der Entscheider braucht zusätzlich zur Zuweisung das Recht workflows.completeSteps. Ist am Schritt requireRejectionComment gesetzt, verlangt eine Ablehnung einen Kommentar (400 REJECTION_COMMENT_REQUIRED). Antwort: {decision, complete} — complete=false heißt, dass weitere Zustimmungen ausstehen. Der Kommentar — bei Zustimmung wie bei Ablehnung — landet im Vorgang unter data.stepOutputs[Schritt-Name].comments und ist dort in der Übersicht und im Aktivitäts-Verlauf sichtbar.
Workflow via API-Key triggern (Externe Systeme)
POST /api/workflow/api/trigger/:templateId
X-API-Key: your-api-key-here
{
"data": {
"externalSystemId": "SAP-12345",
"purchaseOrderNumber": "PO-2026-1234",
"amount": 25000,
"vendor": "Dell Technologies"
},
"metadata": {
"source": "SAP",
"correlationId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-01-27T15:30:00Z"
}
}
- Auth: X-API-Key Header. Die Rolle des API-Keys braucht das Recht workflows.startWorkflow UND muss — sofern das Template Start-Berechtigungen führt — in dessen allowedRoles stehen (allowedUsers greift nur für Benutzer-Starts, nicht für API-Keys; sind beide Listen leer, genügt das globale Recht).
- Body:
data(optional, gegen das triggerSchema des Templates validiert, Größen-Limit; landet in der Instanz unterdata.trigger) ·metadata(optional: source, correlationId (UUID), timestamp) - Response:
202 Accepted— die Instanz wird erstellt und asynchron von der workflow-engine abgearbeitet. - Die Vorlage muss triggerType API tragen — bei jedem anderen Wert antwortet die Route 400 INVALID_TRIGGER_TYPE. Das Feld kennt genau zwei Werte: MANUAL (Start über den Katalog) und API (Start durch ein externes System); ein anderer Wert wird beim Speichern der Vorlage mit 400 abgelehnt.
Workflow Catalog abrufen
GET /api/workflow/catalog/list?category=Procurement&search=hardware&page=1&limit=20
Response
{
"data": [
{
"id": "clx...",
"name": "Hardware Purchase Request",
"description": "Multi-step approval for hardware purchases > €5,000",
"category": "Procurement",
"version": 3,
"isActive": true,
"isPublished": true,
"triggerType": "MANUAL",
"triggerSchema": { "fields": [ "..." ] }
}
],
"pagination": { "page": 1, "limit": 20, "total": 7, "totalPages": 1, "hasMore": false }
}
Der Katalog ist die Nutzer-Fläche: Er verlangt workflows.startWorkflow und liefert ausschließlich veröffentlichte, aktive Vorlagen, die der Aufrufer auch tatsächlich starten darf (allowedRoles/allowedUsers). Die Vorlagen-VERWALTUNG (GET /api/workflow/templates mit den vollständigen Step-Definitionen) erfordert dagegen workflows.viewTemplates.
Instanz mit Details abrufen
GET /api/workflow/instances/:id
Response (Full Instance)
{
"id": "clx...",
"templateId": "clx...",
"status": "RUNNING",
"priority": "HIGH",
"currentSteps": ["step-3"],
"completedSteps": {
"step-1": { "status": "COMPLETED", "outcome": "COMPLETED", "startedAt": "2026-01-27T15:30:00Z", "completedAt": "2026-01-27T15:35:00Z", "errorMessage": null },
"step-2": { "status": "COMPLETED", "outcome": "APPROVED", "startedAt": "2026-01-27T15:35:00Z", "completedAt": "2026-01-27T16:20:00Z", "errorMessage": null }
},
"data": {
"trigger": { "requestType": "Hardware", "amount": 12500 },
"variables": { "budgetLineItem": "IT-Equipment-2026" },
"stepOutputs": {
"Submit Request": { "stepType": "DATA_COLLECTION", "budgetLineItem": "IT-Equipment-2026" },
"Finance Approval": { "stepType": "APPROVAL", "outcome": "APPROVED", "autoApproved": true }
}
},
"stepDefinitions": [ "... snapshot of the steps at start ..." ],
"templateVersion": 3,
"template": {
"id": "clx...",
"name": "Hardware Purchase Request",
"category": "Procurement"
},
"stepExecutions": [
{
"id": "clx...",
"stepId": "step-1",
"stepName": "Submit Request",
"stepType": "DATA_COLLECTION",
"status": "COMPLETED",
"assignedToId": "clx...",
"completedAt": "2026-01-27T15:35:00Z",
"outputData": {
"budgetLineItem": "IT-Equipment-2026"
}
},
{
"id": "clx...",
"stepId": "step-2",
"stepName": "Finance Approval",
"stepType": "APPROVAL",
"status": "COMPLETED",
"outcome": "APPROVED",
"assignedToId": "clx...",
"completedAt": "2026-01-27T16:20:00Z",
"outputData": {
"autoApproved": true,
"reason": "Amount below auto-approval threshold"
}
},
{
"id": "clx...",
"stepId": "step-3",
"stepName": "Manager Approval",
"stepType": "APPROVAL",
"status": "ASSIGNED",
"assignedToId": "clx...",
"assignedAt": "2026-01-27T16:20:00Z"
}
],
"activities": [
{
"id": "clx...",
"action": "WORKFLOW_STARTED",
"timestamp": "2026-01-27T15:30:00Z",
"user": {
"id": "clx...",
"name": "John Doe",
"email": "john@example.com"
}
},
{
"id": "clx...",
"action": "STEP_COMPLETED",
"timestamp": "2026-01-27T15:35:00Z",
"details": {
"stepName": "Submit Request",
"outcome": "COMPLETED"
}
},
{
"id": "clx...",
"action": "STEP_AUTO_APPROVED",
"timestamp": "2026-01-27T16:20:00Z",
"details": {
"stepName": "Finance Approval",
"reason": "Amount below auto-approval threshold"
}
}
],
"initiator": {
"id": "clx...",
"name": "John Doe",
"email": "john@example.com"
},
"startedAt": "2026-01-27T15:30:00Z",
"createdAt": "2026-01-27T15:30:00Z",
"updatedAt": "2026-01-27T16:20:00Z"
}
Datenmodell eines laufenden Workflows
WorkflowInstance.data hat genau drei Bereiche: trigger, variables und stepOutputs. Die Schlüssel von stepOutputs sind die Step-Namen, deshalb müssen Step-Namen pro Vorlage eindeutig sein. Jeder Eintrag trägt stepType plus die im Step gesammelten Ausgaben (z.B. comments bei MANUAL_TASK und bei der Entscheidung eines APPROVAL-Schritts, Formularfelder bei DATA_COLLECTION, ein checklist-Objekt bei Checklisten).
// WorkflowInstance.data
{
"trigger": { /* Trigger/start data from triggerSchema */ },
"variables": { /* Workflow variables */ },
"stepOutputs": {
"Step 1": { "stepType": "MANUAL_TASK", "comments": "Ok cool" },
"Step 2": { "stepType": "DATA_COLLECTION", "roomNumber": "option4" },
"Finance Approval": { "stepType": "APPROVAL", "comments": "Budget confirmed" },
"Step 3": {
"stepType": "MANUAL_TASK",
"comments": "Rejected",
"checklist": { "items": ["..."], "checked": ["..."], "totalCount": 5, "completedCount": 5 }
}
}
}
Zusätzlich auf der Instanz: currentSteps[] (aktive Step-IDs), completedSteps (Map Step-ID → {status, outcome, startedAt, completedAt, errorMessage}), sowie stepDefinitions (Snapshot der Step-Definitionen beim Start) + templateVersion — so brechen Template-Änderungen laufende Instanzen nicht. Die eingebettete template-Relation enthält nur id, name und category; die Schritt-Struktur des Vorgangs steht in stepDefinitions.
Templating/Pfade greifen auf dieses Modell zu: {{ trigger.x }}, {{ variables.y }}, {{ stepOutputs["Step Name"].field }}
(Punkt- oder Bracket-Notation).
StepExecution
Pro Step (eindeutig je Instanz, [workflowInstanceId, stepId]) existiert eine StepExecution:
- status: PENDING, ASSIGNED, IN_PROGRESS, COMPLETED, REJECTED, FAILED, SKIPPED · outcome: APPROVED, REJECTED, COMPLETED, FAILED
- assignmentType: user, group, system, initiator, initiator_manager, previous_step_user, dynamic (+ assignedToId / assignedRole / assignedGroup)
- inputData, outputData, formData, formSchema (für DATA_COLLECTION), validationErrors, retryCount, comments/rejectionReason
- Beim Abschluss wird outputData zusätzlich nach data.stepOutputs[stepName] gespiegelt (Audit-Trail).
Workflow-Engine (Container)
Die Ausführung übernimmt ein eigener Container (workflow-engine). Nach außen bietet er nur /health; alle Daten liest und schreibt er über die interne Backend-API: Er liest laufende und überfällige Instanzen und Steps, legt die Folge-Schritte an, schließt Steps ab oder markiert sie als fehlgeschlagen, führt Variablen zusammen und verarbeitet Timer und Approvals. Jeder Schritt-Typ hat einen eigenen Ausführungsbaustein (automated, approval, manual, notification, gateway, dataCollection, timer).
- Timer – prüft alle 30 Sekunden, welche TIMER_EVENT-Steps ihren Zeitpunkt (executeAt) erreicht haben
- SLA-Überwachung – überfällige Steps/Instanzen, Eskalation
- Zuweisung – dynamische Zuweisung (user/role/group, Templating gegen trigger/variables/stepOutputs)
- Auto-Approval – Auto-Approve/Reject per Bedingung
Advanced Features
Auto-Approval Logic
Approval-Steps können automatisch approved/rejected werden basierend auf Conditions:
{
"type": "APPROVAL",
"name": "Finance Approval",
"config": {
"autoApproveConditions": [
{
"type": "field",
"field": "trigger.amount",
"operator": "lessThan",
"value": 5000
}
],
"autoRejectConditions": [
{
"type": "field",
"field": "trigger.priority",
"operator": "in",
"value": ["URGENT", "HIGH"]
}
],
"autoRejectLogic": "OR",
"failOnReject": true,
"escalationStepId": "step-escalation"
}
}
Neben type: "field" kennen Auto-Entscheidungen auch type: "permission" (Recht) und type: "role" (Rolle); checkPermissionsFor wählt dabei, wer geprüft wird — der Initiator (Standard) oder der Bearbeiter des Schritts. Mehrere Bedingungen verknüpft autoApproveLogic mit AND (Standard) oder OR, autoRejectLogic mit OR (Standard) oder AND — eine Ablehnung genügt also im Zweifel schon aus einem Grund.
Bedingungen müssen auswertbar sein: Beim Speichern einer Vorlage prüft die API jede Bedingung der Auto-Entscheidungen und jede Pfad-Bedingung eines CONDITIONAL_BRANCH. Unvollständige Bedingungen lehnt sie mit 400 INVALID_STEP_CONDITIONS ab; details.conditionErrors nennt je Zeile den Schritt, die Fläche (autoApprove, autoReject, branchPath), die Zeilennummer und den Grund — fehlendes Feld, fehlender oder unbekannter Operator, fehlendes Recht bzw. fehlende Rolle, fehlende Werte-Liste bei in/notIn, unvollständige Grenzen bei between, leere AND/OR-Gruppe oder eine Verschachtelung über zehn Ebenen.
Der Grund für die Strenge: Eine unvollständige Bedingung liefert zur Laufzeit immer dasselbe Ergebnis. Sie gilt deshalb als nicht erfüllt — eine Auto-Genehmigung greift dann nicht, ein Verzweigungs-Pfad wird nicht gewählt. Was nichts bewirken kann, soll gar nicht erst gespeichert werden.
Webhook mit SSRF-Protection
{
"type": "AUTOMATED_ACTION",
"name": "Notify External System",
"config": {
"actionType": "webhook",
"actionConfig": {
"url": "https://external-system.com/api/webhook",
"method": "POST",
"headers": {
"Authorization": "Bearer {{"{{"}}apiToken{{"}}"}}",
"X-Event-Type": "workflow.completed"
},
"body": {
"workflowId": "{{"{{"}}workflowId{{"}}"}}",
"status": "{{"{{"}}status{{"}}"}}",
"completedAt": "{{"{{"}}completedAt{{"}}"}}"
},
"timeout": 15000
}
}
}
Hinweis: Webhook-URLs werden validiert:
- Private IPs blockiert (127.0.0.1, 10.x, 192.168.x)
- DNS-Resolution-Check gegen SSRF
- Redirects deaktiviert
- Timeout-Protection (Default: 10s, Max: 60s)
Parallel Approval
{
"type": "PARALLEL_GATEWAY",
"name": "Multi-Department Approval",
"nextSteps": [
"step-legal-approval",
"step-finance-approval",
"step-it-approval"
]
}
// All 3 approvals run in parallel
// Workflow is COMPLETED when ALL 3 are approved
Conditional Routing
{
"type": "CONDITIONAL_BRANCH",
"name": "Route by Amount",
"config": {
"paths": [
{
"id": "high-value",
"name": "CFO",
"condition": {
"type": "field",
"field": "trigger.amount",
"operator": "greaterThan",
"value": 50000
},
"nextSteps": ["step-cfo-approval"]
},
{
"id": "medium-value",
"name": "Manager",
"condition": {
"type": "field",
"field": "trigger.amount",
"operator": "between",
"value": [10000, 50000]
},
"nextSteps": ["step-manager-approval"]
},
{
"id": "rest",
"name": "Default",
"isDefault": true,
"nextSteps": ["step-auto-approve"]
}
]
}
}
Die Pfade werden der Reihe nach geprüft, der erste Treffer gewinnt; Pfade mit isDefault greifen erst, wenn keine Bedingung passt. Ein Default-Pfad braucht selbst keine Bedingung — er ist der einzige Pfad, der ohne auskommt. Passt nichts und gibt es keinen Default-Pfad, schlägt der Schritt fehl.
Timer mit Notification
{
"steps": [
{
"id": "step-1",
"type": "APPROVAL",
"name": "Approval Required",
"nextSteps": ["step-2"]
},
{
"id": "step-2",
"type": "TIMER_EVENT",
"name": "Wait 24 Hours",
"config": {
"timerType": "duration",
"hours": 24
},
"nextSteps": ["step-3"]
},
{
"id": "step-3",
"type": "NOTIFICATION",
"name": "Reminder",
"config": {
"notifyInitiator": true,
"message": "Your request has been pending for 24 hours"
},
"nextSteps": ["step-4"]
}
]
}
Filtering & Pagination
Instanzen filtern
GET /api/workflow/instances?status=RUNNING&priority=HIGH&templateId=clx...&search=purchase&page=1&limit=20&sortBy=startedAt&sortOrder=desc
| Parameter | Beschreibung |
|---|---|
status | DRAFT, RUNNING, PAUSED, COMPLETED, CANCELLED, FAILED |
priority | LOW, MEDIUM, HIGH, URGENT |
templateId | Filter nach Template |
initiatorId | Filter nach Initiator |
category | Filter nach Vorlagen-Kategorie |
search | Suche in Template-Namen |
page | Seite (Default: 1) |
limit | Items pro Seite (Default: 50, Max: 100) |
sortBy | startedAt, status oder templateName (Default: startedAt) |
sortOrder | asc oder desc (Default: desc) |
Permissions
| Permission | Beschreibung |
|---|---|
workflows.startWorkflow | Katalog sehen und Workflows starten (Nutzer-Fläche; auch für API-Key-Trigger) |
workflows.viewOwnInstances | Eigene Workflow-Instanzen sehen |
workflows.viewAllInstances | Alle Workflow-Instanzen sehen |
workflows.cancelInstances | Instanzen abbrechen/pausieren/fortsetzen und fehlgeschlagene Steps wiederholen |
workflows.completeSteps | Zugewiesene Schritte abschließen und Approval-Entscheidungen buchen |
workflows.reassignSteps | Schritte einem anderen Benutzer zuweisen |
workflows.assignable | Kann Workflow-Schritte zugewiesen bekommen |
workflows.viewTemplates | Vorlagen-VERWALTUNG: Vorlagen-Liste, Statistik und Definitionen sehen |
workflows.createTemplates | Vorlagen erstellen |
workflows.editTemplates | Vorlagen bearbeiten, versionieren und Start-Berechtigungen setzen |
workflows.publishTemplates | Vorlagen veröffentlichen/zurückziehen |
workflows.deleteTemplates | Vorlagen löschen (Soft-Delete) |
workflows.viewDeletedTemplates | Papierkorb sehen (?includeDeleted auf Liste und Statistik) |
workflows.restoreTemplates | Gelöschte Vorlagen wiederherstellen — zusammen mit viewDeletedTemplates |
Nutzung und Verwaltung sind getrennt berechtigt: startWorkflow deckt Katalog, Start und die eigenen Aufgaben ab, viewOwn/viewAllInstances die laufenden Vorgänge — die vollständigen Vorlagen-Definitionen (inklusive Webhook-Konfiguration der AUTOMATED_ACTION-Schritte) sieht nur, wer viewTemplates trägt.
Zwei Rechte gelten zusätzlich nur für sichtbare Instanzen: Abbrechen, Pausieren, Fortsetzen und Retry wirken nur auf Instanzen, die der Aufrufer sehen darf (als Initiator oder mit viewAllInstances). Dasselbe prüft Reassign zusätzlich zum Recht.
Error-Handling
Häufige Fehler
| Error Code | HTTP Status | Beschreibung |
|---|---|---|
WORKFLOW_TEMPLATE_NOT_FOUND | 404 | Template-ID existiert nicht |
TEMPLATE_INACTIVE | 403 | Template ist nicht aktiv |
TEMPLATE_NOT_PUBLISHED | 403 | Template nicht published |
TEMPLATE_ARCHIVED | 403 | Template ist archiviert |
CANNOT_EDIT_PUBLISHED_TEMPLATE | 400 | Veröffentlichte Vorlagen sind gesperrt — neue Version erstellen (Ausnahme: isActive und die Start-Berechtigungen) |
INVALID_PERMISSIONS_CONFIG | 400 | Start-Berechtigungen ungültig; details.validationErrors nennt je Zeile Code + Parameter |
INVALID_STEP_CONDITIONS | 400 | Unvollständige Bedingung in einem Schritt; details.conditionErrors nennt je Zeile Schritt, Fläche und Grund |
ESCALATION_STEP_NOT_FOUND | 400 | escalationStepId verweist auf einen Schritt, den die Vorlage nicht enthält |
ESCALATION_CYCLE_DETECTED | 400 | Die Eskalations-Kette führt im Kreis; details.path zeigt den Ring |
WORKFLOW_INSTANCE_NOT_FOUND | 404 | Instanz-ID existiert nicht |
STEP_NOT_ASSIGNED | 403 | Step ist nicht dir zugewiesen |
STEP_ALREADY_COMPLETED | 409 | Step bereits abgeschlossen |
STEP_INVALID_STATUS | 400 | Step ist nicht ASSIGNED/IN_PROGRESS |
WORKFLOW_NOT_RUNNING | 400 | Workflow ist nicht RUNNING |
FORM_VALIDATION_FAILED | 400 | Form-Daten ungültig |
APPROVAL_STEP_REQUIRES_DECISION | 400 | Genehmigungsschritt über /complete statt über /approve angesprochen |
INVALID_APPROVAL_DECISION | 400 | decision ist nicht APPROVED/REJECTED |
REJECTION_COMMENT_REQUIRED | 400 | Ablehnung ohne Kommentar, obwohl der Schritt ihn verlangt |
WORKFLOW_DATA_LIMIT_EXCEEDED | 400 | Workflow-Daten > 1MB |
CONCURRENT_MODIFICATION | 409 | Gleichzeitige Änderung erkannt (Optimistic Locking) |
Fehler-Beispiel
{
"error": "Step not assigned to you",
"errorCode": "STEP_NOT_ASSIGNED",
"message": "You cannot complete this step as it is assigned to user clx...",
"statusCode": 403
}
Best Practices
💡 Tipps
1. Workflow-Design
- • Verwende PARALLEL_GATEWAY für unabhängige Approvals (schneller)
- • Setze Auto-Approve für Low-Risk Approvals (< €5k)
- • Verwende CONDITIONAL_BRANCH statt mehrerer Workflows
- • Eskalation immer konfigurieren (verhindert FAILED-Status)
2. Performance
- • Halte workflow.data < 500KB (Limit: 1MB)
- • Verwende Webhook-Timeouts (Default: 10s, Max: 60s)
- • Vermeide tief verschachtelte Conditions (Max: 10 Levels)
3. Sicherheit
- • Keine Secrets in Workflow-Daten (trigger, variables, stepOutputs) speichern; Zugangsdaten für Webhooks gehören in die Step-Konfiguration der Vorlage, die nur mit workflows.viewTemplates sichtbar ist
- • Webhook-URLs validieren (SSRF-Protection aktiv)
- • API-Keys für externe Trigger rotieren (alle 90 Tage)
- • Permissions prüfen vor Template-Publish
4. Monitoring
- • Activity Log für Audit-Trail nutzen
- • Monitoring auf FAILED/PAUSED Workflows
- • SLA-Monitoring für zeitkritische Workflows
Technical Details
Architecture
┌─────────────────────────────────────────────────────────────┐
│ FRONTEND (React) │
│ • Visual Workflow Designer (React Flow) │
│ • Instance Monitoring Dashboard │
│ • Step Action UI (Complete, Approve, Form) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ BACKEND API (Node.js) │
│ • /api/workflow/instances (CRUD) │
│ • /api/workflow/catalog (Browse & Start) │
│ • /api/workflow/api/trigger (External Systems) │
│ • Validation (Zod), RBAC, Activity Logging │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ WORKFLOW-ENGINE (Separate Container) │
│ • WorkflowEngine: State Machine, Step Orchestration │
│ • StepExecutorFactory: Routes to correct Executor │
│ • 8 Step Executors (Manual, Approval, Automated, etc.) │
│ • ConditionEvaluator: 18 Operators │
│ • AutoApprovalService: Field/Permission/Role Logic │
│ • TimerChecker: Polls Timer Jobs every 30s │
│ • Redis PubSub: workflow:step:complete Events │
└─────────────────────────────────────────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ NOTIFICATION │ │ JOB-WORKER │
│ WORKER │ │ (CronJobs) │
│ │ │ │
│ • Email │ │ • Timer Jobs │
│ • In-App │ │ • Scheduled │
└─────────────────┘ └─────────────────┘
Versioning
Workflows unterstützen Versioning auf Template-Ebene.
Hinweis: Laufende Instanzen verwenden immer die Template-Version, mit der sie gestartet wurden. Template-Änderungen beeinflussen nur neue Instanzen.
Attachments
Workflows nutzen das Unified Attachment System für Approval-Documents, Supporting-Documents, etc.:
# Upload file to workflow
POST /api/attachments/WORKFLOW/:workflowId
# All attachments of a workflow
GET /api/attachments/WORKFLOW/:workflowId
Details: Siehe Attachments & File Settings API für Zero-Trust Virus-Scan, File-Settings und Retention-Policies.