Skip to content

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.

Note

For a user guide on creating and managing flows in the dashboard, see Flows.


Endpoints

MethodPathDescription
GET/v1/flowsList flows with filtering and stats
POST/v1/flowsCreate a flow
GET/v1/flows/{id}Get a flow
PUT/v1/flows/{id}Update a flow (creates a version snapshot)
POST/v1/flows/{id}/activateActivate a draft, paused, or stopped flow
POST/v1/flows/{id}/pausePause an active flow
POST/v1/flows/{id}/stopStop an active flow (terminates all trips)
POST/v1/flows/{id}/duplicateClone a flow
PUT/v1/flows/{id}/settingsUpdate flow settings only
GET/v1/flows/{id}/tripsList recent trips
GET/v1/flows/{id}/versionsList version history
GET/v1/flows/{id}/versions/{version}Get a specific version
POST/v1/flows/{id}/versions/{version}/restoreRestore a version
GET/v1/flows/{id}/goalsList conversion goals
POST/v1/flows/{id}/goalsCreate 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/reorderReorder conversion goals
GET/v1/flows/{id}/goal-conversionsPer-goal conversion metrics for a flow
POST/v1/flows/bulk-actionBulk activate, pause, or stop flows
GET/v1/flows/exportExport flows as CSV
GET/v1/flows/tagsList all tags used across flows

List Flows

bash
curl "https://synapse-api.pyrx.tech/v1/flows?status_filter=active&limit=20&offset=0" \
-H "Authorization: Bearer <jwt>"

Auth: JWT (dashboard). Requires flows:read permission.

Query Parameters

ParameterTypeDefaultDescription
searchstringSearch flows by name (substring match)
status_filterstringFilter by status: draft, active, paused, stopped
content_typestringFilter: transactional or promotional
tagstringFilter by tag (exact match)
created_afterdatetimeFilter by creation date (ISO 8601)
created_beforedatetimeFilter by creation date (ISO 8601)
updated_afterdatetimeFilter by last update date (ISO 8601)
updated_beforedatetimeFilter by last update date (ISO 8601)
limitinteger50Items per page (max 200)
offsetinteger0Number of items to skip

Response

json
{
"flows": [
{
"id": "8f14e45f-ceea-467f-a83c-01a01ba3c5db",
"name": "Claim Submitted Notification",
"description": "Sends notification when a worker submits a claim",
"trigger_event": "claim_submitted",
"trigger_conditions": {"claim_type": "work_injury"},
"segment_id": null,
"steps": [
{"type": "send_email", "template_slug": "claim-submitted-notification"}
],
"content_type": "transactional",
"status": "active",
"version": 1,
"tags": ["claims", "transactional"],
"settings": {"reentry_policy": "never", "throttle_enabled": false},
"created_by": "member_abc",
"created_at": "2026-04-01T10:00:00Z",
"updated_at": "2026-04-01T10:00:00Z",
"trips_started": 1542,
"trips_completed": 1480
}
],
"counts": {
"all": 42,
"active": 15,
"paused": 8,
"draft": 12,
"stopped": 7
},
"total": 42,
"limit": 20,
"offset": 0
}

Create a Flow

Auth: JWT (dashboard). Requires flows:write permission.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome Series",
"description": "Three-email welcome sequence for new signups",
"trigger_event": "signup_completed",
"trigger_conditions": {},
"content_type": "transactional",
"tags": ["onboarding", "welcome"],
"settings": {
"reentry_policy": "never",
"throttle_enabled": false
},
"steps": [
{
"type": "send_email",
"template_slug": "welcome-email"
},
{
"type": "wait",
"duration": "24h"
},
{
"type": "send_email",
"template_slug": "getting-started-guide"
},
{
"type": "wait",
"duration": "72h"
},
{
"type": "condition",
"check": {
"source": "latest_event",
"event_name": "first_action_completed",
"exists": true
},
"on_true": "skip_to_end",
"on_false": "continue"
},
{
"type": "send_email",
"template_slug": "activation-reminder"
}
]
}'

Response (201 Created)

Returns the full flow object:

json
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Welcome Series",
"description": "Three-email welcome sequence for new signups",
"trigger_event": "signup_completed",
"trigger_conditions": {},
"segment_id": null,
"steps": [...],
"content_type": "transactional",
"status": "draft",
"version": 1,
"tags": ["onboarding", "welcome"],
"settings": {"reentry_policy": "never", "throttle_enabled": false},
"created_by": "member_abc",
"created_at": "2026-04-07T10:30:00Z",
"updated_at": "2026-04-07T10:30:00Z"
}
Note

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

