Appearance
ODP Approval Modes
ODP supports two distinct approval modes. Both use the same Workflow Engine under the hood but differ in what gets approved and how the main item is affected during review.
At a Glance
| Content Moderation | Direct Approval | |
|---|---|---|
| Use case | Review content changes before publishing | Process approval, document sign-off, status transitions |
| Trigger | versions.submit | items.create, items.update, or manual |
| Main item during review | Unchanged — changes live in a draft version | Directly affected — item already exists/changed |
| On approve | Version delta applied to main item (promote) | Workflow completes, optional action steps run |
| On reject | Main item untouched, version marked rejected | Workflow follows reject path, item already has the data |
| Who creates the change | Author creates a version (draft) | Author edits the item directly |
| Versioning required | Yes | No |
Mode 1: Content Moderation (Version-Based)
When to use: Published content that must be reviewed before changes go live — articles, product pages, legal documents, landing pages.
Key principle: The published item never changes until a reviewer approves.
Flow
Author System Reviewer
│ │ │
├── POST /versions ────────────>│ create draft (status: draft) │
├── PATCH /versions/:id ───────>│ edit delta │
├── POST /versions/:id/submit ─>│ status → pending │
│ ├── auto-start workflow ───────>│
│ │ ├── GET /workflow-tasks
│ │ ├── GET /versions/:id (review)
│ │ │
│ │<── approve ───────────────────┤
│ ├── promote version │
│ ├── main item updated │
│ ├── version status → approved │
│ │ │
│ │<── reject ────────────────────┤
│ ├── main item UNCHANGED │
│ ├── version status → rejected │Version Status Lifecycle
draft ──> pending ──> approved (delta applied to main item)
│
└──> rejected (main item unchanged)
│
└──> draft (admin cancelled → re-editable)Workflow Setup
json
{
"name": "Article Review",
"collection": "articles",
"trigger_event": "versions.submit"
}The workflow auto-starts when a version is submitted. On final approval, the engine promotes the version automatically.
API Endpoints (Author)
| Action | Method | Endpoint |
|---|---|---|
| Create draft | POST | /versions |
| Edit draft | PATCH | /versions/:id |
| Submit for review | POST | /versions/:id/submit |
| Check status | GET | /versions/:id |
| List my versions | GET | /versions?filter[user_created][_eq]=$CURRENT_USER |
Delta Format
The delta contains only changed fields, not the entire item:
json
{
"collection": "articles",
"item": "42",
"key": "june-revision",
"name": "Update headline and featured flag",
"delta": {
"title": "New Headline",
"featured": true
}
}FE Preview
Merge delta onto the current item to render a full preview:
ts
const preview = { ...mainItem, ...version.delta };Diff rendering: iterate Object.keys(delta), compare each value against mainItem[field].
Mode 2: Direct Approval (Item-Based)
When to use: Process-driven workflows where the item itself IS the subject of approval — purchase requests, leave forms, document sign-off, onboarding checklists, status change gates.
Key principle: The item already has the data. The workflow controls what happens next (status transitions, notifications, field updates).
Flow
Author System Reviewer
│ │ │
├── POST /items/requests ──────>│ item created │
│ ├── auto-start workflow ───────>│
│ │ (trigger: items.create) │
│ │ ├── GET /workflow-tasks
│ │ ├── GET /items/requests/:id
│ │ │
│ │<── approve ───────────────────┤
│ ├── action: update_field │
│ │ (status → approved) │
│ ├── action: send_notification │
│ │ │
│ │<── reject ────────────────────┤
│ ├── action: update_field │
│ │ (status → rejected) │Workflow Setup
json
{
"name": "Purchase Request Approval",
"collection": "purchase_requests",
"trigger_event": "items.create",
"trigger_filter": {
"amount": { "_gt": 1000 }
}
}items.create— triggers when a new item is createditems.update— triggers on update (usetrigger_filterto target specific changes)manual— no auto-trigger, start viaPOST /workflow-instances
Trigger Filter
Only start the workflow when conditions are met:
json
{
"trigger_filter": {
"department": { "_eq": "finance" },
"amount": { "_gte": 5000 }
}
}Action Steps
Automate side effects between approval steps:
| Action Type | Description | Example |
|---|---|---|
update_field | Update a field on the item | Set status to approved |
send_notification | Send notification to users | Notify the requester |
promote_version | Promote a linked version | Only used in content moderation |
API Endpoints
| Action | Method | Endpoint |
|---|---|---|
| Start manually | POST | /workflow-instances |
| List instances | GET | /workflow-instances?collection=purchase_requests |
| View instance | GET | /workflow-instances/:id |
| Cancel | POST | /workflow-instances/:id/cancel |
Shared: Reviewer Experience
Both modes share the same reviewer experience via the Task Inbox.
Task Inbox
GET /workflow-tasks → my pending tasks
GET /workflow-tasks/count → badge count
GET /workflow-tasks/all → all tasks (admin)Each task includes:
json
{
"instance_id": "...",
"step_id": "...",
"workflow_name": "Article Review",
"step_name": "Editor Review",
"collection": "articles",
"item_id": "42",
"version_id": "ver-uuid", // ← null for direct approval
"status": "active",
"activated_at": "2026-06-08T10:00:00Z",
"timeout_at": "2026-06-09T10:00:00Z",
"actions_available": ["approve", "reject", "delegate", "comment"]
}version_id is the key differentiator:
- Has value → content moderation task. Show version diff (delta vs main item).
- Null → direct approval task. Show the item itself.
Reviewer Actions
| Action | Endpoint | Notes |
|---|---|---|
| Approve | POST /workflow-instances/:id/steps/:stepId/approve | Comment optional |
| Reject | POST /workflow-instances/:id/steps/:stepId/reject | Comment required |
| Delegate | POST /workflow-instances/:id/steps/:stepId/delegate | to_user required |
| Comment | POST /workflow-instances/:id/steps/:stepId/comment | No state change |
Approval Modes
Each approval step can be configured with:
| Mode | Description |
|---|---|
any | One assignee approves → step passes |
all | Every assignee must approve |
majority | >50% must approve |
count | Exactly N approvals needed |
Timeouts
Steps can auto-resolve after a deadline:
| Timeout Action | Effect |
|---|---|
auto_approve | Step approved automatically |
auto_reject | Step rejected automatically |
escalate | Task reassigned to escalate_to user |
notify | Notification sent, step stays active |
Permission Model
Two independent permission modules control access:
Versioning Permissions (versioning.*)
| Action | Description | Scope |
|---|---|---|
versioning.view | View versions, compare with main item | Per collection |
versioning.create | Create/edit drafts, submit for review | Per collection |
versioning.promote | Manual promote (outside workflow) | Per collection |
versioning.manage | Delete versions, admin ops | Per collection |
Ownership rule: only the version creator (or admin) can submit a version.
Workflow Permissions (workflow.*)
| Action | Description | Scope |
|---|---|---|
workflow.view | View workflow definitions and instances | Global |
workflow.start | Start workflow instances | Per collection |
workflow.participate | Approve, reject, delegate, comment | Per collection |
workflow.manage | Create/edit workflows, cancel instances, view all tasks | Global |
Pre-built Policies
| Policy | Permissions | Typical Role |
|---|---|---|
| Version Author | versioning.view + versioning.create | Content editors |
| Version Reviewer | + versioning.promote | Senior editors |
| Version Manager | versioning.* | Content admins |
| Workflow Participant | workflow.view + workflow.start + workflow.participate | All staff |
| Workflow Manager | workflow.* | System admins |
| News Reviewer | workflow.participate scoped to news | News desk |
Policies are assigned to roles or users via odp_access → odp_policies → odp_app_permissions.
Disable all app permission checks: ENABLE_APP_PERMISSIONS=false.
FE Implementation Guide
Deciding Which Mode to Show
ts
// When rendering a task from the inbox:
if (task.version_id) {
// Content moderation → show version diff UI
const version = await api.get(`/versions/${task.version_id}`);
const mainItem = await api.get(`/items/${task.collection}/${task.item_id}`);
const changes = diffDelta(version.delta, mainItem);
// Render: field-by-field comparison table
} else {
// Direct approval → show the item itself
const item = await api.get(`/items/${task.collection}/${task.item_id}`);
// Render: item detail view with approve/reject buttons
}Content Moderation UI Components
| Component | Data Source | Purpose |
|---|---|---|
| Edit Version Form | GET /items/:collection/:id + form fields | Create draft with changed fields |
| Version List | GET /versions?filter[item][_eq]=:id | Show all versions for an item |
| Diff View | version.delta vs main item | Field-by-field comparison |
| Preview | { ...mainItem, ...version.delta } | Full preview after promotion |
| Status Badge | version.status | draft / pending / approved / rejected |
| Submit Button | POST /versions/:id/submit | Only visible when status === 'draft' |
Direct Approval UI Components
| Component | Data Source | Purpose |
|---|---|---|
| Item Detail | GET /items/:collection/:id | Show the item being reviewed |
| Instance Timeline | GET /workflow-instances/:id | Step history + actions |
| Action Buttons | task.actions_available | Show only available actions |
Shared UI Components
| Component | Data Source | Purpose |
|---|---|---|
| Task Inbox | GET /workflow-tasks | List of pending tasks |
| Inbox Badge | GET /workflow-tasks/count | Notification count |
| Approve/Reject | POST .../approve or .../reject | Decision buttons |
| Comment Thread | GET /workflow-instances/:id → actions | Audit trail |
| Delegate Modal | POST .../delegate | Reassign task |
Polling Strategy
| What | Endpoint | Interval | Who |
|---|---|---|---|
| Inbox badge | GET /workflow-tasks/count | 30s | All reviewers |
| Version status | GET /versions/:id | 10s (while pending) | Author watching their submission |
| Instance state | GET /workflow-instances/:id | 10s (while running) | Anyone viewing the instance |
Multi-Step Examples
Content: Article → Editor → Legal → Publish
Start ──> Editor Review ──approve──> Legal Review ──approve──> End (auto-promote)
│ │
└──reject──> End └──reject──> EndProcess: Leave Request → Manager → HR
Start ──> Manager Approval ──approve──> HR Confirmation ──approve──> [update_field: status=approved] ──> End
│ │
└──reject──> [update_field: status=rejected] ──> EndConditional: Purchase Order by Amount
Start ──> [condition: amount >= 10000?]
│yes │no
v v
Director Approval Manager Approval
│ │
└───── approve ─────┘
│
v
[send_notification] ──> EndAPI Reference
Versioning
POST /versions — Create a draft version
Create a new version containing proposed changes to an item. The version starts in draft status.
Permission: versioning.create (scoped to collection)
Request Body:
json
{
"collection": "articles",
"item": "42",
"key": "june-revision",
"name": "Update headline and featured flag",
"delta": {
"title": "New Headline",
"featured": true
}
}| Field | Type | Required | Description |
|---|---|---|---|
collection | string | Yes | Target collection name |
item | string | Yes | Primary key of the target item |
key | string | Yes | Unique key for this version (slug) |
name | string | No | Human-readable name for display |
delta | object | No | Object containing only the fields being changed. Omitted fields remain unchanged on the main item. |
Response 200:
json
{ "data": "version-uuid" }GET /versions — List versions
List versions with optional filtering and pagination. Supports standard ODP query parameters.
Permission: versioning.view
Common Query Parameters:
| Param | Example | Description |
|---|---|---|
filter[collection][_eq] | articles | Filter by collection |
filter[item][_eq] | 42 | Filter by item (show all versions of one item) |
filter[user_created][_eq] | $CURRENT_USER | Filter by creator (my versions) |
filter[status][_eq] | pending | Filter by status |
sort | -date_created | Sort order |
limit | 25 | Max results |
offset | 0 | Pagination offset |
meta | total_count,filter_count | Include meta counts |
Response 200:
json
{
"data": [
{
"id": "version-uuid",
"collection": "articles",
"item": "42",
"key": "june-revision",
"name": "Update headline",
"status": "draft",
"delta": { "title": "New Headline" },
"user_created": "user-uuid",
"date_created": "2026-06-08T10:00:00Z",
"date_updated": null
}
],
"meta": { "total_count": 12, "filter_count": 3 }
}GET /versions/:id — Read a single version
Fetch one version with its full delta.
Permission: versioning.view (scoped to version's collection)
Response 200:
json
{
"data": {
"id": "version-uuid",
"collection": "articles",
"item": "42",
"key": "june-revision",
"name": "Update headline",
"status": "pending",
"delta": { "title": "New Headline", "featured": true },
"hash": null,
"user_created": "user-uuid",
"user_updated": null,
"date_created": "2026-06-08T10:00:00Z",
"date_updated": "2026-06-08T10:05:00Z"
}
}PATCH /versions/:id — Update a draft version
Edit the delta or metadata of a version. Only meaningful while status === 'draft'.
Permission: versioning.create (scoped to version's collection)
Request Body (partial):
json
{
"name": "Updated name",
"delta": {
"title": "Final Headline",
"featured": true,
"body": "Updated body text..."
}
}The delta is replaced entirely (not merged). Send the full delta object.
Response 200: { "data": "version-uuid" }
DELETE /versions/:id — Delete a version
Permanently remove a version record.
Permission: versioning.manage (scoped to version's collection)
Response 204: No content.
POST /versions/:id/submit — Submit for review
Transition a draft version to pending and trigger any matching versions.submit workflow on the collection. The workflow starts automatically — no manual POST /workflow-instances needed.
Permission: versioning.create (scoped to version's collection) + must be the version creator (or admin)
Validation rules:
| Rule | Error |
|---|---|
Version status must be draft | 400: Version cannot be submitted: current status is "..." |
Version must have a non-empty delta | 400: Version has no changes to submit |
| No running workflow instance for this version | 400: A workflow is already running for this version |
Response 200:
json
{ "data": { "id": "version-uuid", "status": "pending" } }POST /versions/:id/promote — Manual promote
Apply the version's delta directly to the main item, bypassing workflow. Use this when no workflow is configured, or for admin overrides.
Permission: versioning.promote (scoped to version's collection)
After promotion:
- Each field in
deltais written to the main item - Version status changes to
approved - Relational objects in delta are flattened to FK scalars (e.g.
{ user: { id: "..." } }→user: "...") - Array/object values for JSON columns are serialized automatically
Response 204: No content.
Workflow Definitions
POST /workflows — Create a workflow definition
Permission: workflow.manage
Request Body:
json
{
"name": "Article Review",
"description": "Review articles before publishing",
"collection": "articles",
"trigger_event": "versions.submit",
"trigger_filter": null,
"options": null
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
description | string | No | Description |
collection | string | No | Bound collection. Required for auto-trigger workflows, null for manual-only. |
trigger_event | string | Yes | versions.submit, items.create, items.update, or manual |
trigger_filter | object | No | ODP filter object. Only items matching this filter trigger the workflow. |
options | object | No | Additional configuration |
New workflows start in draft status. They must be activated before they can trigger.
Response 201: { "data": { "id": "workflow-uuid" } }
GET /workflows — List workflow definitions
Permission: workflow.view
Query Parameters: status, collection, sort, limit, offset
Response 200: { "data": [ { ...workflow, steps: [...], transitions: [...] } ] }
Each workflow includes its full steps and transitions arrays.
GET /workflows/:id — Read a workflow definition
Permission: workflow.view
Returns the workflow with all steps and transitions.
PATCH /workflows/:id — Update a workflow
Permission: workflow.manage
Partial update. Can modify name, description, collection, trigger_event, trigger_filter, options.
DELETE /workflows/:id — Delete a workflow
Permission: workflow.manage
Cannot delete workflows that have running instances.
Response 204: No content.
POST /workflows/:id/duplicate — Duplicate a workflow
Permission: workflow.manage
Creates a new workflow in draft status with copies of all steps and transitions.
Response 201: { "data": { "id": "new-workflow-uuid" } }
POST /workflows/:id/activate — Activate a workflow
Permission: workflow.manage
Changes status from draft → active. Only active workflows respond to trigger events.
Response 200: { "data": { "id": "...", "status": "active" } }
POST /workflows/:id/deactivate — Deactivate a workflow
Permission: workflow.manage
Changes status from active → draft. Running instances are not affected.
Response 200: { "data": { "id": "...", "status": "draft" } }
Workflow Steps
Steps define the nodes of a workflow graph. Each workflow needs at least a start and end step.
POST /workflows/:id/steps — Add a step
Permission: workflow.manage
Request Body:
json
{
"key": "editor_review",
"name": "Editor Review",
"type": "approval",
"assign_type": "role",
"assign_value": "editor-role-uuid",
"approval_mode": "any",
"approval_count": null,
"timeout_minutes": 1440,
"timeout_action": "auto_reject",
"escalate_to": null,
"action_type": null,
"action_config": null,
"position_x": 200,
"position_y": 100,
"sort_order": 2
}| Field | Type | When | Description |
|---|---|---|---|
key | string | Always | Unique identifier within the workflow |
name | string | Always | Display name |
type | string | Always | start, approval, condition, action, notification, end |
assign_type | string | approval | role, user, field, creator, auto |
assign_value | string | approval | Role UUID, user UUID, field name, or null for creator/auto |
approval_mode | string | approval | any, all, majority, count |
approval_count | number | count mode | Required number of approvals |
timeout_minutes | number | approval | Timeout deadline in minutes (null = no timeout) |
timeout_action | string | When timeout set | auto_approve, auto_reject, escalate, notify |
escalate_to | string | escalate action | User UUID to reassign to on timeout |
action_type | string | action type | update_field, send_notification, promote_version |
action_config | object | action type | Configuration for the action (field/value, recipients, etc.) |
position_x/y | number | Optional | Visual canvas coordinates |
sort_order | number | Optional | Display order |
Response 201: { "data": { "id": "step-uuid" } }
PATCH /workflows/:id/steps/:key — Update a step
Permission: workflow.manage
Partial update by step key.
DELETE /workflows/:id/steps/:key — Remove a step
Permission: workflow.manage
Response 204: No content.
Workflow Transitions
Transitions define the edges of the workflow graph — how flow moves between steps.
POST /workflows/:id/transitions — Add a transition
Permission: workflow.manage
Request Body:
json
{
"from_step_id": "review-step-uuid",
"to_step_id": "end-step-uuid",
"trigger": "approve",
"condition": null,
"label": "Approved",
"sort_order": 1
}| Field | Type | Description |
|---|---|---|
from_step_id | UUID | Source step |
to_step_id | UUID | Destination step |
trigger | string | approve, reject, timeout, condition, always |
condition | object | Condition expression (only for condition trigger) |
label | string | Display label on the canvas edge |
sort_order | number | Priority when multiple transitions match |
Trigger types:
| Trigger | When it fires |
|---|---|
always | Immediately after the source step completes (used from start steps) |
approve | When the source step is approved |
reject | When the source step is rejected |
timeout | When the source step times out |
condition | Evaluated against item data; fires if expression is truthy |
Response 201: { "data": { "id": "transition-uuid" } }
PATCH /workflows/:id/transitions/:tid — Update a transition
Permission: workflow.manage
DELETE /workflows/:id/transitions/:tid — Remove a transition
Permission: workflow.manage
Response 204: No content.
Workflow Instances
An instance is a running execution of a workflow, bound to a specific item (and optionally a version).
POST /workflow-instances — Start an instance manually
Start a workflow for an item. Only needed for manual trigger workflows — items.create, items.update, and versions.submit workflows start automatically.
Permission: workflow.start (scoped to collection)
Request Body:
json
{
"workflow_id": "workflow-uuid",
"collection": "articles",
"item_id": "42"
}Response 201:
json
{
"data": {
"id": "instance-uuid",
"workflow_id": "workflow-uuid",
"collection": "articles",
"item_id": "42",
"version_id": null,
"status": "running",
"current_step_id": "first-step-uuid",
"started_by": "user-uuid",
"started_at": "2026-06-08T10:00:00Z",
"completed_at": null
}
}GET /workflow-instances — List instances
Permission: workflow.view
Query Parameters:
| Param | Description |
|---|---|
workflow_id | Filter by workflow definition |
collection | Filter by collection |
item_id | Filter by item |
status | running, completed, rejected, cancelled, error |
version_id | Filter by version (content moderation) |
limit / offset | Pagination |
Response 200: { "data": [ ...instances ] }
GET /workflow-instances/:id — Read an instance (full detail)
Returns the instance with enriched data: workflow step definitions, instance step states (with approval records), and the full action audit log.
Permission: workflow.view OR workflow.participate
Response 200:
json
{
"data": {
"id": "instance-uuid",
"workflow_id": "workflow-uuid",
"workflow_name": "Article Review",
"collection": "articles",
"item_id": "42",
"version_id": "version-uuid",
"status": "running",
"current_step_id": "review-step-uuid",
"started_by": "user-uuid",
"started_at": "2026-06-08T10:00:00Z",
"completed_at": null,
"workflow_steps": [
{ "id": "...", "key": "start", "name": "Start", "type": "start" },
{ "id": "...", "key": "review", "name": "Editor Review", "type": "approval", "assign_type": "role", "assign_value": "..." }
],
"instance_steps": [
{
"id": "instance-step-uuid",
"step_id": "review-step-uuid",
"status": "active",
"approvals": [],
"activated_at": "2026-06-08T10:00:05Z",
"completed_at": null,
"timeout_at": "2026-06-09T10:00:05Z"
}
],
"actions": [
{
"id": "action-uuid",
"step_id": "start-step-uuid",
"user_id": "user-uuid",
"action": "start",
"comment": null,
"created_at": "2026-06-08T10:00:00Z"
}
]
}
}Use this response to build:
- Timeline: iterate
actionssorted bycreated_at - Step progress: map
instance_stepstoworkflow_stepsbystep_id - Current assignees: check
workflow_stepswherestep_id === current_step_id
POST /workflow-instances/:id/cancel — Cancel a running instance
Stop a workflow in progress. If the instance has a linked version_id and the version is pending, the version reverts to draft (re-editable by the author).
Permission: workflow.manage
Request Body (optional):
json
{ "comment": "No longer needed" }Response 200: { "data": { "id": "instance-uuid" } }
Step Actions (Reviewer)
These endpoints allow assigned reviewers to act on the current active step of an instance.
All step action endpoints require workflow.participate permission scoped to the instance's collection. Additionally, the engine enforces step assignment — only users assigned to the step (by role, direct assignment, or field) can act on it, unless the user is admin.
POST /workflow-instances/:id/steps/:stepId/approve — Approve
Approve the current step. If the step's approval_mode requirements are met (e.g. all assignees approved in all mode), the engine advances to the next step via the approve transition.
If this is the final step (transition leads to end) and the instance has a version_id, the version is automatically promoted (delta applied to main item).
Permission: workflow.participate (scoped to collection) + assigned to step
Request Body (optional):
json
{ "comment": "Looks good, approved." }Response 200: { "data": { "success": true } }
POST /workflow-instances/:id/steps/:stepId/reject — Reject
Reject the current step. The engine follows the reject transition. If the instance has a version_id, the version status changes to rejected and the main item remains unchanged.
Permission: workflow.participate (scoped to collection) + assigned to step
Request Body:
json
{ "comment": "Title needs revision. Second paragraph is unclear." }Comment is required for rejection.
Response 200: { "data": { "success": true } }
POST /workflow-instances/:id/steps/:stepId/delegate — Delegate
Reassign the task to another user. The original assignee is removed, and the target user becomes the new assignee. The target user must also have workflow.participate permission on the collection.
Permission: workflow.participate (scoped to collection) + assigned to step
Request Body:
json
{
"to_user": "target-user-uuid",
"comment": "Needs legal review, reassigning."
}| Field | Type | Required | Description |
|---|---|---|---|
to_user | UUID | Yes | User to delegate to |
comment | string | No | Reason for delegation |
Response 200: { "data": { "success": true } }
POST /workflow-instances/:id/steps/:stepId/comment — Comment only
Add a comment to the step's audit log without changing any state. The step remains active, no transitions fire, no approvals are recorded. Use this for discussion, feedback, or questions before making a decision.
Permission: workflow.participate (scoped to collection)
Request Body:
json
{ "comment": "Consider rephrasing the introduction before I approve." }Comment is required.
Response 200: { "data": { "success": true } }
Task Inbox
The task inbox surfaces pending approval tasks for the current user.
GET /workflow-tasks — My pending tasks
Returns all active tasks assigned to the current user (by role, direct assignment, or field-based resolution).
Permission: workflow.participate
Query Parameters: limit, offset
Response 200:
json
{
"data": [
{
"id": "instance-step-uuid",
"step_id": "step-uuid",
"instance_id": "instance-uuid",
"workflow_name": "Article Review",
"step_name": "Editor Review",
"collection": "articles",
"item_id": "42",
"version_id": "version-uuid",
"status": "active",
"activated_at": "2026-06-08T10:00:00Z",
"timeout_at": "2026-06-09T10:00:00Z",
"actions_available": ["approve", "reject", "delegate", "comment"]
}
],
"meta": { "total_count": 5 }
}Key fields for FE:
| Field | Usage |
|---|---|
instance_id + step_id | Used in approve/reject/delegate/comment endpoint paths |
version_id | Non-null → content moderation task (show diff). Null → direct approval (show item). |
actions_available | Render only these action buttons |
timeout_at | Show countdown timer. Null = no timeout. |
GET /workflow-tasks/count — My task count
Returns a single number — the count of the current user's pending tasks. Use for inbox badge display.
Permission: workflow.participate
Response 200:
json
{ "data": { "count": 3 } }GET /workflow-tasks/all — All active tasks (admin)
Returns all active tasks across all users, regardless of assignment.
Permission: workflow.manage
Query Parameters: limit, offset
Same response shape as GET /workflow-tasks.
Audit Log
GET /workflow-instances/:id/actions — Instance action history
Full audit trail for a specific workflow instance. Every approve, reject, delegate, comment, start, cancel, timeout, and escalation is recorded.
Permission: workflow.view OR workflow.participate
Response 200:
json
{
"data": [
{
"id": "action-uuid",
"instance_id": "instance-uuid",
"step_id": "step-uuid",
"user_id": "user-uuid",
"action": "approve",
"comment": "Looks great!",
"data": null,
"created_at": "2026-06-08T11:00:00Z"
}
]
}Action types:
| Action | Description |
|---|---|
start | Instance was started |
approve | Step was approved by a user |
reject | Step was rejected by a user |
delegate | Step was reassigned to another user |
comment | Comment added without state change |
escalate | Step was escalated due to timeout |
auto_approve | Step was auto-approved due to timeout |
timeout | Step timed out |
cancel | Instance was cancelled by admin |
GET /workflow-actions — All actions (admin)
Query actions across all instances.
Permission: workflow.manage
Query Parameters:
| Param | Description |
|---|---|
instance_id | Filter by instance |
action | Filter by action type |
limit / offset | Pagination |
See Also
- Workflow Engine — full engine reference, data model, all endpoints
- Content Moderation — detailed version-based flow with code examples