Endpoints
status is draft, active, paused, or archived. Structure (triggers, steps, reentry policy) is only editable while the flow is a draft or paused — pause an active flow before changing it, then reactivate.
Drafts may be incomplete. Step and trigger format is validated on every write, but completeness (a
send_email step without an email, a trigger without its list) is only enforced when you activate. Activation failures return the missing pieces.Create a Flow
Triggers and steps can be supplied here or configured incrementally before activation.
Request Example
A welcome series — send an email, wait three days, then follow up only if the first email wasn’t opened:check-opened (its true branch has no edge); everyone else gets the follow-up.
Response Example
201 Created with the flow object. All flow and step endpoints on this page return this same shape, except the member endpoints:
Triggers
A flow can have up to25 triggers; a contact entering via any of them joins the flow, subject to the reentry policy. Each trigger is an object discriminated on type:
Replace the full trigger list with:
Steps
Steps form a graph. Every step needs a uniquestep_id (1-120 characters) and links to the next step through edge fields — next_step_id for linear steps, or true_step_id / false_step_id / default_step_id on condition steps. Omitting the edge ends the flow after that step. An optional label names the step in the UI and member history.
Condition Rules
condition_check and condition-mode wait steps take the same rule tree as blast audiences, plus one extra source that is only legal inside flows: email_step, which tests engagement with an earlier send_email step.
metric is one of sent, delivered, opened, clicked, bounced, or unsubscribed, and nodeId references the step_id of a send_email step in the same flow.
Editing Steps
PUT takes {"steps": [...]} and replaces the whole list. POST inserts one step, wrapped in a step key, plus at most one placement selector — position (start or end, default end), before_step_id, or after_step_id:
*_step_id fields yourself to wire the step in. Duplicate step IDs return 409 Conflict with error_code: "campaign_flow_duplicate_step_id".
PATCH also wraps the partial update in a step key; only the provided fields are merged onto the existing step, and step_id cannot be changed:
409 Conflict (campaign_flow_step_referenced) unless you pass repair_edges:
null as the replacement to detach the dangling edges instead.
Set a Step’s Email
send_email steps. Takes the same body as a blast’s Set the Email: {"source": "existing", "email_id": "..."} or an inline email seeded from a template_id, your own html, or a blank branded scaffold. Inline emails start as drafts and must be approved before the flow can activate — the created email_id appears on the step in the response.
Lifecycle
POST .../activate returns 403 Forbidden with error_code: "paid_plan_required":
400 Bad Request with error_code: "campaign_flow_not_ready" and the missing fields:
added_to_static_list flow retroactively.
Members
Add a Contact
{{campaign.dynamic.<key>}} merge tokens:
{{contact.*}} and {{campaign.dynamic.*}} alike — accept an optional case format, e.g. {{campaign.dynamic.couponCode:uppercase}}. See Formatting values.
Enrollment respects the flow’s rules: 409 Conflict if the contact is already in the flow (campaign_flow_already_in_flow) or blocked by no_reentry (campaign_flow_reentry_denied), and 400 Bad Request if disqualification rules match (campaign_flow_contact_disqualified).
201 Created
Trigger a Flow (API trigger)
tented_api trigger — 409 Conflict (flow_api_trigger_not_configured) if the flow doesn’t have one. Identify the contact with exactly one of contact_id, email, or phone (the latter two are looked up against existing contacts), and optionally pass token values:
{{campaign.dynamic.<key>}} merge token for the run’s emails and webhooks — {{campaign.dynamic.coupon_code}} above. The rules:
- Values must be JSON scalars (string, number, or boolean); keys start with a letter and use letters/numbers/underscores.
"trigger"is reserved. - If the flow’s
tented_apitrigger declaresfields, keys are restricted to the declared names and values must match the declared types (campaign_flow_trigger_unknown_token/campaign_flow_trigger_invalid_token_type). A trigger with no declared fields accepts any keys. - The resolved payload must stay under 64 KB (
campaign_flow_trigger_payload_too_large).
201 Created with the membership.
Remove a Contact
200 OK with the updated membership, or 204 No Content if the contact was not in the flow.
List Members
Each member entry includes the contact identity, run status, current step, timestamps, and the steps executed so far.
Run Details
timeline of events (entered flow, step executed, email sent/delivered/opened/clicked, dropped) with timestamps.
List Flows
status (draft, active, paused, archived, or any), archived, sort_by (updated_at, created_at, name), sort_order, page, limit, and search. Responses contain flows and a pagination object. Each flow includes aggregate details — total_entered, active_count, waiting_count, completed_count, dropped_count, and most_recent_entry_at.
Update, Archive, Delete
PATCH /v1/email-flows/{flowId}patchesname,description,reentry_policy,triggers,steps,disqualification_rules, orsettings.triggersandstepsare full replacements — use the step endpoints for incremental edits. Settingdisqualification_ruleson an active flow immediately drops current members that match.POST /v1/email-flows/{flowId}/archivearchives a non-active flow.DELETE /v1/email-flows/{flowId}deletes it and returns{"flow_id": "...", "deleted": true}. Active flows must be paused first.
Common Errors
Back to API Overview
Review the full public API endpoint map.