Skip to main content

Build a Flow via the API

You describe a flow as a builder doc: a JSON document in SendSeven's own format. SendSeven validates it, compiles it to Meta's Flow JSON (version 7.3) and keeps every saved state as an immutable version. You never have to write Meta Flow JSON yourself.

There are four ways to start a flow:

Start fromRequest
BlankPOST /whatsapp-flows with only a name
Your own builder docPOST /whatsapp-flows with builder_doc
A SendSeven templatePOST /whatsapp-flows with template_key (+ placeholder_mapping)
An AI generationPOST /whatsapp-flows with origin: "ai", builder_doc and ai_generation_id (see AI generation)

You can also import a flow that already exists on your WhatsApp Business Account.

The builder doc​

{
"schema_version": 1,
"meta": {
"name": "Rückruf anfordern",
"categories": ["CONTACT_US"],
"kind": "static",
"language": "de",
"cta": "Rückruf anfordern"
},
"screens": [
{
"id": "CALLBACK",
"title": "Rückruf",
"terminal": true,
"success": true,
"components": [
{ "type": "TextHeading", "text": "Wann dürfen wir Sie anrufen?" },
{
"type": "TextInput",
"name": "first_name",
"label": "Vorname",
"required": true,
"mapping": { "target": "contact_field", "field": "first_name" }
},
{
"type": "RadioButtonsGroup",
"name": "slot",
"label": "Zeitfenster",
"required": true,
"options": [
{ "id": "morning", "title": "Vormittags (9–12 Uhr)" },
{ "id": "afternoon", "title": "Nachmittags (13–17 Uhr)" }
]
},
{
"type": "Footer",
"label": "Absenden",
"on_click_action": { "type": "complete" }
}
]
}
],
"on_complete": { "tag_ids": ["5b7f0c3e-1d2a-4e8b-9f10-2c3d4e5f6a7b"] }
}

Rules that apply everywhere:

  • Keys are snake_case. Unknown keys are rejected, so a typo fails loudly instead of being ignored.
  • The first screen is the entry screen.
  • Every input has a name. Names must be unique across the whole flow, because they become the keys of the answers.
  • At least one screen must be terminal: true, and every terminal screen needs a Footer.
  • SUCCESS is a reserved screen id.
  • Any string property can be a dynamic reference such as ${data.slots} or ${form.first_name}.

Top level​

FieldTypeNotes
schema_version1Always 1.
meta.namestringDisplay name of the flow on Meta.
meta.categoriesstring[]Meta categories: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY, OTHER. Default ["OTHER"].
meta.kind"static" | "dynamic"Default static. See Static vs dynamic.
meta.languagestringLanguage of the texts, e.g. de, en. One flow per language.
meta.descriptionstringOptional.
meta.ctastringDefault button text when the flow is sent. Max 30 characters (20 recommended).
screensScreen[]1 to 100 screens.
data_sourcesBinding[]Data bindings that fill screen data. See Managed endpoint and data sources.
on_complete.tag_idsstring[]Tags added to the contact when the flow is completed (add-only).

Screen​

FieldTypeNotes
idstringUnique. Letters and underscores; upper case recommended.
titlestringShown in the WhatsApp header. Keep it at 30 characters or less.
terminalbooleanThe flow can end on this screen.
successbooleanMarks a terminal screen as a successful outcome.
refresh_on_backbooleanDynamic flows only: re-load the screen's data when the user goes back to it.
sensitivestring[]Input names whose answers are masked (••••) in session listings and in the whatsapp_flow.completed webhook.
dataobjectScreen data keys: { "<key>": { "type", "example", "items"?, "properties"?, "description"? } }. example is required. It is used for previews and as the value when nothing else fills the key.
componentsComponent[]Up to 50 per screen, counted including nested If / Switch branches.

Components​

GroupTypes
TextTextHeading, TextSubheading, TextBody, TextCaption, RichText
InputsTextInput, TextArea, Dropdown, RadioButtonsGroup, CheckboxGroup, ChipsSelector, DatePicker, CalendarPicker, OptIn, PhotoPicker, DocumentPicker
Navigation and mediaNavigationList, EmbeddedLink, Image, ImageCarousel, Footer
LogicIf (condition, then, else), Switch (value, cases)

Every input accepts name, required, enabled, visible, init_value, error_message and an optional mapping. Type-specific fields follow Meta's components in snake_case, for example input_type, min_chars, max_chars, pattern and helper_text on TextInput; min_selected_items and max_selected_items on CheckboxGroup; min_date, max_date and unavailable_dates on DatePicker.

Selection inputs (Dropdown, RadioButtonsGroup, CheckboxGroup, ChipsSelector) take options as either a static list or a reference to a screen data key:

