Workflows Reference

This page lists the workflow identifiers, contracts, and limits used by the dashboard, API, Terraform, and automation clients. For a task-oriented introduction, see the Workflows guide.

The authenticated GET /v1/workflows/catalog response is the canonical contract for the triggers, scope fields, actions, parameters, outputs, and graph nodes available to the current organization. The catalog accounts for the installed edition and enabled capabilities; clients should use it instead of hard-coding the lists below.

Triggers

Common triggers

IdentifierStarts a run when
alert.triggeredMoneat fires an alert lifecycle event
alert.resolvedMoneat resolves an alert episode
monitor.alertedA telemetry monitor enters an alerting state
monitor.recoveredA telemetry monitor recovers
uptime.downAn uptime monitor reports a failure
uptime.upAn uptime monitor recovers
synthetic.failedA synthetic test fails
synthetic.passedA synthetic test passes
incident.createdAn incident is created
incident.resolvedAn incident is resolved
incident.role_changedAn incident response role changes
security.signalMoneat creates a security signal
manualAn authorized operator starts a run
apiAn authenticated client submits a typed input payload
webhookA request passes the workflow's signature verification
scheduleAn enabled schedule reaches an occurrence

Incident and routing triggers

Additional incident response and deterministic alert routing triggers include:

  • Incident changes: incident.lifecycle_changed, incident.status_changed, incident.severity_changed, incident.field_changed, incident.update_published, incident.action_changed, incident.follow_up_changed, incident.escalation_changed, incident.participant_changed, incident.message_received, incident.post_incident_started, and incident.acknowledged
  • Routing decisions: alert.route_matched, alert.group_created, alert.group_updated, alert.group_decision, incident.triage_accepted, incident.triage_declined, and incident.triage_merged

A published version can declare multiple compatible triggers. Every run records the trigger context that admitted it. Fields that are not present in every selected trigger are rejected where the graph requires a shared, unambiguous type.

Response rule trigger

incident.response starts a response rule's workflow when an incident becomes active: declared active, or accepted from triage. The catalog marks it response_rule: true. It can't be picked for a workflow; each response rule's workflow is created with it from Response rules.

A custom rule's trigger node keeps its entry conditions in params.entry_conditions, one incident predicate in the same shape as an incident decision case. A custom rule can't be published without them, and the default rule's trigger has none.

Replaying a response rule's run would page again, so Moneat refuses it. Retry the run instead.

Incident context

Runs started by incident.response, incident.created, incident.acknowledged, incident.resolved, incident.role_changed, and the incident change triggers above carry the incident's attributes. The triage triggers (incident.triage_accepted, incident.triage_declined, and incident.triage_merged) do not. The catalog marks each trigger that carries them with incident_context: true.

ReferenceType or meaning
incident.priorityPriority key, from P0 through P5
incident.priority.rank1 for P0 through 6 for P5
incident.priority.sourcelinked_alert, not_set, or normalization_error
incident.typeIncident type key
incident.teamOwning team ID
incident.visibilityORGANIZATION, PRIVATE, or PUBLIC
incident.modeIncident mode
incident.custom_fieldsValues from the declaration and acceptance forms, keyed by field key
incident.declared_byID of the user who declared the incident
incident.activated_atTime the incident became active; absent while it is in triage
incident.withheldReferences this run can't read, each with the reason: private, too_large, or sensitive

An incident's priority is the highest priority among its linked alerts. normalization_error means a linked alert carries a value that is not one of P0 through P5. When a form field was filled in at declaration and again at acceptance, the acceptance value is used. References with no value are left out of the run's scope.

A run reads the incident as it was when the event happened, even when the run starts later.

A private incident's type, owning team, custom fields, and declarer reach a run only when the workflow's Runs as identity can view the incident. Otherwise they are listed under incident.withheld with the reason private.

A run reads up to 64 custom fields of up to 4,096 characters each, with credential-like values redacted. A field that an incident decision reads is always included, or listed under incident.withheld as too_large or sensitive when it can't be read whole.

Alert context

The catalog supplies the complete scope for each trigger. Frequently used alert references include:

ReferenceType or meaning
alert.titleAlert title
alert.descriptionAlert description
alert.priorityPriority from P0 through P5
alert.statusFiring or resolved state
alert.sourceTelemetry source
alert.deduplication_keyStable identity of the underlying condition
alert.episode_keyIdentity of the current alert episode
alert.notification_sequenceNotification number within the episode
alert.notification_kindInitial, reminder, or resolved notification
alert.opened_atTime the episode opened
alert.last_seen_atLast time the episode was observed as active
alert.urlLink to the alert in Moneat
organization.idOrganization identifier

Dashboard alerts also expose alert.dashboard.title, alert.widget.title, alert.condition, alert.threshold, and alert.current_value.

Conditions and schemas

Conditions support nested and, or, and not groups, equality and membership, regular expressions, numeric comparisons, date/time comparisons, null predicates, and supported changed-from/changed-to predicates. Operators are checked against the reference type during validation.

Equality and membership do not match a missing value unless a null predicate is used. Ordering and regular-expression operators reject missing or incompatible values.

JSON
{
  "operator": "and",
  "children": [
    {"operator": "eq", "reference": "alert.priority", "value": "P1"},
    {"operator": "contains", "reference": "telemetry.service.name", "value": "checkout"}
  ]
}

Use input_schema for workflows started manually, through the API, by signed webhook, or as a subflow. Inputs and step outputs preserve bounded strings, numbers, booleans, objects, arrays, timestamps, and resource references. Secrets are not available to expressions; connector actions receive credentials only through protected connection parameters.

Actions

Notifications

IdentifierResult
notification.email_orgEmail verified organization members
notification.slackPost to the configured Slack alert channel
notification.discordPost to the configured Discord alert channel

Telemetry and Moneat resources

CategoryIdentifiers and primary outputs
Logsmoneat.logs.search (logs: Object), moneat.logs.aggregate (aggregate: Object)
Metricsmoneat.metrics.query (metrics: Object)
Tracesmoneat.traces.search (traces: Array), moneat.span.get (span: Object)
Issuesmoneat.issues.list (issues: Array), moneat.issues.get (issue: Object)
Status pagesstatuspage.update (status_page: Object), statuspage.incident.create (incident: Object)
Alertsalert.silence (silence: Object)
On-calloncall.page (alert_id: ResourceReference, incident_id: ResourceReference, incident_reference: String, replayed: Boolean, message: String, skipped: Boolean, reason: String), oncall.incident.declare (incident_id: ResourceReference), oncall.incident.action.create (action_id: ResourceReference), oncall.incident.investigate (investigation_id: ResourceReference, status: String, created: Boolean)

oncall.page needs a plan that includes on-call. The other on-call actions and the incident actions need a plan that includes on-call and incident response. The default Team and Business plans include both. A workflow that uses an action the organization's plan leaves out still saves, but publishing it, testing it or running it manually fails with On-call is not available on your current plan. or Incident actions are not available on your current plan. On-call and incident actions output opaque resource identifiers.

oncall.page pages once per step: a retried step returns the alert it already created, with replayed: true, and each loop iteration pages again. When the run belongs to an incident and runs as an organization member, the alert is linked to that incident before the step completes; incident_id and incident_reference (such as INC-12) name it, and message says why when the alert could not be linked. If Moneat cannot confirm the page, the step tries again and fails once its attempts run out. Check on-call alerts before you replay the run, because a replay pages again.

ParameterValues
targetescalation_policy (default) pages escalation_policy_id; owning_team pages the escalation policy of the incident's owning team
urgencyHIGH or LOW pages at that urgency; empty uses the urgency the priority sets. An escalation step's own urgency still wins

Paging the owning team fails the step when the run has no incident, the incident has no owning team, or the team has no escalation policy. A page for the run's incident fails when the workflow runs as someone who can't view the incident. A page for an incident that has been resolved, closed, cancelled, declined or merged is skipped with skipped: true and the reason, as is a page for a test or retrospective incident when the organization's paging settings don't page those.

Incident response

