API reference
Workflows
Build multi-step contact flows as drafts, validate and publish them, and run them for one contact.
A workflow describes, step by step, what happens to one contact: call with an agent, read the extracted data, wait, retry, call an HTTP endpoint, end with an outcome. You edit a draft, validate it and publish it. Publishing freezes the draft as a numbered version that never changes. You then run a version for a single contact from this page, or for a list through a workflow campaign. See Campaigns and workflows.
All routes need Authorization: Bearer <key>. Reading needs the viewer role; changing needs member; the webhook test needs admin.
| Operation | Method and path |
|---|---|
| Create a workflow | POST /workflows |
| List workflows | GET /workflows |
| Get a workflow | GET /workflows/{workflow_id} |
| Update a workflow | PATCH /workflows/{workflow_id} |
| Delete a workflow | DELETE /workflows/{workflow_id} |
| Get the draft | GET /workflows/{workflow_id}/draft |
| Save the draft | PUT /workflows/{workflow_id}/draft |
| Validate the draft | POST /workflows/{workflow_id}/validate |
| Publish the draft | POST /workflows/{workflow_id}/publish |
| Get a published version | GET /workflows/{workflow_id}/versions/{version} |
| Run a workflow for one contact | POST /workflows/{workflow_id}/run |
| Send a test webhook | POST /workflows/{workflow_id}/webhook:test |
| List node types | GET /workflow-node-types |
Error format
The workflow, workflow campaign and workflow execution routes use their own error body, different from the rest of the API:
{"detail": {"code": "revision_conflict", "message": "expected_revision 0 is stale; the draft is at revision 1", "current_revision": 1}}code is a stable identifier, message is for people, and some errors add fields such as current_revision, issues or fields. Request validation problems answer 422 with code validation_failed. An unknown id answers 404 with code not_found. Authentication, role and rate-limit errors (401, 403, 429) still use the standard body {"error": <status>, "message": "..."}.
The definition
A definition is a JSON object of at most 512 KB.
| Field | Type | Description |
|---|---|---|
entry_node_id | string | The start node. |
on_no_match | string | Node to go to when a node's cases do not match and the node has no fallback of its own. |
nodes | array | The steps. |
nodes[].id | string | Lower-case letters, digits and underscores, up to 64 characters. |
nodes[].type | string | start, agent, extraction, api, time, retry, aisensy_whatsapp or end. |
nodes[].name | string | Optional label. |
nodes[].config | object | Settings of the node type. List node types returns every parameter with its default and bounds. |
nodes[].cases | array | Ordered branches: {"when": <condition>, "then": {"to": "<node id>"}}. The first match wins. time nodes use a single to instead. |
A condition is one of {"always": true}, {"cmp": <operator>, "left": <operand>, "right": <operand>}, {"all": [...]}, {"any": [...]}, {"not": <condition>}, {"exists": {"var": "..."}} or {"not_exists": {"var": "..."}}. Comparison operators are ==, !=, >, >=, <, <=, in, not_in and contains. An operand is {"var": "call.status"}, {"const": <value>} or arithmetic {"op": "+", "args": [...]}. Variables live in the namespaces entry, call, extraction, response, state, node and whatsapp.
The start node declares the contact fields the workflow accepts under config.fields, each with a name, a type (string, number, boolean, timestamp, phone, email, url, json) and required. Four system fields always exist: reference_id, mobile_number, name and email.
Limitation: The
aisensy_whatsappnode sends through a third-party WhatsApp provider and only works when the operator has configured that provider. See Messaging.
Operations
Create a workflow
POST /workflows
Creates a workflow. Its draft starts out empty, at revision 0.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
name | body | string | yes | 1 to 120 characters. |
Request
curl -X POST https://api.sevennadi.si/workflows \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"name": "Loan follow-up"}'Response 201 Created
{
"id": "2ab95da4-50dd-4492-a355-793ec7d810c5",
"name": "Loan follow-up",
"status": "active",
"created_at": "2026-10-13T07:22:20Z",
"updated_at": "2026-10-13T07:22:20Z"
}latest_published_version is added once the workflow has been published.
Errors
| Status | Code | Message |
|---|---|---|
| 422 | validation_failed | name must be 1 to 120 characters |
| 422 | validation_failed | Malformed JSON body or The body must be a JSON object |
List workflows
GET /workflows
Returns the account's workflows, newest first.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
name | query | string | no | Case-insensitive substring of the name. |
limit | query | integer | no | 1 to 100. Default 20. |
offset | query | integer | no | Rows to skip. Default 0. |
Request
curl "https://api.sevennadi.si/workflows?name=loan&limit=20&offset=0" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"items": [
{
"id": "2ab95da4-50dd-4492-a355-793ec7d810c5",
"name": "Loan follow-up",
"status": "active",
"latest_published_version": 1,
"created_at": "2026-10-13T07:22:20Z",
"updated_at": "2026-10-13T07:30:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}The workflow routes page with limit and offset, not page_number and page_size. See Pagination.
Errors
| Status | Code | Message |
|---|---|---|
| 422 | validation_failed | limit: Input should be less than or equal to 100 |
Get a workflow
GET /workflows/{workflow_id}
Returns a workflow with its webhook settings, the state of its draft and a summary of each published version.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
Request
curl https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5 \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"workflow": {
"id": "2ab95da4-50dd-4492-a355-793ec7d810c5",
"name": "Loan follow-up",
"status": "active",
"latest_published_version": 1,
"created_at": "2026-10-13T07:22:20Z",
"updated_at": "2026-10-13T07:30:00Z"
},
"settings": {"webhook": {"url": "https://hooks.example.test/wf", "headers": {"X-Token": "**********"}}},
"draft": {"version": 2, "state": "draft", "revision": 0},
"published": [
{
"version": 1,
"state": "published",
"revision": 0,
"published_at": "2026-10-13T07:30:00Z",
"campaigns": 1,
"success_count": 12,
"failure_count": 3,
"neutral_count": 0,
"terminal_count": 15,
"contacts": 18
}
]
}settings is {} when no webhook is set. Header values are always masked.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
Update a workflow
PATCH /workflows/{workflow_id}
Renames a workflow or changes its webhook settings. settings is applied as a JSON merge patch: keys you send replace stored ones, null removes a key, and keys you leave out stay.
When an execution reaches a final status, the platform posts an execution.terminal payload to settings.webhook.url with the configured headers.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
name | body | string | no | 1 to 120 characters. |
settings.webhook.url | body | string | no | An https URL on port 443 or 8443 that does not point at a private or internal address. |
settings.webhook.headers | body | object | no | Header names to string values. Send null for one header to remove it. |
Request
curl -X PATCH https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5 \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"settings": {"webhook": {"url": "https://hooks.example.test/wf", "headers": {"X-Token": "<your secret>"}}}}'Response 200 OK
{
"id": "2ab95da4-50dd-4492-a355-793ec7d810c5",
"name": "Loan follow-up",
"status": "active",
"created_at": "2026-10-13T07:22:20Z",
"updated_at": "2026-10-13T07:25:00Z",
"settings": {"webhook": {"url": "https://hooks.example.test/wf", "headers": {"X-Token": "**********"}}}
}Send {"settings": {"webhook": null}} to remove the webhook. If the URL moves to another host, stored headers are dropped unless you send them again.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
| 422 | validation_failed | Send name and/or settings, Unknown field <name>, Unknown settings key <name>, Unknown settings.webhook key <name> |
| 422 | validation_failed | settings.webhook.url is required |
| 422 | invalid_webhook_url | settings.webhook.url must use https, settings.webhook.url must use port 443 or 8443, settings.webhook.url must not point at a private or internal address |
| 422 | invalid_webhook_headers | Header <name> is reserved, Invalid header name <name>, Header <name> must be a string, Header <name>: send the real value, or omit it to keep it |
Delete a workflow
DELETE /workflows/{workflow_id}
Deletes a workflow. It disappears from lists and can no longer be run.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
Request
curl -X DELETE https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5 \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 204 No Content
No body.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
Get the draft
GET /workflows/{workflow_id}/draft
Returns the draft definition and its revision number. You need the revision to save.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
Request
curl https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5/draft \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{"definition": {"nodes": [], "on_no_match": "", "entry_node_id": ""}, "revision": 0}Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
Save the draft
PUT /workflows/{workflow_id}/draft
Replaces the draft definition. The save succeeds only if expected_revision equals the draft's current revision; the revision then goes up by one. This stops two editors from overwriting each other. The definition is stored as sent; it is not validated here.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
expected_revision | body | integer | yes | The revision you last read. |
definition | body | object | yes | See The definition. |
Request
curl -X PUT https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5/draft \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{
"expected_revision": 0,
"definition": {
"entry_node_id": "n_start",
"on_no_match": "n_end_unhandled",
"nodes": [
{"id": "n_start", "type": "start", "config": {"trigger": {"kind": "manual"}},
"cases": [{"when": {"always": true}, "then": {"to": "n_call"}}]},
{"id": "n_call", "type": "agent", "name": "Qualify",
"config": {"agent_id": "f412f2b1-3493-4458-bcf2-8676cc798edd", "timeout_s": 3600,
"variables": {"name": {"value": {"var": "entry.name"}}}},
"cases": [
{"when": {"cmp": "==", "left": {"var": "call.status"}, "right": {"const": "completed"}},
"then": {"to": "n_end_reached"}},
{"when": {"cmp": "in", "left": {"var": "call.status"}, "right": {"const": ["busy", "no_answer"]}},
"then": {"to": "n_retry"}}
]},
{"id": "n_retry", "type": "retry",
"config": {"target_node_id": "n_call", "attempts": [{"delay": {"minutes": 30}}, {"delay": {"hours": 2}}]},
"cases": [{"when": {"always": true}, "then": {"to": "n_end_unreachable"}}]},
{"id": "n_end_reached", "type": "end", "config": {"label": "reached", "outcome": "success"}},
{"id": "n_end_unreachable", "type": "end", "config": {"label": "unreachable", "outcome": "failure"}},
{"id": "n_end_unhandled", "type": "end", "config": {"label": "unhandled", "outcome": "failure"}}
]
}
}'Response 200 OK
{"revision": 1}Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
| 409 | revision_conflict | expected_revision 0 is stale; the draft is at revision 1 (with current_revision) |
| 413 | definition_too_large | The definition exceeds 512 KB |
| 422 | validation_failed | expected_revision must be an integer >= 0 |
| 422 | validation_failed | definition must be a JSON object |
Validate the draft
POST /workflows/{workflow_id}/validate
Checks the saved draft and returns every problem found. Nothing is changed. The request needs no body.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
Request
curl -X POST https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5/validate \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{"issues": []}Each issue is an object with severity (error or warning), code, message and, where they apply, node_id and path. Only errors block publishing.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
Publish the draft
POST /workflows/{workflow_id}/publish
Validates the draft and freezes it as the next version. The draft's revision counter returns to 0.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
expected_revision | body | integer | no | Publish only if the draft is still at this revision. |
Request
curl -X POST https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5/publish \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"expected_revision": 1}'Response 200 OK
{"version": 1, "published_at": "2026-10-13T07:30:00Z", "warnings": []}Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
| 409 | revision_conflict | The draft changed since you read it (with current_revision) |
| 409 | already_published | The draft is identical to version <n> (with version) |
| 422 | invalid_definition | The draft has <n> error(s) (with the full issues list) |
Get a published version
GET /workflows/{workflow_id}/versions/{version}
Returns the definition of a published version. It never changes once published.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
version | path | integer | yes | The version number. |
Request
curl https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5/versions/1 \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"version": 1,
"published_at": "2026-10-13T07:30:00Z",
"definition": {"entry_node_id": "n_start", "on_no_match": "n_end_unhandled", "nodes": []},
"warnings": []
}definition.nodes holds the full node list; it is shortened here.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found or Workflow version not found |
Run a workflow for one contact
POST /workflows/{workflow_id}/run
Starts one execution of the latest published version for a single contact. The body is the contact: the system fields and the fields the start node declares. Declared fields may be sent at the top level or inside custom_fields.
Runs of one version are grouped under an automatic campaign of kind api, whose id is returned. A reference_id can run only once per version; if you leave it out, one is derived from the contact's values.
Auth: API key or session token. Role: member and up. When the operator sets REQUIRE_VERIFIED_EMAIL, the login must have a verified email address.
This route accepts an Idempotency-Key header; see Idempotency.
Warning: A run places real calls when an
agentnode is reached and the platform is connected to live telephony.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
reference_id | body | string | no | Your id for this contact. |
mobile_number | body | string | no | E.164. The default recipient of agent nodes. |
name | body | string | no | Contact name. |
email | body | string | no | Contact email. |
| declared fields | body | as declared | Fields from the start node's config.fields. |
Request
curl -X POST https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5/run \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: loan-1042-followup" \
-d '{"reference_id": "loan-1042", "mobile_number": "+919876500012", "name": "Asha"}'Response 201 Created
{
"execution_id": "exec:7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9:loan-1042",
"campaign_id": "7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9",
"workflow_version": 1,
"status": "pending"
}Follow the run with Get a workflow execution.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
| 409 | duplicate_run | reference_id <id> already ran on this version (with execution_id) |
| 409 | invalid_state | Runs of this version were aborted |
| 422 | no_published_version | The workflow has no published version |
| 422 | unknown_field | Unknown field <name> (with fields) |
| 422 | empty_row | The row is empty |
| 422 | missing_required_value | <field>: a value is required |
| 422 | invalid_phone | <field>: expected an E.164 phone number, e.g. +919876543210 |
| 422 | invalid_field_type | <field>: expected <type> |
Send a test webhook
POST /workflows/{workflow_id}/webhook:test
Posts a sample execution.terminal payload to the workflow's webhook. The sample follows the first branch of each node of the latest published version from the start node to an end node, and is marked "test": true.
Auth: API key or session token. Role: admin and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | path | string (UUID) | yes | The workflow. |
Request
curl -X POST "https://api.sevennadi.si/workflows/2ab95da4-50dd-4492-a355-793ec7d810c5/webhook:test" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"webhook_url": "https://hooks.example.test/wf",
"workflow_version": 1,
"sample_path": ["n_start", "n_call", "n_end_reached"],
"sample_outcome": "success",
"result": {"delivered": true, "http_status": 200, "response_body": "ok"}
}The call answers 200 even when delivery fails. Then result.delivered is false, with error_type (http_error or unreachable) and error.
Errors
| Status | Code | Message |
|---|---|---|
| 403 | Standard body: Your role (member) can't change webhooks. It needs admin access; ask an owner or admin of the workspace. | |
| 404 | not_found | Workflow not found |
| 409 | no_webhook | No webhook is configured on this workflow |
| 422 | no_published_version | The workflow has no published version |
List node types
GET /workflow-node-types
Returns the catalog of node types: each type's parameters with their types, defaults and bounds, what it branches on, the variable namespaces, and the available effects. Use it to build or check definitions.
Auth: API key or session token. Role: viewer and up.
No parameters.
Request
curl https://api.sevennadi.si/workflow-node-types \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"node_types": [
{
"type": "start",
"label": "Start",
"summary": "Entry point; declares the contact fields and input variables the workflow accepts.",
"available": true,
"transition": "cases",
"branches_on": "entry.*",
"params": [
{"name": "trigger.kind", "type": "enum", "required": true, "description": "How contacts enter the workflow.", "options": ["manual"]},
{"name": "fields", "type": "array", "required": false, "default": [], "description": "Declared contact fields (up to 50).", "max": 50}
]
}
],
"namespaces": ["call", "entry", "extraction", "node", "response", "state", "whatsapp"],
"effects": [
{
"type": "set_value",
"summary": "Writes an operand's value to state.<name> (one level deep) before moving on.",
"example": {"set_value": {"state.attempted": {"const": true}}}
}
]
}node_types is shortened here to its first entry.