REST API Workflow - edmozley/freeitsm GitHub Wiki
โ๏ธ REST API: Workflow
The complete usage guide for the Workflow module of the REST API โ automation rules, test-firing, the engine's run history, and the machine-readable trigger/action catalogues. Mirrors the interactive documentation at System โ API โ Documentation (with its live "Try it" tester).
[!NOTE] New to the API? The basics table on REST API: Tickets covers base URL, authentication, the response envelope, error codes, rate limits and PATCH semantics โ identical across all modules.
What's distinctive about Workflow:
| ๐ The engine's catalogues are the contract | trigger_event, condition operators and action types are validated at write time against the engine's own catalogues โ nothing unexecutable can be stored. Unknown operators are a 422 (the UI stores them and the condition then silently fails at run time). |
| ๐ฅ Test-fire over the API | POST /workflows/{id}/fire is the editor's "Test fire" button: synthetic payload in, real actions out, the full per-step log back โ and production run stats (run_count, last_run) stay untouched. |
| ๐งพ Run history that survives deletion | Executions are the engine's audit trail. Deleting a workflow keeps its runs โ they're detached (workflow.id becomes null) but stay attributable via a name snapshot taken at run time. ?orphaned=true finds them. |
| ๐ A debugging surface the UI only hints at | A single execution read returns the complete step log: every condition with the actual value it saw and whether it passed, every action with its result or error. The editor's sidebar shows only the last 20 runs' summaries. |
| โ ๏ธ Powerful grants | Workflow actions run with engine privileges โ they create tickets, send email from the ticket's mailbox, assign analysts. Treat workflows.create / workflows.update / workflows.fire as admin-level permissions and scope them to trusted keys. |
๐ Quick start โ build a rule, test it, watch it run
B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_โฆ"
# What can I trigger on, and what can I do? (no guessing โ ask the engine)
curl -s "$B/workflow-triggers" -H "$K"
curl -s "$B/workflow-actions" -H "$K"
# Create a rule: note every urgent ticket
WF=$(curl -s -X POST "$B/workflows" -H "$K" -H "Content-Type: application/json" -d '{
"name": "Flag urgent tickets",
"trigger_event": "ticket.created",
"conditions": [ { "field": "ticket.subject", "op": "contains", "value": "urgent" } ],
"actions": [ { "type": "add_ticket_note",
"args": { "ticket_id": "{{ticket.id}}", "note": "Auto-flagged as urgent." } } ]
}' | jq '.data'); ID=$(echo "$WF" | jq -r '.id')
# Test-fire it with a synthetic payload โ real actions, stats untouched
curl -s -X POST "$B/workflows/$ID/fire" -H "$K" -H "Content-Type: application/json" \
-d '{ "payload": { "ticket": { "id": 123, "subject": "urgent: server down" } } }'
# Watch it run for real
curl -s "$B/workflows/$ID/executions" -H "$K"
โ๏ธ Workflows
๐ข GET /workflows ย ๐ workflows.read
All rules, most recently updated first, with run stats and conditions_count / actions_count (the full rule bodies come on the single read). Filters: trigger_event, is_active=true|false, q (names + descriptions). Install-wide โ workflows have no company scoping, like the module.
๐ต POST /workflows ย ๐ workflows.create
name and trigger_event required. conditions is an array of {field, op, value} (AND semantics โ empty means always fire); actions is an ordered array of {type, args}. Zero actions is allowed (draft-friendly, matching the editor). Action args support {{dot.path}} template variables resolved from the event payload โ "ticket_id": "{{ticket.id}}" is the idiom that binds an action to the triggering ticket.
Everything is validated against the engine's catalogues: unknown triggers, operators and action types are a 422 pointing you at GET /workflow-triggers / GET /workflow-actions.
๐ข GET /workflows/{id} ย ๐ workflows.read
The full rule โ decoded conditions and actions, creator, run stats:
{ "data": { "id": 7, "name": "Flag urgent tickets", "trigger_event": "ticket.created",
"is_active": true, "created_by": { "id": 1, "name": "Administrator" },
"conditions": [ { "field": "ticket.subject", "op": "contains", "value": "urgent" } ],
"actions": [ { "type": "add_ticket_note", "args": { "ticket_id": "{{ticket.id}}", "note": "โฆ" } } ],
"last_run": { "at": "2026-07-04T08:42:44Z", "status": "success" }, "run_count": 12 } }
๐ PATCH /workflows/{id} ย ๐ workflows.update
Partial update โ sent fields are validated like create. {"is_active": false} pauses a rule without losing it; the engine only picks up active workflows on dispatch.
๐ด DELETE /workflows/{id} ย ๐ workflows.delete
Hard delete. Execution history survives โ runs are detached (workflow.id โ null) but keep the workflow's name via the snapshot, so the audit trail stays readable forever.
๐ต POST /workflows/{id}/fire ย ๐ workflows.fire
Runs the workflow immediately with a synthetic payload โ the editor's Test fire. The payload is whatever your conditions and {{templates}} read ({ "payload": { "ticket": { "id": 123, "subject": "โฆ" } } }). Actions really execute (a create_ticket action really creates a ticket), but run_count / last_run are untouched so test runs don't pollute production stats. Returns the execution result inline:
{ "data": { "execution_id": 17, "status": "success",
"step_log": [
{ "kind": "condition", "field": "ticket.subject", "op": "contains",
"value": "urgent", "actual": "urgent: server down", "passed": true },
{ "kind": "action", "type": "add_ticket_note", "status": "success",
"result": { "ticket_id": 123, "note_length": 24 } } ] } }
A run whose conditions don't match returns "status": "skipped" with the failing condition's actual value in the log โ the fastest way to debug a rule that "never fires".
๐งพ Executions
Read-only โ only the engine writes these. Statuses: running, success, failed, skipped (conditions didn't match), aborted (loop protection stepped in).
๐ข GET /workflows/{id}/executions ย ๐ workflow_executions.read
One workflow's runs, newest first, paginated. Filters: status, trigger_event, started_since (ISO 8601).
๐ข GET /workflow-executions ย ๐ workflow_executions.read
The install-wide run history โ including orphaned runs whose workflow has since been deleted (?orphaned=true), which the module's UI never surfaces. Same filters plus workflow_id.
๐ข GET /workflow-executions/{id} ย ๐ workflow_executions.read
Full detail: the trigger_payload snapshot the engine saw, and the complete step_log โ every condition evaluated (with the actual value read from the payload) and every action's result or error message.
๐ Catalogues (reference data)
๐ข GET /workflow-triggers ย ๐ reference.read
Every trigger event with its condition fields โ each field's dotted payload path, its type (lookup / numeric / text) and the operators valid for it. Seven triggers today: ticket.created, ticket.status_changed, ticket.priority_changed, ticket.assigned, form.submitted, task.completed, change.approved.
๐ข GET /workflow-actions ย ๐ reference.read
Every action type with its full args spec โ labels, required flags, defaults, lookup sources and whether an arg supports {{template}} variables. Eight actions today: log_message, set_ticket_status, set_ticket_priority, assign_ticket, add_ticket_note, send_email, create_task, create_ticket.
๐ Notes & gotchas
- The engine runs synchronously โ a fire request returns when the actions finish. Keep test payloads realistic but small.
- Loop protection is request-scoped: a workflow can't re-enter itself, chains cap at depth 10, and 100 runs per request โ blocked runs are recorded as
abortedexecutions, visible in the history. - The AI compose endpoints stay UI-only (Workflow โ editor โ AI compose) โ like every AI feature in the API so far.
- The
firepermission is separate fromupdateprecisely so you can hand a monitoring script the ability to trigger a runbook workflow without letting it rewrite the rule.
See also: REST API (how it all works) ยท Workflows (the module itself) ยท the other module guides linked from the sidebar.