Incident response actions include:

  • Participation and roles: incident.invite_user, incident.invite_group, incident.participant.add, incident.observer.add, incident.role.assign, incident.role.claim, incident.role.unassign, and incident.role.handover
  • Actions and follow-ups: incident.action.create, incident.action.update, incident.action.claim, incident.action.reassign, incident.action.complete, incident.action.cancel, incident.follow_up.create, incident.follow_up.update, incident.follow_up.accept, incident.follow_up.complete, and incident.follow_up.cancel
  • Lifecycle and coordination: incident.update.publish, incident.acknowledge, incident.request_decision, incident.timeline.add, incident.resolve, incident.close, incident.cancel, and incident.reopen
  • Status and chat: incident.status_page.create, incident.status_page.update, incident.status_page.resolve, incident.slack.message, incident.channel.create, and incident.announcement.post

Execution requires the corresponding incident response capabilities and access. An incident step acts on the incident it names in incident_id, or on the incident that started the run when that is empty. Each visit of a step issues its incident commands under one key, so a retried attempt replays the first attempt instead of acting twice, even when the incident has changed since and no longer matches expected_version.

ActionBehavior
incident.channel.createCreates the incident's Slack channel in each connected workspace, named by your channel settings. A workspace that already has the channel keeps it, and a channel whose incident ends before Slack finishes creating it is archived. Outputs requested, existing, and failed channel counts. Only live incidents get a channel
incident.announcement.postPosts the incident's announcement card to the announcements in announcements, or wherever the incident is announced automatically when that is empty. Each announcement's settings decide: it must be turned on, the incident must match its minimum severity and incident types, and a private incident needs an announcement that includes private incidents. Test incidents aren't announced. Each channel keeps one card per incident, and the card follows the incident's changes. Outputs posted, not_allowed, and failed
incident.role.assignGives role_id to assignee: user (assignee_user_id), schedule (whoever is on call for schedule_id), declarer, or acknowledger. A role someone already holds is left with them, with held: true

An announcement's target must be the announcement channel of a connected Slack workspace, written as its name (#incidents) or its channel ID; the step fails when no connected channel matches.

With assignee: acknowledger, the role goes to whoever acknowledges page_alert_id, an alert a page step in the same run sent, such as {{steps.page.output.alert_id}}. If the page is already acknowledged the role is assigned at once; otherwise the step completes with pending: true and pending_until, and the acknowledgement fills the role. The wait ends without filling the role when expires_after passes (default PT1H, at most P7D), when the incident is resolved, closed, cancelled, declined or merged, or when someone else changes the role.

Participant, role, channel and announcement steps skip with skipped: true and the reason once the incident has ended. Every incident action fails, without retrying, once the organization's plan no longer includes incident actions.

Retries

A step that sets no retry policy uses its action's default:

ActionsAttemptsFirst wait
oncall.page5PT2S
Incident actions (incident.*, oncall.incident.*), statuspage.update, and the Moneat log, metric, trace, span and issue reads3PT2S
Every other action1PT1S

Waits double between attempts, up to 30 seconds. Moneat repeats an attempt that times out, or a page it can't confirm; a step that reports an error fails without retrying.

Connectors and egress

IdentifierPrimary outputs
connector.jira.create_issueissue_id: ResourceReference, issue_key: String, url: String
connector.github.create_issueissue_id: ResourceReference, issue_number: Number, url: String
connector.pagerduty.trigger_incidentincident_id: ResourceReference, incident_number: String, status: String, url: String
connector.servicenow.create_incidentincident_id: ResourceReference, incident_number: String, url: String

Connector actions require an authorized workflow connection. Delivery keys make confirmed provider operations idempotent across retries.

The http.request and transform.graaljs actions are available only when WORKFLOWS_EGRESS_ENABLED=true on an isolated egress deployment. Unavailable editions, entitlements, connections, or permissions fail closed during validation or execution.

Control nodes

IdentifierPurpose
condition.ifChoose between true and false branches
condition.switchChoose among named cases
condition.working_hoursChoose between during and outside a working-hours calendar's hours
condition.incident_decisionChoose the first case the run's incident matches
control.sleepPause for a duration
control.wait_untilWait for a correlated event or timeout
control.wait_until_working_hoursHold the run until a working-hours calendar's hours open
control.for_eachIterate over a bounded collection
control.whileRepeat while a bounded condition matches
control.approvalWait for an eligible operator's decision
control.parallelStart bounded parallel branches
control.joinJoin branches using the configured policy
control.subflowRun a pinned published workflow version
control.breakLeave the current loop
control.continueStart the next loop iteration
control.stopEnd the run as completed; steps in other branches that haven't started don't start. Its optional reason appears in run history