TypeDescriptionParameters
send_emailRender and send an email templatetemplate_slug, optional email_send_settings
waitPause execution for a durationduration (string: "1h", "24h", "7d")
conditionBranch based on a true/false checkcheck, on_true, on_false
conditional_splitBranch into multiple pathsbranches (array of condition objects)
ab_splitRandom split by percentagevariants (array of {name, percentage})
update_contactUpdate contact propertiesset (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:

FieldTypeDefaultDescription
ignore_frequency_cappingbooleantrueBypass frequency cap rules
count_for_frequency_cappingbooleanfalseCount this send toward the cap
ignore_dndbooleantrueBypass do-not-disturb windows
send_to_unsubscribedbooleanfalseSend even if the contact is unsubscribed
send_window_enabledbooleanfalseRestrict sends to a time window
send_window_startstringWindow start in "HH:MM" (24h format)
send_window_endstringWindow end in "HH:MM" (24h format)
send_window_timezonestring"contact""contact" (use the contact's timezone) or an IANA timezone

Flow Settings

FieldTypeDefaultDescription
reentry_policystring"never""never", "after_completion", "always", or "cooldown"
reentry_cooldown_daysintegernullCooldown in days (required when policy is "cooldown")
throttle_enabledbooleanfalseEnable send throttling
throttle_limitintegernullMax sends per period
throttle_periodstringnull"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.

json
{
"trigger_event": "claim_submitted",
"trigger_conditions": {
"claim_type": "work_injury",
"country": "SG"
}
}

Content Type

ValueDescriptionRabbitMQ Priority
transactionalBusiness-critical notifications (claims, orders)High
promotionalMarketing campaigns, newslettersLow

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.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows/{flow_id}/activate \
-H "Authorization: Bearer <jwt>"

Returns the full flow object with "status": "active".

Warning

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.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows/{flow_id}/pause \
-H "Authorization: Bearer <jwt>"

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.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows/{flow_id}/stop \
-H "Authorization: Bearer <jwt>"

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.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows/{flow_id}/duplicate \
-H "Authorization: Bearer <jwt>"

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.

bash
curl -X PUT https://synapse-api.pyrx.tech/v1/flows/{flow_id}/settings \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"reentry_policy": "cooldown",
"reentry_cooldown_days": 7,
"throttle_enabled": true,
"throttle_limit": 100,
"throttle_period": "hour"
}
}'

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.

bash
curl https://synapse-api.pyrx.tech/v1/flows/{flow_id}/versions \
-H "Authorization: Bearer <jwt>"
json
[
{
"id": "ver_1234-abcd",
"flow_id": "flow_5678-efgh",
"version": 3,
"name": "Welcome Series",
"description": "Updated subject line",
"trigger_event": "signup_completed",
"trigger_conditions": {},
"steps": [...],
"content_type": "transactional",
"status_at_snapshot": "active",
"changed_by": "member_abc",
"change_summary": "Updated welcome email template",
"tags": ["onboarding"],
"settings": {"reentry_policy": "never"},
"created_at": "2026-04-05T14:00:00Z"
}
]

Get a Specific Version

bash
curl https://synapse-api.pyrx.tech/v1/flows/{flow_id}/versions/{version_number} \
-H "Authorization: Bearer <jwt>"

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.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows/{flow_id}/versions/{version_number}/restore \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"change_summary": "Reverting to version 3 — new template had rendering issues"}'

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

FieldTypeDefaultDescription
change_summarystringnullOptional note recorded on the snapshot. Defaults to "Restored from version {n}".
forcebooleanfalseAcknowledge 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:

json
{
"detail": {
"code": "flow_has_inflight_trips",
"message": "This flow has 3 in-flight trips (active, awaiting send, or wait-parked). Restoring a version rewrites the flow's steps without reconciling those trips — parked contacts resume by step index into the restored steps and may be silently skipped or mis-branched. Stop the flow first to terminate in-flight trips, or pass `{\"force\": true}` to restore anyway.",
"inflight_count": 3
}
}

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 firstPOST /v1/flows/{flow_id}/stop terminates 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.

Warning

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.

bash
# Restore despite in-flight trips (after understanding the step-drift risk)
curl -X POST https://synapse-api.pyrx.tech/v1/flows/{flow_id}/versions/{version_number}/restore \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"force": true, "change_summary": "Reverting to v3 — accept in-flight step drift"}'

Responses

StatusMeaning
200Restored. Returns the full flow object with an incremented version.
400Flow is not in draft / paused / stopped status. Checked before the 409 guard.
404Version not found for this flow.
409Flow has in-flight trips and force was not true (flow_has_inflight_trips).
422The 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.

bash
curl https://synapse-api.pyrx.tech/v1/flows/{flow_id}/goals \
-H "Authorization: Bearer <jwt>"
json
[
{
"id": "goal_1234-abcd",
"flow_id": "flow_5678-efgh",
"position": 0,
"name": "Completed Purchase",
"event_name": "purchase_completed",
"event_filters": {"amount_gte": 50},
"attribution_window_value": 7,
"attribution_window_unit": "days",
"exit_on_conversion": false,
"created_at": "2026-04-01T10:00:00Z",
"updated_at": "2026-04-01T10:00:00Z"
}
]

Create Goal

