The automations API
Automation rules through the REST API: what Platform → Automations shows, for scripts and AI assistants that set the desk up. How rules behave, in the app's words, is in the automations guide; this page is the reference for the API.
Everything is under /api/v1/rules. A person needs Manage automations (rule.settings) in the organization; a
token needs rule.settings itself (or admin), and its owner must hold it.
Endpoints
| Endpoint | What |
|---|---|
GET /api/v1/rules | Every rule you may manage, in their order: the rule with unit ({ id, name }), runs7d (how often it ran in the last 7 days) and lastRunAt. |
GET /api/v1/rules/references | The names of the people, teams, accounts and forms the rules point at: { users, teams, accounts, forms }, each [{ id, name }]. |
GET /api/v1/rules/<id> | One rule, with its last 20 runs: runs (on cases: { at, actions, case: { ref, subject } }) or recordRuns (on a plugin's records: { at, actions, record: { key, title, href } }). actions says in words what each action did, or why it was skipped; steps is each step as it ran, in order: { stepId, kind, outcome, text, ms } (outcome: done, nothing (nothing to change), failed, then or else for an If, stopped). |
POST /api/v1/rules | A new rule (the body below), placed last in its organization. |
PATCH /api/v1/rules/<id> | Change any part; the rule is checked again as a whole. |
DELETE /api/v1/rules/<id> | Delete it. Its history goes with it. |
POST /api/v1/rules/reorder | { ids }: the new order of the rules of one organization. |
POST /api/v1/rules/flows | A new flow: { name, description?, unitId, subject, trigger }. It starts off, with an empty draft. |
PUT /api/v1/rules/<id>/draft | { flow }: save the draft (see Flows). Any rule can have one; a simple rule keeps running as it is until the draft is published. |
DELETE /api/v1/rules/<id>/draft | Discard the draft. |
POST /api/v1/rules/<id>/publish | { note? }: check the draft and make it the version that runs. The first version turns the flow on. A problem answers 400 with the step in details[].path (when, or steps.<step id>). |
GET /api/v1/rules/<id>/versions | The published versions, newest first: { versions: [{ version, note, publishedAt, publishedBy }] }. |
GET /api/v1/rules/<id>/versions/<n> | One version with its flow. |
POST /api/v1/rules/test | A dry run: { unitId, subject, trigger, formId, matchAll, conditions }. Nothing is changed. On cases it checks the latest 200 (for submitted, those from the form) and answers { checked, matched, cases }, with up to 25 matches; on a plugin's records, the open ones (up to 500), as records. |
Creating, changing, publishing and deleting a rule are in the audit log (rule.created, rule.updated, rule.published, rule.deleted).
A rule
{
"name": "Laptop requests to IT",
"description": "Portal requests for a new laptop go to the IT team.",
"unitId": "<unit id>",
"subject": "case",
"trigger": "created",
"matchAll": true,
"conditions": [{ "field": "form", "op": "is", "value": "<form id>" }],
"actions": [
{ "type": "assign_team", "value": "<team id>" },
{ "type": "set_priority", "value": "high" }
],
"stopProcessing": false,
"enabled": true,
"runAs": "system"
}
| Field | What |
|---|---|
name, description | Up to 100 and 500 characters. |
unitId | Where it applies: the unit and everything below it. For submitted, the form's unit is used instead. |
subject | What it runs on: case (the default), or a plugin's records, like changes.change or problems.problem. |
trigger | When it runs (below). |
formId | For submitted: the form whose cases it runs on. |
matchAll | true: every condition must hold; false: any one. |
conditions | Up to 20: { field, op, value, key }. key names a form field (field) or a data property (case_prop, account_prop, contact_prop). |
actions | 1 to 10: { type, value, target, key, fields }. |
stopProcessing | Later rules skip what this rule acted on. |
enabled | Off rules are kept and don't run. |
runAs, runAsId | Who it creates records as: system (the default) or person, with the person's id (below). |
Rules run in their order across the organization (reorder), each at most once per event.
A rule that doesn't hold together answers 400 validation_failed, with details naming the part and what is wrong:
[{ "path": "actions.0.value", "message": "Pick a team" }]. Values must exist in the rule's organization (a unit, a
person working there, a team, an account, a form), and a unit picked must be the rule's or one below it.
Flows
A rule of kind flow is built as steps. Its flow is the version that runs, version its number, and draft
the changes not yet published. The trigger and the conditions under when are copied to the rule's trigger,
matchAll and conditions when it is published, and its actions (from every branch) to actions. On a flow,
PATCH changes the name, description, unit, who it runs as and whether it is on; its trigger, conditions and steps
change only through the draft (400 use_draft otherwise).
{
"trigger": "created",
"when": { "matchAll": true, "conditions": [] },
"steps": [
{ "id": "s1", "kind": "if", "matchAll": true,
"conditions": [{ "field": "priority", "op": "is", "value": "urgent" }],
"then": [{ "id": "s2", "kind": "action", "action": { "type": "add_labels", "value": "outage" }, "onError": "stop" }],
"else": [] },
{ "id": "s3", "kind": "action", "action": { "type": "assign_team", "value": "<team id>" } },
{ "id": "s4", "kind": "stop" }
]
}
A step is an action (any action a rule has; onError continue or stop), an if with its own conditions and
two branches (then, else), or stop. Each step has its own id (letters, digits, - and _). A flow has at
most 60 steps, and Ifs go at most 5 deep. Publish checks the trigger and its conditions, then each step on its own,
as a rule is checked.
Triggers
| Trigger | On | When |
|---|---|---|
created | Cases, plugins' records | A new case (from any channel), or a plugin's record recorded. |
submitted | Cases | A case from the form formId came in, wherever it was placed. Its answers are placeholders. |
customer_replied | Cases | The requester wrote. |
changed | Plugins' records | The record moved to another status. |
scheduled | Both | Every 5 minutes, on open cases (or a plugin's open records). Needs a time condition; time conditions work only here. Each case acts once per situation, not every 5 minutes. |
A case from a form runs both the created rules of its unit and the submitted rules of its form, in the rules'
order.
Conditions on cases
field | op | value |
|---|---|---|
unit | in, not_in | A unit id (that unit and below). |
source | is, is_not | form, portal, agent, api, email, import or automation. |
form | is, is_not | A form id. |
priority | is, is_not, at_least | low, normal, high, urgent. |
status | is, is_not | new, open, pending, on_hold, resolved, closed. |
team | is, is_not, none | A team id. |
labels | has, not_has | A label. |
assignee | none, any, is | A person's id, for is. |
email | domain, is | The requester's domain or address. |
locale | is | en, no, sv or da. |
account | is, is_not, any, none | An account id. |
account_labels | has, not_has | A label. |
subject, text | contains_any, contains_none | Words, separated by commas. text is the subject or the messages. |
field | is, is_not, contains, set, empty | With key: a form field. |
case_prop, account_prop, contact_prop | is, is_not, contains, gt, lt, set, empty | With key: a data property. gt and lt for numbers and dates. |
hours_since_created, hours_waiting, hours_in_status | gt | Hours. Scheduled rules only. |
sla_due | within (minutes), overdue | The next service-level target. Scheduled rules only. |
Plugins that are on add their own, named <plugin>.<name>: Service Configuration's itsm.service (is, is_not,
under, any, none, a service id) and Problem Management's problems.linked (is, any, none, a problem id).
GET /api/v1/plugins lists every plugin's conditions, actions and subjects under rules, with their operators,
choices and the endpoints that look up their values (lookup, such as /api/v1/x/itsm/services/pick). A saved
condition of a plugin that is off never matches, and its actions are skipped.
Actions on cases
type | value |
|---|---|
set_priority | low, normal, high, urgent. |
set_status | open, pending, on_hold, resolved (closing is left to people and auto-close). |
add_labels, remove_labels | Labels, separated by commas. |
assign | A person who can see every case the rule applies to. |
assign_round_robin, assign_least_busy | A unit (the rule's or below): in turn, or to the agent with fewest open cases. |
unassign | None. |
assign_team, assign_team_round_robin, assign_team_least_busy | A team: its queue, or one of its members. |
move | A unit (the rule's or below). |
note | The text of an internal note. |
set_property | With key: a case data property, and its value (several choices separated by commas). Not a unique one. |
notify | target: assignee (or the unit's agents if nobody), unit, or user with a person's id as value. |
create_record | target and fields (below). |
Plugins add itsm.set_service (a service id), itsm.assign_service_team, itsm.notify_service_owner and
problems.link (a problem id).
On a plugin's records, the conditions and actions are the subject's own, without the prefix (for changes: type,
status, risk, service, owner, implementer, created, updated, starts; notify, set_implementer,
note); GET /api/v1/plugins lists them, and changes and problems
describes them.
Create a record
The action create_record makes a new record from what the rule runs on, linked back to it. target is the kind;
fields maps each field's id to its value: text with {{placeholders}}, or the id of what is picked.
target | Plugin | Needs | Fields (required in bold) |
|---|---|---|---|
case | case.create | subject, description, requester (an email address; a new contact for one the desk doesn't know), unit, priority, team, assignee, labels (comma-separated). | |
incidents.incident | Incident Management | incident.write | title, summary, impact, urgency (high, medium, low), unit, service, commander (empty: it starts as New). |
problems.problem | Problem Management | problem.create | title, description, impact, urgency, unit, service. |
changes.change | Change Management | change.write | title, description, service, type (normal, emergency), owner, implementer. A draft. |
changes.task | Change Management | change.write | title, assignee. Only from a rule on changes. |
{
"type": "create_record",
"target": "incidents.incident",
"fields": {
"title": "{{title}}",
"summary": "Reported by {{requester.name}} ({{account.name}}): {{fields.what_happened}}\n\n{{url}}",
"impact": "high",
"urgency": "medium"
}
}
Placeholders, in text fields only:
| Placeholder | On | What |
|---|---|---|
{{title}}, {{ref}}, {{url}} | Anything | The subject or title, the reference (CASE-1042) or key (CHG-12), and its link. |
{{description}} | Cases | The request (the first message). |
{{priority}}, {{status}}, {{unit}}, {{labels}} | Cases | As they are when the rule runs. |
{{requester.name}}, {{requester.email}}, {{account.name}} | Cases | The requester and their account. |
{{fields.<key>}} | submitted rules | The form's answer to that question (lists joined with commas). |
A placeholder that doesn't exist for the rule is refused when it is saved ({{fields.…}} only works in a rule that
runs when its form is submitted). A required field that comes out empty skips the action.
A case made this way has the source automation, gets its confirmation email and runs its own created rules. The
new record names the rule in its history, and the rule's runs say Created incident INC-14.
What stops rules running away
| Guard | How |
|---|---|
| Not on its own records | A rule never runs on a record it made itself. |
| A chain of 3 | A record made by a rule may make another, and that one a third, but no deeper. |
| 50 an hour | One rule makes at most 50 records an hour. |
| Once per event | Each rule acts once on each event (a retry doesn't act twice), and a scheduled rule once per situation. |
What a guard stops is skipped, and the rule's runs say why: Skipped: an incident (this rule made 50 records in the last hour).
Who a rule acts as
Rules act as the System user: it can't sign in, isn't listed or picked, and isn't a staff seat. Their changes show on the timeline and in the audit log as made by the rule, through System. As an actor System may do anything inside the rule's organization and nothing outside it; who may write automations is what limits it.
runAs: "person" with runAsId makes a rule's Create a record actions act as that person instead, with what
they may do when it runs:
- the person must work in the organization and hold the kind's permission (
incident.write…) in the rule's unit when the rule is saved, and again each time it runs; - only someone with
user.writefor the whole organization (its administrators) picks someone other than themselves (anyone may keep who it was; otherwise400onrunAsId); - when the person is disabled or loses the permission, the action is skipped and the runs say why; it never falls back to System.
The rule's other actions (labels, priority, notes, assigning, notifying) act the same either way.
A form and its automation together
The guided form builder (Platform → Forms → New form → Guided) saves a form and the rule that goes with it in one request, or neither. The same endpoints are there for scripts:
| Endpoint | What |
|---|---|
POST /api/v1/forms/guided/check | Checks the body below and saves nothing: { ok, checks, rule }. Each check is { id, level, text }, level being ok, warn or fail; rule is the rule it would write (name, actions), or null. |
POST /api/v1/forms/guided | Creates the form, its place on a portal and its rule, in one transaction: { form: { id, publicId, name, enabled }, portalEntry, rule }. A failed check answers 400 with each failure in details (path is the check's id) and saves nothing. |
{
"creates": "incidents.incident",
"form": {
"unitId": "<unit id>",
"name": "Report an outage",
"audience": "signed_in",
"askSubject": false,
"fields": [{ "key": "affected", "type": "short_text", "label": { "en": "What is affected?" }, "required": true }]
},
"portal": { "portalId": "<portal id>", "categoryId": "<category id>" },
"record": { "title": "Outage: {{fields.affected}}", "impact": "high", "urgency": "medium" },
"teamId": null,
"notify": "unit",
"live": true
}
creates:case(the form only opens cases), or a kind of Create a record that can be made from a case and whose plugin is on:incidents.incident,changes.change,changes.standard.form: whatPOST /api/v1/formstakes for the unit, name, title, description, icon,fields,askName,askSubject,browserMode,defaultPriority,labels,successMessageandaudience(anyone,signed_inorstaff). On a portal,browserModedefaults to off and the title to the name.record: the record's fields, as in Create a record; the form's answers are{{fields.<key>}}of its own questions.teamId,notify(none,unitoruserwithnotifyUserId): the rule also puts the case in the team's queue, or tells the unit's agents or a person.ruleName: the rule's name; left out, the form's name and what it does.live:falsecreates the form turned off.
A rule is written when the form creates a record, routes to a team or tells someone: trigger submitted on this form,
in its unit, run as System. Whoever sends it needs form.settings in the form's unit (the endpoints are under
Form settings); for a rule, rule.settings in the organization; for a portal, portal.settings on it; and for a
record, the permission to create it in the form's unit themselves (incident.write, change.write, and
change.template too for a standard change), though the rule then creates as System. A unit or portal of another
organization, or one they don't manage, answers 400 (invalid_unit, invalid_portal). The audit log gets
form.created, portal.form_added and rule.created.
Examples
Declare an incident when a portal form for outages is submitted:
curl -X POST "$DESK_URL/api/v1/rules" -H "Authorization: Bearer $DESK_TOKEN" -H "Content-Type: application/json" -d '{
"name": "Outage form declares an incident",
"unitId": "<the form unit>",
"trigger": "submitted",
"formId": "<form id>",
"actions": [
{ "type": "create_record", "target": "incidents.incident",
"fields": { "title": "{{title}}", "summary": "{{fields.details}}\n\nFrom {{ref}}: {{url}}", "impact": "high", "urgency": "high" } }
]
}'
Remind the assignee of cases waiting more than 8 hours:
{
"name": "Waiting too long",
"unitId": "<unit id>",
"trigger": "scheduled",
"conditions": [{ "field": "hours_waiting", "op": "gt", "value": 8 }],
"actions": [{ "type": "notify", "target": "assignee" }]
}
[!TIP] For an AI assistant: find the ids first (
describe_desk,list_units, orGET /api/v1/formsandGET /api/v1/teams), try the conditions withPOST /api/v1/rules/test, and save the rule withenabled: falseuntil a person has looked at it.