A stop step can't lead to other steps. Only action nodes can originate error edges. Graph cycles are rejected except inside bounded loop bodies. Parallel joins support all, any, quorum, and timeout behavior. Recursive subflows, protected values, missing outputs, incompatible mappings, and unbounded fan-out are rejected.

Working hours

condition.working_hours takes the true branch during the calendar's working hours and the false branch outside them.

ParameterValues
calendar_idID of a working-hours calendar
time_basisrun_start (default) checks the time the run started; step checks the time the step runs

control.wait_until_working_hours takes calendar_id. When the step runs inside working hours it continues at once; otherwise it holds the run until the calendar's next opening and then follows its outgoing edge. The step fails when the calendar has no working hours in the next 400 days.

A run pins the revision of every calendar its graph reads when the run starts, so editing a calendar does not change what a running workflow decides. Retries and replays keep the source run's revisions. Both nodes fail when their calendar no longer exists.

Incident decisions

condition.incident_decision needs a plan that includes incident response and runs only under triggers that carry incident context. When the organization's plan leaves it out, the workflow still saves, but publishing or testing it fails with Incident decisions are not available on your current plan., and so does the decision step in a run. It checks its cases in order and takes the first case the incident matches; when none match it takes the default branch. Edges leave it by case name or default, as with condition.switch.

Each case carries a predicate: a condition, or a group that combines conditions with ALL or ANY.

JSON
{
  "name": "urgent-after-hours",
  "label": "Urgent, after hours",
  "conditions": [],
  "predicate": {
    "type": "group",
    "match": "ALL",
    "conditions": [
      {"type": "condition", "field": "PRIORITY", "operator": "AT_LEAST", "values": ["P1"]},
      {
        "type": "condition",
        "field": "WORKING_HOURS",
        "operator": "OUTSIDE",
        "calendarId": "3f0c9a52-7d1e-4b8a-9c61-2f5e8d4a1b70",
        "timeBasis": "STEP"
      }
    ]
  }
}
FieldOperatorsValues
SEVERITYIS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SETSEV-0 through SEV-4
PRIORITYIS_ONE_OF, IS_NOT_ONE_OF, AT_LEAST, IS_SET, IS_NOT_SETPriority keys, such as P1
INCIDENT_TYPEIS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SETIncident type keys
OWNING_TEAMIS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SETTeam IDs
VISIBILITYIS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SETORGANIZATION, PRIVATE, PUBLIC
CUSTOM_FIELDIS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SETAny text; set fieldKey to the field's key
WORKING_HOURSWITHIN, OUTSIDENone; set calendarId, and optionally timeBasis
  • IS_ONE_OF and IS_NOT_ONE_OF need at least one non-blank value.
  • AT_LEAST takes exactly one priority and matches it and every more urgent priority; P0 is the most urgent.
  • timeBasis is ACTIVATION (the time the incident became active) or STEP (the time the step runs).
  • Groups need at least one condition.

A condition that can't be decided fails the step instead of taking a branch: a priority condition when a linked alert's priority is not one of P0 through P5, and any condition on a value listed under incident.withheld. Every decision step records in run history what it read and why it took its branch: the calendar revision and local time for working hours, and each case in order with the result of every condition for incident decisions.

Test runs

POST /v1/workflows/{workflowId}/test-run tests a workflow without running it. Nothing is paged or sent, and no alert, message, run, or priority catalog revision is created; the workflow's audit log records the test. With a preview object the test plans the run on a clock that starts at activation_at; without one it evaluates the fixtures in action_results and control_branches and stops at any action that has none.

Request fieldValues
trigger_nameThe workflow's trigger
payloadTrigger data, keyed by the trigger's scope references
targetdraft (default) tests graph and once_for_template when they're sent, and the most recent saved version otherwise; live tests the latest published version and returns 404 when there is none; version tests the saved version numbered version
versionVersion number, with target: "version"
graphEdited graph to test, with target: "draft"
once_for_templateEdited run-once references, with target: "draft"; a saved version uses its own
previewPlanning options, below