{ "type": "Dropdown", "name": "branch", "label": "Filiale", "options": "${data.branches}" }

A static option is { "id", "title", "description"?, "metadata"?, "enabled"?, "image"?, "alt_text"? }.

Actions​

Actions are used by Footer.on_click_action, EmbeddedLink, OptIn, NavigationList and the on_select_action of selection inputs.

typeFieldsWhat it does
navigatenext, payloadGo to another screen and pass values into its data. Photo and document answers cannot be passed this way.
completepayloadFinish the flow. SendSeven automatically adds every input answer; list only extra keys in payload.
data_exchangepayload, next_screensSend the screen to SendSeven and let the server pick the next screen and its data. Dynamic flows only.
update_datapayloadUpdate the current screen's data without leaving it.
open_urlurlOpen a link (must be https://).

Create a flow​

curl -X POST https://api.sendseven.com/api/v1/whatsapp-flows \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Rückruf anfordern",
"description": "Callback request for the support line",
"builder_doc": { "schema_version": 1, "meta": { "name": "Rückruf anfordern" }, "screens": [ ... ] }
}'
FieldTypeNotes
namestringRequired, 1–200 characters.
descriptionstringOptional, up to 1000 characters.
categoriesstring[]Optional. Defaults to the template's category or meta.categories.
builder_docobjectOptional. Omit for a blank flow.
template_keystringStart from a template. Cannot be combined with builder_doc (422 invalid_request).
languagestringTemplate language (de or en). Defaults to the workspace language.
placeholder_mappingobjectFills the template's placeholders (see Templates).
origin"ai"Only for AI results. Requires builder_doc.
ai_generation_idstringThe generation_id from the AI job result.

Response 201 returns the flow detail:

{
"id": "0d6a3c1e-7f4b-4a2d-9b8e-1f2a3b4c5d6e",
"name": "Rückruf anfordern",
"description": "Callback request for the support line",
"kind": "static",
"origin": "builder",
"categories": ["CONTACT_US"],
"status": "draft",
"definition_status": "active",
"current_version_id": "a1b2c3d4-…",
"published_version_id": null,
"publication_count": 0,
"template_key": null,
"read_only": false,
"current_version": {
"id": "a1b2c3d4-…",
"version_number": 1,
"is_valid": true,
"error_count": 0,
"warning_count": 0,
"builder_doc": { … },
"compiled_flow_json": { … },
"flow_json_version": "7.3",
"validation": { "valid": true, "error_count": 0, "warning_count": 0, "issues": [] }
},
"created_at": "2026-10-03T08:00:00Z",
"updated_at": "2026-10-03T08:00:00Z"
}

status is the display status across all publications (draft, published, deprecated or archived). definition_status is active or archived.

A dynamic builder doc on a plan without dynamic flows returns 403 with code: "feature_not_available" (see Static vs dynamic).

Versions​

Each save creates a new, immutable version:

curl -X POST https://api.sendseven.com/api/v1/whatsapp-flows/{flow_id}/versions \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{ "builder_doc": { … }, "change_note": "Added a second time slot" }'
FieldNotes
builder_docRequired.
change_noteOptional, up to 500 characters.
restored_from_version_idOptional. Marks the save as a restore; the change note defaults to Restored from vN.
  • Returns 201 with the new version, or 200 with the current version if the content did not change.
  • A doc with validation errors is still saved, with is_valid: false and the issues in validation. You cannot push an invalid version to WhatsApp.
  • A doc that cannot even be parsed (wrong shape, unknown keys) is rejected with 422 invalid_builder_doc and an issues list.
EndpointPurpose
GET /whatsapp-flows/{id}/versionsPaginated version list (page, page_size).
GET /whatsapp-flows/{id}/versions/{version_id}One version including builder_doc, compiled_flow_json and validation.

Version fields: id, version_number, is_valid, error_count, warning_count, change_note, content_hash, is_published, ai_generation_id, created_by_user_id, created_by_name, created_at.

Validation​

Validate a doc without saving it:

curl -X POST https://api.sendseven.com/api/v1/whatsapp-flows/{flow_id}/validate \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{ "builder_doc": { … } }'
{
"valid": false,
"error_count": 1,
"warning_count": 1,
"issues": [
{
"code": "component.label_too_long",
"path": "screens[0].components[1].label",
"message": "Label is 25 characters; Meta allows 20.",
"severity": "error"
},
{
"code": "binding.no_outputs",
"path": "data_sources[0].outputs",
"message": "No outputs mapped; result keys are used as screen data keys.",
"severity": "warning"
}
]
}
  • severity: "error" blocks pushing and publishing. warning is advice.
  • Length and count limits are reported as issues, not as parse errors, so you get all problems in one pass. See Limits.
  • Validation also checks your workspace: referenced custom fields, tags, lists and data sources must exist.

