Take a shadowing workflow live

Search Documentation

Search across all developer documentation

workflows

Take a shadowing workflow live

POST /v1/workflows/{id}/go-live
Auth required api.workflows.manage

Arms a workflow that has been running in shadow for real, with no re-authoring. A workflow whose send_mode is not shadow is 409 conflict. On writes {id} resolves as a UUID or, failing that, as the workflow's org-unique key.

In one transaction it:

  • Ends the shadow runs. Every live run with shadow: true is cancelled with exit_kind: "shadow_ended". That is what frees each contact's one-live-run slot so the same people can enrol for real — and it is why a shadow run never coexists with a live one.
  • Flips the mode to live and clears the cohort and shadow_started_at.
  • Re-registers the backfills. The workflow_backfills unique key is lifetime, so the shadow's rows are deleted first and registered again. The back catalogue is then enrolled for real rather than rehearsed.

The send-approval rule is applied exactly as at activation: a flow containing a send statement at any depth has the gate forced ON.

A contact the shadow observed can enrol again even under reentry once — the re-entry guards ignore shadow runs entirely, which is what makes going live clean.

Classified destructive and refused for a sk_test_ credential: it ends in-flight journeys and turns recorded sends into real email.

Path parameters

id string required

The workflow's UUID or, when the value does not parse as a UUID, its org-unique key. Unknown handles read as 404 not_found. Accepted on every workflow route, read and write alike.

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 The live workflow, what the shadow observed, and what going live started application/json
workflow object required

A drip-workflow definition — metadata only. The .flow source, canvas layout, content hash and profile versions are intentionally not exposed.

Fields of workflow:

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.

cancelled_shadow_runs integer required

How many in-flight shadow runs were ended with exit_kind shadow_ended. Zero is a real answer — a shadow can be armed and taken live before anybody triggers it.

shadow object required

What a shadow observed. would_send counts recorded sends regardless of the consent verdict and filtered_by_consent the subset that would have been dropped, so the mail that would actually leave is the difference. They are separate numbers because the question before going live is both "how much mail is this" and "how much of my audience does it not reach", and one number answers neither.

Fields of shadow:

active boolean required

Whether the workflow is shadowing right now. False on one that has gone live, where the counts are the rehearsal's record.

started_at string<date-time> | null required

When the shadow began. Null when it never shadowed.

enrolled integer<int64> required

Distinct runs the shadow created.

would_send integer<int64> required

Recorded sends across every send node.

filtered_by_consent integer<int64> required

The recorded sends the consent re-check would have dropped.

would_set_attribute integer<int64> required
would_add_to_list integer<int64> required
filter_reasons object required

filtered_by_consent broken down by cause (suppressed, unsubscribed_all, opted_out, not_subscribed, frequency_cap). {} when nothing was filtered.

require_send_approval boolean required

The gate as it stands AFTER this call. Going live applies the same rule activation does: a flow with a step that sends email or calls a webhook has it forced on.

send_steps integer | null required

The count that decision was made on. Null when the stored source could not be read, in which case the gate was forced on.

backfills array<object> required

The freshly re-registered enroll existing rows. Going live clears the shadow's backfill ledger and registers it again, so the back catalogue is enrolled for real rather than rehearsed.

Each entry in backfills:

trigger_kind string required

segment, event, activity or date_rel.

key string required

The segment key

status string optional

pending, running, done or failed.

window_start string<date-time> | null optional

Null for an unbounded backfill — the whole back-catalogue.

window_end string<date-time> | null optional
estimate integer<int64> | null optional

APPROXIMATE — a count over the same window the backfill will scan, taken before it runs. Null when it could not be sized, which is not the same as zero.

401 Missing, malformed, or unknown API key application/problem+json
403 Either the credential lacks the required scope (code: invalid_scope), or it is bound to the test environment and this operation is irreversible (code: test_environment_forbidden). Branch on code: the first is fixed by granting the scope, the second only by using a live credential. See the "Live and test credentials" section of the API description. application/problem+json
404 Resource not found application/problem+json
409 The mutation is rejected by a state rule rather than a bad request. The code is conflict. application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json