The result's version is the version tested; it is absent for edits. Each trigger in the catalog carries an example payload. POST /v1/workflows/{workflowId}/test-example takes trigger_name, graph, target, and version as a test run does, and returns that example with your organization, your user, and one of your teams in place of the placeholders, fitted to the trigger's conditions. The team is the first the conditions name, else one whose escalation policy is published, else one with an escalation policy, else any team; an organization with no teams gets an example with no team. On incident triggers, the incident.status, incident.previous_status, and incident.kind scope references list in options the values that trigger sets.

preview fieldValues
activation_atISO-8601 instant the run starts; absent means now
time_zoneIANA zone the result's times read in; default UTC
acknowledgement{"mode": "never"}, or {"mode": "after", "after": "PT10M"}: each page is acknowledged that long after it's sent, from 0 minutes to 7 days
scenariosUp to 6 acknowledgement assumptions to compare; absent compares never, PT5M, and PT30M
expected_fingerprintThe fingerprint of an earlier result; the test returns 409 when the result would differ

The catalog lists each step's and node type's preview contract, which says what a test run can know about it:

ContractNodesIn a test run
evaluatedConditions, switches, working-hours and incident decisions, sleeps, loops, control.stopDecided from the sample and the current calendars and priorities, at the instant the step runs
predictedMoneat queries, incident steps other than Slack delivery, oncall.page, incident.role.assignReported as would_run at its instant; later steps can't read its outputs
assumedcontrol.wait_untilA wait for incident.acknowledged follows the acknowledgement assumption; a wait for any other event is unknown
unknownnotification.*, incident.channel.create, incident.announcement.post, incident.slack.message, http.request, transform.graaljs, connector.*, oncall.incident.investigate, control.approval, control.parallel, control.join, control.subflowNot run; the test follows each way the run could go from it, and reports every step after parallel branches as unknown

Each step reports at, until for a step that waits, and a certainty: certain, assumed when it follows from the acknowledgement assumption, or conditional when it depends on something the test can't know. because lists the reasons, such as Only if “HTTP request” succeeds. A condition that reads an unknown value is unknown, and each of its branches is reported as conditional. A loop over an unknown list is followed both with no items and through its steps.

A run that continues after a wait for an approval or another event continues at a time the test can't know, up to the wait's timeout. From there each time is a window: at and until are the earliest instants and at_latest and until_latest the latest. A decision or acknowledgement wait whose result differs across the window is unknown, with each branch followed. A timeout's instant is exact. A page sent in a window reports page.notification_eligible_at_latest and leaves out its urgency, escalation, and acknowledgement due time; a role filled in a window reports assignment.assigned_at_latest.

An oncall.page step resolves its target the way a page does and walks the escalation policy's published version from the step's instant. page.notification_eligible_at and page.acknowledgement_due_at come from the policy's first level, and page.escalation lists every level and branch as the escalation preview does. A page for an incident that has ended, or for a test or retrospective incident your organization doesn't page, is skipped. An incident.role.assign step with the acknowledger assignee fills the role when the page is acknowledged within expires_after.

Each of preview.scenarios is the same run under one assumption, with its label, outcome, ends_at (and ends_at_latest when it ends in a window), pages, unknown_steps, and steps. kind is acknowledgement for an acknowledgement assumption. When the trigger carries an incident and the workflow decides on incident.priority, the list adds incident rows for the highest priority in your catalog that pages, its lowest priority, and no priority. When the workflow checks working hours, rows start inside and outside the first calendar it reads, at the start or at the calendar's next opening or closing, and each priority is compared both ways. activation_at is when each row starts. The list stops at 8 rows, and scenario_count is how many there were.

A run can branch more ways than a test follows. After 32 alternative paths the test stops, stopped_early is true, and the steps it didn't reach are unknown.

preview.fingerprint covers the workflow version, or the draft's contents, the sample, the start, zone, and assumptions, the revisions listed in preview.reads (calendars, priorities, escalation policies, schedules, and the people they reach), and the result itself. A change in who is on call therefore also makes an earlier result out of date. To recheck a result, send the same activation_at with its fingerprint as expected_fingerprint.

