Flows API
Flows are event-triggered email journeys. When a matching event is ingested, Synapse creates a "trip" for the contact and executes the flow's steps in sequence.
For a user guide on creating and managing flows in the dashboard, see Flows.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/flows | List flows with filtering and stats |
POST | /v1/flows | Create a flow |
GET | /v1/flows/{id} | Get a flow |
PUT | /v1/flows/{id} | Update a flow (creates a version snapshot) |
POST | /v1/flows/{id}/activate | Activate a draft, paused, or stopped flow |
POST | /v1/flows/{id}/pause | Pause an active flow |
POST | /v1/flows/{id}/stop | Stop an active flow (terminates all trips) |
POST | /v1/flows/{id}/duplicate | Clone a flow |
PUT | /v1/flows/{id}/settings | Update flow settings only |
GET | /v1/flows/{id}/trips | List recent trips |
GET | /v1/flows/{id}/versions | List version history |
GET | /v1/flows/{id}/versions/{version} | Get a specific version |
POST | /v1/flows/{id}/versions/{version}/restore | Restore a version |
GET | /v1/flows/{id}/goals | List conversion goals |
POST | /v1/flows/{id}/goals | Create a conversion goal |
PUT | /v1/flows/{id}/goals/{goal_id} | Update a conversion goal |
DELETE | /v1/flows/{id}/goals/{goal_id} | Delete a conversion goal |
PUT | /v1/flows/{id}/goals/reorder | Reorder conversion goals |
GET | /v1/flows/{id}/goal-conversions | Per-goal conversion metrics for a flow |
POST | /v1/flows/bulk-action | Bulk activate, pause, or stop flows |
GET | /v1/flows/export | Export flows as CSV |
GET | /v1/flows/tags | List all tags used across flows |
List Flows
Auth: JWT (dashboard). Requires flows:read permission.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
search | string | Search flows by name (substring match) | |
status_filter | string | Filter by status: draft, active, paused, stopped | |
content_type | string | Filter: transactional or promotional | |
tag | string | Filter by tag (exact match) | |
created_after | datetime | Filter by creation date (ISO 8601) | |
created_before | datetime | Filter by creation date (ISO 8601) | |
updated_after | datetime | Filter by last update date (ISO 8601) | |
updated_before | datetime | Filter by last update date (ISO 8601) | |
limit | integer | 50 | Items per page (max 200) |
offset | integer | 0 | Number of items to skip |
Response
Create a Flow
Auth: JWT (dashboard). Requires flows:write permission.
Response (201 Created)
Returns the full flow object:
New flows are created in draft status. They do not trigger on events until you activate them with POST /v1/flows/{id}/activate.
Flow Configuration Schema
Step Types
| Type | Description | Parameters |
|---|---|---|
send_email | Render and send an email template | template_slug, optional email_send_settings |
wait | Pause execution for a duration | duration (string: "1h", "24h", "7d") |
condition | Branch based on a true/false check | check, on_true, on_false |
conditional_split | Branch into multiple paths | branches (array of condition objects) |
ab_split | Random split by percentage | variants (array of {name, percentage}) |
update_contact | Update contact properties | set (object of key-value pairs) |
Email Send Settings (per send_email step)
Each send_email step can carry delivery-level settings via the email_send_settings object:
| Field | Type | Default | Description |
|---|---|---|---|
ignore_frequency_capping | boolean | true | Bypass frequency cap rules |
count_for_frequency_capping | boolean | false | Count this send toward the cap |
ignore_dnd | boolean | true | Bypass do-not-disturb windows |
send_to_unsubscribed | boolean | false | Send even if the contact is unsubscribed |
send_window_enabled | boolean | false | Restrict sends to a time window |
send_window_start | string | Window start in "HH:MM" (24h format) | |
send_window_end | string | Window end in "HH:MM" (24h format) | |
send_window_timezone | string | "contact" | "contact" (use the contact's timezone) or an IANA timezone |
Flow Settings
| Field | Type | Default | Description |
|---|---|---|---|
reentry_policy | string | "never" | "never", "after_completion", "always", or "cooldown" |
reentry_cooldown_days | integer | null | Cooldown in days (required when policy is "cooldown") |
throttle_enabled | boolean | false | Enable send throttling |
throttle_limit | integer | null | Max sends per period |
throttle_period | string | null | "hour" or "day" |
Trigger Conditions
Optional JSON object that adds filtering on top of the trigger_event. Only events whose attributes match all conditions will trigger the flow.
Content Type
| Value | Description | RabbitMQ Priority |
|---|---|---|
transactional | Business-critical notifications (claims, orders) | High |
promotional | Marketing campaigns, newsletters | Low |
Activate a Flow
Activates a flow from draft, paused, or stopped status. Before activating, Synapse validates all send_email step templates -- if any template has syntax errors, the request fails with 422 and details about which templates need fixing.
Auth: JWT (dashboard). Requires flows:write permission.
Returns the full flow object with "status": "active".
Activating a flow makes it immediately eligible to trigger on incoming events. Verify your template content and trigger conditions before activating in production.
Pause a Flow
Pauses an active flow. New trips stop being created, but in-flight trips continue to completion.
Auth: JWT (dashboard). Requires flows:write permission.
Returns the full flow object with "status": "paused".
Stop a Flow
Stops an active flow and terminates all in-progress trips immediately. A version snapshot is created before stopping.
Auth: JWT (dashboard). Requires flows:write permission.
Returns the full flow object with "status": "stopped".
Duplicate a Flow
Creates a copy of a flow in draft status. Copies all steps, trigger configuration, settings, tags, and conversion goals.
Auth: JWT (dashboard). Requires flows:write permission.
Response (201 Created)
Returns the full flow object. The name is the original name with " (Copy)" appended.
Update Flow Settings
Update only the settings JSONB on a flow without creating a version snapshot. Use this for toggling re-entry or throttle settings without affecting version history.
Auth: JWT (dashboard). Requires flows:write permission.
Returns the full flow object with the updated settings.
Version History
Every update to a flow creates a version snapshot of the previous state.
List Versions
Auth: JWT (dashboard). Requires flows:read permission.
Get a Specific Version
Restore a Version
Restores a previous version's configuration to the flow. The flow must be in draft, paused, or stopped status — restoring onto an active flow returns 400.
Auth: JWT (dashboard). Requires flows:write permission.
The current state is saved as a version snapshot before the restore. The flow's version number is incremented. Returns the full flow object.
Request Body
| Field | Type | Default | Description |
|---|---|---|---|
change_summary | string | null | Optional note recorded on the snapshot. Defaults to "Restored from version {n}". |
force | boolean | false | Acknowledge and bypass the in-flight-trip guard (see below). Has no effect when the flow has no in-flight trips. |
In-flight trips block restore by default (409)
Restoring a version rewrites the flow's steps array in place. It does not reconcile in-flight trips: a wait-parked contact resumes at a numeric index into the steps array, so if the restored version has fewer or reordered steps, that contact can be silently skipped (an out-of-range index quietly stops the trip) or silently mis-branched (a still-valid index continues down the wrong path). Restore never mutates, terminates, or reconciles a trip — this guard is warn-and-confirm only.
To surface that risk before it happens, restore returns 409 flow_has_inflight_trips when the flow has any in-flight trips (trip status active, awaiting_send, or wait-parked paused) and force is not true:
inflight_count is the number of in-flight trips for this flow (scoped to your workspace and environment). To resolve the 409, choose one of:
- Terminate the parked contacts first —
POST /v1/flows/{flow_id}/stopterminates all in-flight trips, then restore proceeds cleanly. Recommended when you don't want those contacts continuing on the restored steps. - Restore anyway — resend the request with
{"force": true}, an explicit acknowledgement that parked contacts may be skipped or mis-branched by the step change.
A flow with zero in-flight trips (or only terminal trips such as completed) restores as before — force is not needed and has no effect.
The draft / paused / stopped status requirement is checked first, before the in-flight-trip guard. {"force": true} acknowledges the step-drift risk — it does not let you restore onto an active flow. Pause or stop the flow first.
Responses
| Status | Meaning |
|---|---|
200 | Restored. Returns the full flow object with an incremented version. |
400 | Flow is not in draft / paused / stopped status. Checked before the 409 guard. |
404 | Version not found for this flow. |
409 | Flow has in-flight trips and force was not true (flow_has_inflight_trips). |
422 | The restored version's settings fail re-validation. |
Conversion Goals
Conversion goals track how effectively a flow drives a desired outcome. You define a target event on the flow; when an enrolled contact performs that event within the goal's attribution window, Synapse records a conversion. For the concepts behind these metrics — the ≤100% rate versus the uncapped volume, and why goal conversion is not the same as flow completion — see Conversion Goals.
List Goals
Auth: JWT (dashboard). Requires flows:read permission.
Create Goal
Auth: JWT (dashboard). Requires flows:write permission.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | Required | Goal name (1--255 chars) |
event_name | string | Required | Event that counts as a conversion (1--255 chars). Must be unique across the flow's goals — see below. |
event_filters | object | {} | Optional attribute filters on the event. {} counts every occurrence; {"plan": "pro"} counts only events whose attributes match every key/value. |
attribution_window_value | integer | 7 | Attribution window size, 1--365. The window starts when the contact enters the flow. |
attribution_window_unit | string | "days" | "hours", "days", or "weeks". |
exit_on_conversion | boolean | false | When true, a fresh conversion ends the contact's trip and cancels its still-pending sends across email, SMS, WhatsApp, and push. The conversion is always recorded first. |
Returns the created goal (201 Created), including its server-assigned id, position, and timestamps.
A flow can have at most one goal per event_name. Creating (or renaming a goal to) an event_name already used by another goal on the same flow returns 409:
Update Goal
All fields are optional -- only the fields you send are updated. Changing event_name to one already used by another goal on the same flow returns 409 goal_event_name_conflict (see Create Goal).
Delete Goal
Returns 204 No Content. Remaining goals are automatically reordered to fill the gap.
Reorder Goals
The goal_ids array must include exactly all goals for the flow, with no duplicates. The order of IDs in the array becomes the new display order (position 0, 1, 2, ...). A payload that omits a goal, includes an unknown goal, or repeats an id returns 400.
Goal Conversions
Returns per-goal conversion metrics for a single flow over a date range.
Auth: JWT (dashboard). Requires flows:read permission — the same gate as reading the goals themselves. Note this is not analytics:read: roles that can see analytics but not flows (operations, viewer) cannot read goal conversions.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
date_range | string | "30d" | Relative range: 1d, 7d, 30d, 90d, today, or yesterday. Any unrecognized value falls back to 30d. Ignored when both from_date and to_date are provided. |
from_date | string | Explicit start (ISO-8601). Overrides date_range when combined with to_date. | |
to_date | string | Explicit end (ISO-8601). |
Response
Each goal reports two distinct counts:
| Field | Meaning |
|---|---|
entered | Trips that entered this flow during the range. The denominator for conversion_rate, and the same value the flow funnel reports as entered. |
conversions | Distinct converting trips from that entered-in-range group (a trip converting on several qualifying events counts once). The numerator of conversion_rate. Always ≤ entered. |
total_conversions | Distinct conversion events that occurred in the range — a volume metric. May exceed conversions and entered (repeat conversions; conversions of trips that entered earlier). Not the rate numerator. |
conversion_rate | conversions ÷ entered, as a percentage 0--100 (already scaled — not a 0--1 fraction), rounded to two decimals. 0 when no trips entered. ≤100% by construction. |
The response is per flow only — there is deliberately no cross-flow total. One contact's conversion can credit several flows at once, so a cross-sum could exceed the number of distinct converting contacts. See Conversion Goals for the full model.
from_date / to_date must be valid ISO-8601. A malformed value returns 422:
When passing an explicit offset like +00:00, URL-encode the + as %2B so it is not read as a space.
Bulk Action
Apply a status change to multiple flows at once.
Auth: JWT (dashboard). Requires flows:write permission.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
flow_ids | UUID[] | Yes | Flow IDs to act on. Must contain between 1 and 100 items. |
action | string | Yes | "activate", "pause", or "stop" |
Valid Transitions
| Action | From Status | To Status |
|---|---|---|
activate | draft, paused, stopped | active |
pause | active | paused |
stop | active | stopped |
Response
Flows that cannot transition (wrong status) are reported in errors with details.
Errors
flow_ids must contain between 1 and 100 items. A request outside that range fails with 422 before any flow is touched — the whole batch is rejected, not partially applied.
More than 100 flow IDs returns 422 with code too_long:
An empty flow_ids array returns 422 with code too_short:
To act on more than 100 flows, split the request into batches of 100 or fewer.
Export Flows
Export your flows list as a CSV file. The export mirrors the flows list you see on screen.
Auth: JWT (dashboard). Requires flows:read permission.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
search | string | Filter by name (substring match) |
status | string | Filter by status: draft, active, paused, stopped, or archived |
content_type | string | Filter by content type: transactional or promotional |
Archived flows are excluded by default. With no status filter, the export returns your non-archived flows — matching the default flows list (GET /v1/flows), which also hides archived flows. To export archived flows, pass ?status=archived (this returns only archived flows, the same as filtering the list to the Archived tab).
Returns a CSV file with columns: Name, Trigger Event, Content Type, Status, Version, Steps (count), Tags, Created, Updated. Rows are ordered by last-updated, newest first.
Tags
List all unique tags used across flows in the workspace.
Auth: JWT (dashboard). Requires flows:read permission.
Plan Limits
| Plan | Max Flows |
|---|---|
| Free | 5 |
| Starter | 25 |
| Growth | 100 |
| Enterprise | Unlimited |
Exceeding the flow limit returns 403: