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
| Identifier | Starts a run when |
|---|---|
alert.triggered | Moneat fires an alert lifecycle event |
alert.resolved | Moneat resolves an alert episode |
monitor.alerted | A telemetry monitor enters an alerting state |
monitor.recovered | A telemetry monitor recovers |
uptime.down | An uptime monitor reports a failure |
uptime.up | An uptime monitor recovers |
synthetic.failed | A synthetic test fails |
synthetic.passed | A synthetic test passes |
incident.created | An incident is created |
incident.resolved | An incident is resolved |
incident.role_changed | An incident response role changes |
security.signal | Moneat creates a security signal |
manual | An authorized operator starts a run |
api | An authenticated client submits a typed input payload |
webhook | A request passes the workflow's signature verification |
schedule | An 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, andincident.acknowledged - Routing decisions:
alert.route_matched,alert.group_created,alert.group_updated,alert.group_decision,incident.triage_accepted,incident.triage_declined, andincident.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.
| Reference | Type or meaning |
|---|---|
incident.priority | Priority key, from P0 through P5 |
incident.priority.rank | 1 for P0 through 6 for P5 |
incident.priority.source | linked_alert, not_set, or normalization_error |
incident.type | Incident type key |
incident.team | Owning team ID |
incident.visibility | ORGANIZATION, PRIVATE, or PUBLIC |
incident.mode | Incident mode |
incident.custom_fields | Values from the declaration and acceptance forms, keyed by field key |
incident.declared_by | ID of the user who declared the incident |
incident.activated_at | Time the incident became active; absent while it is in triage |
incident.withheld | References 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:
| Reference | Type or meaning |
|---|---|
alert.title | Alert title |
alert.description | Alert description |
alert.priority | Priority from P0 through P5 |
alert.status | Firing or resolved state |
alert.source | Telemetry source |
alert.deduplication_key | Stable identity of the underlying condition |
alert.episode_key | Identity of the current alert episode |
alert.notification_sequence | Notification number within the episode |
alert.notification_kind | Initial, reminder, or resolved notification |
alert.opened_at | Time the episode opened |
alert.last_seen_at | Last time the episode was observed as active |
alert.url | Link to the alert in Moneat |
organization.id | Organization 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.
{
"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
| Identifier | Result |
|---|---|
notification.email_org | Email verified organization members |
notification.slack | Post to the configured Slack alert channel |
notification.discord | Post to the configured Discord alert channel |
Telemetry and Moneat resources
| Category | Identifiers and primary outputs |
|---|---|
| Logs | moneat.logs.search (logs: Object), moneat.logs.aggregate (aggregate: Object) |
| Metrics | moneat.metrics.query (metrics: Object) |
| Traces | moneat.traces.search (traces: Array), moneat.span.get (span: Object) |
| Issues | moneat.issues.list (issues: Array), moneat.issues.get (issue: Object) |
| Status pages | statuspage.update (status_page: Object), statuspage.incident.create (incident: Object) |
| Alerts | alert.silence (silence: Object) |
| On-call | oncall.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.
| Parameter | Values |
|---|---|
target | escalation_policy (default) pages escalation_policy_id; owning_team pages the escalation policy of the incident's owning team |
urgency | HIGH 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, andincident.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, andincident.follow_up.cancel - Lifecycle and coordination:
incident.update.publish,incident.acknowledge,incident.request_decision,incident.timeline.add,incident.resolve,incident.close,incident.cancel, andincident.reopen - Status and chat:
incident.status_page.create,incident.status_page.update,incident.status_page.resolve,incident.slack.message,incident.channel.create, andincident.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.
| Action | Behavior |
|---|---|
incident.channel.create | Creates 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.post | Posts 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.assign | Gives 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:
| Actions | Attempts | First wait |
|---|---|---|
oncall.page | 5 | PT2S |
Incident actions (incident.*, oncall.incident.*), statuspage.update, and the Moneat log, metric, trace, span and issue reads | 3 | PT2S |
| Every other action | 1 | PT1S |
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
| Identifier | Primary outputs |
|---|---|
connector.jira.create_issue | issue_id: ResourceReference, issue_key: String, url: String |
connector.github.create_issue | issue_id: ResourceReference, issue_number: Number, url: String |
connector.pagerduty.trigger_incident | incident_id: ResourceReference, incident_number: String, status: String, url: String |
connector.servicenow.create_incident | incident_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
| Identifier | Purpose |
|---|---|
condition.if | Choose between true and false branches |
condition.switch | Choose among named cases |
condition.working_hours | Choose between during and outside a working-hours calendar's hours |
condition.incident_decision | Choose the first case the run's incident matches |
control.sleep | Pause for a duration |
control.wait_until | Wait for a correlated event or timeout |
control.wait_until_working_hours | Hold the run until a working-hours calendar's hours open |
control.for_each | Iterate over a bounded collection |
control.while | Repeat while a bounded condition matches |
control.approval | Wait for an eligible operator's decision |
control.parallel | Start bounded parallel branches |
control.join | Join branches using the configured policy |
control.subflow | Run a pinned published workflow version |
control.break | Leave the current loop |
control.continue | Start the next loop iteration |
control.stop | End 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.
| Parameter | Values |
|---|---|
calendar_id | ID of a working-hours calendar |
time_basis | run_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.
{
"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"
}
]
}
}| Field | Operators | Values |
|---|---|---|
SEVERITY | IS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SET | SEV-0 through SEV-4 |
PRIORITY | IS_ONE_OF, IS_NOT_ONE_OF, AT_LEAST, IS_SET, IS_NOT_SET | Priority keys, such as P1 |
INCIDENT_TYPE | IS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SET | Incident type keys |
OWNING_TEAM | IS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SET | Team IDs |
VISIBILITY | IS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SET | ORGANIZATION, PRIVATE, PUBLIC |
CUSTOM_FIELD | IS_ONE_OF, IS_NOT_ONE_OF, IS_SET, IS_NOT_SET | Any text; set fieldKey to the field's key |
WORKING_HOURS | WITHIN, OUTSIDE | None; set calendarId, and optionally timeBasis |
IS_ONE_OFandIS_NOT_ONE_OFneed at least one non-blank value.AT_LEASTtakes exactly one priority and matches it and every more urgent priority;P0is the most urgent.timeBasisisACTIVATION(the time the incident became active) orSTEP(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 field | Values |
|---|---|
trigger_name | The workflow's trigger |
payload | Trigger data, keyed by the trigger's scope references |
target | draft (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 |
version | Version number, with target: "version" |
graph | Edited graph to test, with target: "draft" |
once_for_template | Edited run-once references, with target: "draft"; a saved version uses its own |
preview | Planning 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 field | Values |
|---|---|
activation_at | ISO-8601 instant the run starts; absent means now |
time_zone | IANA 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 |
scenarios | Up to 6 acknowledgement assumptions to compare; absent compares never, PT5M, and PT30M |
expected_fingerprint | The 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:
| Contract | Nodes | In a test run |
|---|---|---|
evaluated | Conditions, switches, working-hours and incident decisions, sleeps, loops, control.stop | Decided from the sample and the current calendars and priorities, at the instant the step runs |
predicted | Moneat queries, incident steps other than Slack delivery, oncall.page, incident.role.assign | Reported as would_run at its instant; later steps can't read its outputs |
assumed | control.wait_until | A wait for incident.acknowledged follows the acknowledgement assumption; a wait for any other event is unknown |
unknown | notification.*, incident.channel.create, incident.announcement.post, incident.slack.message, http.request, transform.graaljs, connector.*, oncall.incident.investigate, control.approval, control.parallel, control.join, control.subflow | Not 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
| Area | Limit |
|---|---|
| Conditions | 32 per node |
| Expression depth | 8 levels |
| Children per expression group | 32 |
| Condition value length | 4,096 characters |
| Incident decision predicate depth | 4 levels |
| Incident decision conditions | 40 per case |
| Incident decision condition values | 50 per condition |
| Parallel branches | 16 |
| Loop items or while iterations | 2,000 |
| Iteration concurrency | 32; default 1 |
| Subflow depth | 8 |
| Subflow input/output mappings | 64 entries |
| Wait correlation | 8 fields, 512 characters per value |
| Buffered wait signals | 16 per correlation key |
| Draft sample fields | 128 |
| Draft test payload | 128 KiB |
| Test-run acknowledgement assumptions | 6 |
| Test-run scenarios | 8 |
| Alternative paths a test run follows | 32 |
| Steps in an escalation preview | 100 |
| Run-history page size | 100 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.
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:
| Operation | Route |
|---|---|
| Retry | POST /v1/workflows/{workflowId}/instances/{runId}/retry |
| Resume | POST /v1/workflows/{workflowId}/instances/{runId}/resume |
| Replay | POST /v1/workflows/{workflowId}/instances/{runId}/replay |
| Move to dead letter | POST /v1/workflows/{workflowId}/instances/{runId}/dead-letter |
| Cancel | PUT /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.