Limits

AreaLimit
Conditions32 per node
Expression depth8 levels
Children per expression group32
Condition value length4,096 characters
Incident decision predicate depth4 levels
Incident decision conditions40 per case
Incident decision condition values50 per condition
Parallel branches16
Loop items or while iterations2,000
Iteration concurrency32; default 1
Subflow depth8
Subflow input/output mappings64 entries
Wait correlation8 fields, 512 characters per value
Buffered wait signals16 per correlation key
Draft sample fields128
Draft test payload128 KiB
Test-run acknowledgement assumptions6
Test-run scenarios8
Alternative paths a test run follows32
Steps in an escalation preview100
Run-history page size100 results

Schedules

A schedule contains an id, cron expression, IANA timezone, enabled flag, missed_run_policy, overlap_policy, and max_concurrency.

Daylight-saving transitions use the named timezone. Overlap policies are reject, queue, replace, and bounded_parallel. Queued occurrences are promoted in creation order, and bounded parallelism never exceeds max_concurrency.

Terraform exposes schedules through schedules_json:

schedules_json = jsonencode([{
  id                = "maintenance"
  cron              = "0 2 * * 0"
  timezone          = "America/New_York"
  missed_run_policy = "skip"
  overlap_policy    = "queue"
  max_concurrency   = 1
}])

Signed webhooks

Read signing metadata from GET /v1/workflows/{workflowId}/webhook-signing. Sign the exact raw body with HMAC-SHA256, prefix the hexadecimal signature with sha256=, and send it in X-Moneat-Workflow-Signature. Use X-Moneat-Webhook-Event as the delivery identity.

Shell
body='{"incident_id":"<incident-uuid>","reason":"maintenance"}'
signature=$(printf '%s' "$body" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" -hex | sed 's/^.* //')
curl -X POST "https://api.moneat.io/v1/workflows/<workflow-uuid>/webhook" \
  -H 'Content-Type: application/json' \
  -H "X-Moneat-Workflow-Signature: sha256=$signature" \
  -H 'X-Moneat-Webhook-Event: maintenance-<uuid>' \
  --data "$body"

Execution and recovery

Execution history uses these outcomes: starting, running, succeeded, failed, cancelled, dead_letter, deduplicated, and rate_limited. Deduplicated and rate-limited admissions retain their reason and identity without counting as successful executions.

A run is starting until the workflow runtime accepts it. While the runtime is unavailable, the run stays starting and starts once the runtime is back. Each run starts at most once.

Instance recovery routes use public workflow and run UUIDs:

OperationRoute
RetryPOST /v1/workflows/{workflowId}/instances/{runId}/retry
ResumePOST /v1/workflows/{workflowId}/instances/{runId}/resume
ReplayPOST /v1/workflows/{workflowId}/instances/{runId}/replay
Move to dead letterPOST /v1/workflows/{workflowId}/instances/{runId}/dead-letter
CancelPUT /v1/workflows/{workflowId}/instances/{runId}/cancel

Retry, resume, replay, and dead-letter commands require an idempotency key or receive a server-generated one. Retry and replay each create a new run; repeating a command with the same key returns that run. Replaying uses the original immutable version without reusing the source run's progress.

Versions and access

Editing a published workflow creates a draft. Publishing creates an immutable version; active runs stay on the version that admitted them. Administrators can restore an older version into a new draft.

Workflow access is organization-scoped. Owners and grants provide Viewer, Runner, and Editor access. Service-principal grants authorize non-human subflow execution. Connections separately control metadata visibility, action use, and administration. Approval eligibility is checked against current membership and private-incident visibility when the response arrives.

Terraform

The Terraform provider exposes workflows through moneat_workflow. Use trigger_names, input_schema_json, schedules_json, and graph_json. Workflow owners are computed by the server, and connector credentials are never stored in workflow state.

Updates are pinned to the version read during refresh. A concurrent edit returns a conflict and requires Terraform to refresh before planning again.

Return to the Workflows guide for the dashboard workflow and a first notification example.