Restore a previous version of a workflow
Reinstates a previous definition. A restore is an ordinary edit whose content happens to be old: it snapshots the state it replaces in the same transaction, so nothing is lost, the restore is itself in the history, and restoring the version it created undoes it. That is why this operation is NOT classified destructive, and why a test-environment credential may call it.
It bypasses the optimistic-concurrency check by design — "whatever is there now, make it this" is not a statement about what the caller last read — and it deletes no history.
A restore does NOT change the workflow's status and does not cancel, re-enrol or re-version its in-flight runs: each run keeps the version it enrolled under. Restoring a source under an active workflow is a live edit, exactly as an ordinary update of the same workflow is.
Path parameters
id string required The workflow's UUID or its org-unique key.
version integer required The version number, as returned by the versions collection. Version numbers are the handle on every kind here, including the kinds whose dashboard routes address a version by row id.
Responses
Errors follow the RFC 7807 problem format — see the error reference.
kind string required restored integer required The version number that was reinstated.
current object required The definition as it now stands, in the shape the kind's own read route returns.
version object required One row of a definition's history.
Fields of version:
version_number integer required created_at string<date-time> required When the snapshot was taken, which is when the edit that REPLACED this state was made — not when this state was authored.
actor object required Who made one edit. The label is resolved server-side — a person's name, an API key's name, an OAuth client's name — because the name lives in a different table per kind and a client can read none of them. It never carries a key, a token or a client secret; a revoked credential falls back to a short form of its id rather than rendering blank.
Fields of actor:
kind string enum required unknown is an honest value rather than an error: it is what rows predating actor recording carry, and what a write reached with no credential records.
One of: user, api_key, oauth_client, system, unknown
id string optional The users / api_keys / oauth_clients id, per kind. Absent for system and unknown.
label string required session string optional The HTTP request id of the edit — the same value on the matching audit row, so the two can be joined. It is NOT an MCP session id: the MCP transport is stateless and reads no session header.
content_hash string optional summary string required How this version differs from the one before it — +3 −1 lines for a source kind, renamed, type string→number for a schema kind. The oldest row says first recorded version.
current object optional A drip-workflow definition — metadata only. The .flow source, canvas
layout, content hash and profile versions are intentionally not exposed.
Fields of current:
id string<uuid> required name string required key string required The workflow's stable slug within the org.
description string optional status string required Lifecycle status: draft, active, paused, archived, or invalid.
invalid_reason string optional Present only when status is invalid.
require_send_approval boolean optional When true, sends are held for manual review instead of dispatching automatically.
send_mode string enum required shadow means the workflow is ACTIVE and enrolling real contacts but mailing nobody: every send, attribute write and list add is recorded on the run timeline instead of performed. A shadow workflow that looks broken is working as configured — POST /v1/workflows/{id}/go-live arms it for real.
One of: live, shadow
shadow_started_at string<date-time> optional When the current shadow began. Absent while live.
shadow_cohort_list_id string<uuid> optional The list bounding who may enrol while shadowing. Absent while live or when the shadow is unbounded.
shadow_cohort object optional That list resolved: which list, and how many people the rehearsal can reach. Absent while live or when the shadow is unbounded. The bare shadow_cohort_list_id stays beside it because it is the handle activate takes back.
current_version integer required The live definition version.
run_counts object required By-status run tally, e.g. active/completed → counts. Always present.
created_at string<date-time> required updated_at string<date-time> required source string optional The .flow program. Present only for a caller holding api.workflows.manage — the read scope withholds it — and on every response to a write, which returns what was stored.
content_hash string optional The optimistic-concurrency token. Pass it back as expected_content_hash on the next update and a concurrent edit is refused rather than overwritten. Present alongside source.
warnings array<string> optional The analyzer's non-fatal findings from the write that produced this row. A definition can be perfectly valid and still not do what somebody asked for, so these survive a successful save — read them.
diagnostics array<object> optional Every positioned finding, errors as well as warnings, from validating the source this call wrote. This is what makes a 201 with status: invalid actionable: the row was stored, and this says why it cannot run yet. Absent when none were gathered.
Each entry in diagnostics:
line integer | null optional column integer | null optional severity string required error blocks the definition from running; warning does not.
message string required requires array<object> optional Everything this definition still depends on, with each dependency's state. Present on the write responses and on the detail read (the polling surface); absent on the collection read. An empty array means nothing is outstanding — see V1Requirement.
Each entry in requires:
kind string enum required The namespace key is looked up in. Branch on this; never parse detail.
One of: template, segment, list, topic, identity, channel, attribute, activity_property, asset
key string required The identifier as the definition names it — a template name, a segment id or key, a from-address. Empty for a requirement about the ABSENCE of a reference: a send with no topic at all names no topic.
status string enum required ok — resolves and is usable now.
missing — nothing in this organization answers to that key. Correct
the reference, or create the thing.
not_ready — it exists, in a state that cannot be used yet. Usually
a wait, and retrying the identical request is the move. Treating this
as missing sends you hunting for a typo that is not there.
One of: ok, missing, not_ready
detail string required The state, in the words that say which state — deploy_state=pending, member_count=null (evaluation queued). For display and logs; do not parse it.
fix string required The call that clears this requirement, as an HTTP route where there is one and as a plain instruction where there is not (a template deploy is waited for, not called). Empty on an ok entry.
code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json