Issue codes are grouped by prefix:

PrefixCovers
meta.*, flow.*Flow-level rules, e.g. flow.no_terminal (no terminal screen).
screen.*Screen rules, e.g. screen.terminal_missing_footer.
component.*, input.*Component and input rules, e.g. component.label_too_long, input.name_duplicate.
action.*, routing.*, ref.*Actions, screen routes and ${…} references, e.g. action.url_not_https.
mapping.*Field mappings, e.g. mapping.consent_text_required, mapping.custom_field_not_editable, mapping.placeholder_unresolved (warning).
binding.*, data.*Data bindings and screen data.
static.*Things a static flow cannot do, e.g. static.data_exchange, static.binding_not_entry.
kind.dynamic_not_allowedThe doc needs a dynamic flow but your plan does not include it.
on_complete.*on_complete tags.
schema.<type>Shape errors from parsing.

Treat the codes as stable identifiers and the message as human-readable text that may change.

Compile​

POST /whatsapp-flows/{id}/compile with { "builder_doc": … } or { "version_id": "…" } returns the Meta Flow JSON SendSeven would upload:

{ "flow_json": { "version": "7.3", "screens": [ … ] }, "flow_json_version": "7.3", "validation": { … } }

Without a body it compiles the current version (409 version_missing if there is none).

List, update and archive​

EndpointNotes
GET /whatsapp-flowspage, page_size (max 100, default 25), search, status (draft, published, deprecated, archived, active, all; archived flows are hidden by default), kind (static, dynamic). Returns { "items": [...], "pagination": {...} }.
GET /whatsapp-flows/{id}Flow detail with current_version. 404 flow_definition_not_found.
PATCH /whatsapp-flows/{id}name, description, categories (422 invalid_categories for unknown values), status (active or archived).
DELETE /whatsapp-flows/{id}Archives the flow and returns { "success": true, "id": "…" }. Flows already published on WhatsApp stay live there; deprecate them separately.

Templates​

SendSeven ships 13 ready-made flows in German and English:

appointment_request, lead_qualification, nps_csat, newsletter_preferences, newsletter_signup, callback_request, support_ticket, return_request, event_signup, trial_class, waitlist, quote_request, profile_update.

curl "https://api.sendseven.com/api/v1/whatsapp-flows/templates?lang=de" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"

GET /whatsapp-flows/templates returns { "items": [...] } with a short envelope for each template (no builder doc). GET /whatsapp-flows/templates/{key}?lang=de returns the full template, including builder_doc and its placeholders.

Templates contain placeholders for things only your workspace knows:

PlaceholderFill with
{{custom_field:<key>}}A custom field id
{{tag:<key>}}A tag id
{{list:<key>}}A list id
{{url:<key>}}An https:// URL (for example your privacy policy)

Pass them as placeholder_mapping when you create the flow:

curl -X POST https://api.sendseven.com/api/v1/whatsapp-flows \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Newsletter-Anmeldung",
"template_key": "newsletter_signup",
"language": "de",
"placeholder_mapping": {
"{{list:newsletter}}": "3f9a1c2e-…",
"{{url:privacy}}": "https://www.example.de/datenschutz"
}
}'

Keys can be written with or without braces ("list:newsletter" or "{{list:newsletter}}"). Each template lists its placeholders as {token, kind, key, label, suggested_type, required, paths}.

Unfilled placeholders degrade safely instead of breaking the flow: an unmapped custom field or list becomes a variable-only answer, an unmapped tag is dropped, and a link with an unmapped {{url:…}} is removed. Any token still left in the doc is reported as the warning mapping.placeholder_unresolved.

Clone a flow​

POST /whatsapp-flows/{id}/clone copies a flow (current version) within the workspace or into another workspace of the same billing account.

{ "name": "Rückruf (Filiale Köln)", "target_tenant_id": "tenant_def456", "target_channel_ids": ["8c1d0f5e-…"] }

All fields are optional. With target_channel_ids the copy is pushed to those channels' WhatsApp Business Accounts right away. The response is the new flow plus tenant_id and push_results. Cloning across workspaces needs access to the target workspace (403 clone_forbidden otherwise). Imported flows cannot be cloned (409 not_clonable).

Import a flow from Meta​

Flows you built elsewhere (for example in Meta's WhatsApp Manager) can be imported so you can send them and receive their answers in SendSeven:

curl -X POST https://api.sendseven.com/api/v1/channels/{channel_id}/whatsapp-flows/import \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{ "meta_flow_id": "1234567890123456" }'

Imported flows are read-only (read_only: true, edits return 409 read_only) and keep their original Flow JSON in imported_flow_json. Importing the same flow twice returns 409 already_imported.