Auth: JWT (dashboard). Requires flows:write permission.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows/{flow_id}/goals \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "Completed Purchase",
"event_name": "purchase_completed",
"event_filters": {},
"attribution_window_value": 7,
"attribution_window_unit": "days",
"exit_on_conversion": false
}'
FieldTypeDefaultDescription
namestringRequiredGoal name (1--255 chars)
event_namestringRequiredEvent that counts as a conversion (1--255 chars). Must be unique across the flow's goals — see below.
event_filtersobject{}Optional attribute filters on the event. {} counts every occurrence; {"plan": "pro"} counts only events whose attributes match every key/value.
attribution_window_valueinteger7Attribution window size, 1--365. The window starts when the contact enters the flow.
attribution_window_unitstring"days""hours", "days", or "weeks".
exit_on_conversionbooleanfalseWhen 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.

Note

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:

json
{
"detail": {
"code": "goal_event_name_conflict",
"message": "A goal for event 'purchase_completed' already exists on this flow"
}
}

Update Goal

bash
curl -X PUT https://synapse-api.pyrx.tech/v1/flows/{flow_id}/goals/{goal_id} \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"name": "High-Value Purchase", "event_filters": {"amount_gte": 100}}'

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

bash
curl -X DELETE https://synapse-api.pyrx.tech/v1/flows/{flow_id}/goals/{goal_id} \
-H "Authorization: Bearer <jwt>"

Returns 204 No Content. Remaining goals are automatically reordered to fill the gap.

Reorder Goals

bash
curl -X PUT https://synapse-api.pyrx.tech/v1/flows/{flow_id}/goals/reorder \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"goal_ids": ["goal_B", "goal_A", "goal_C"]}'

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.

bash
curl "https://synapse-api.pyrx.tech/v1/flows/{flow_id}/goal-conversions?date_range=30d" \
-H "Authorization: Bearer <jwt>"

Query Parameters

ParameterTypeDefaultDescription
date_rangestring"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_datestringExplicit start (ISO-8601). Overrides date_range when combined with to_date.
to_datestringExplicit end (ISO-8601).

Response

json
{
"flow_id": "8f14e45f-ceea-467f-a83c-01a01ba3c5db",
"goals": [
{
"goal_id": "goal_1234-abcd",
"goal_name": "Completed Purchase",
"event_name": "purchase_completed",
"conversions": 48,
"total_conversions": 63,
"entered": 100,
"conversion_rate": 48.0
}
],
"date_range": { "from": "2026-08-08T00:00:00+00:00", "to": "2026-09-07T00:00:00+00:00" }
}

Each goal reports two distinct counts:

FieldMeaning
enteredTrips that entered this flow during the range. The denominator for conversion_rate, and the same value the flow funnel reports as entered.
conversionsDistinct 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_conversionsDistinct 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_rateconversions ÷ 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.

Warning

from_date / to_date must be valid ISO-8601. A malformed value returns 422:

json
{
"detail": {
"code": "invalid_date_param",
"message": "from_date/to_date must be ISO-8601 (e.g. 2026-09-01T00:00:00+00:00, with the '+' URL-encoded as %2B)."
}
}

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.

bash
curl -X POST https://synapse-api.pyrx.tech/v1/flows/bulk-action \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"flow_ids": ["flow_1234", "flow_5678"],
"action": "activate"
}'

Request Body

FieldTypeRequiredDescription
flow_idsUUID[]YesFlow IDs to act on. Must contain between 1 and 100 items.
actionstringYes"activate", "pause", or "stop"

Valid Transitions

ActionFrom StatusTo Status
activatedraft, paused, stoppedactive
pauseactivepaused
stopactivestopped

Response

json
{
"success": 2,
"failed": 0,
"errors": []
}

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:

json
{
"detail": "flow_ids: List should have at most 100 items after validation, not 101",
"code": "too_long",
"errors": [
{
"field": "flow_ids",
"message": "List should have at most 100 items after validation, not 101"
}
]
}

An empty flow_ids array returns 422 with code too_short:

json
{
"detail": "flow_ids: List should have at least 1 item after validation, not 0",
"code": "too_short",
"errors": [
{
"field": "flow_ids",
"message": "List should have at least 1 item after validation, not 0"
}
]
}
Tip

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.

bash
curl "https://synapse-api.pyrx.tech/v1/flows/export?status=active" \
-H "Authorization: Bearer <jwt>" \
-o flows_export.csv

Query Parameters

ParameterTypeDescription
searchstringFilter by name (substring match)
statusstringFilter by status: draft, active, paused, stopped, or archived
content_typestringFilter by content type: transactional or promotional
Note

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.

bash
curl https://synapse-api.pyrx.tech/v1/flows/tags \
-H "Authorization: Bearer <jwt>"
json
["claims", "marketing", "onboarding", "retention", "transactional"]

Plan Limits

PlanMax Flows
Free5
Starter25
Growth100
EnterpriseUnlimited

Exceeding the flow limit returns 403:

json
{
"detail": "Flow limit reached for your plan. Upgrade to create more flows.",
"code": "plan_limit_reached"
}