API reference
Workflow campaigns
Run a published workflow version for a list of contacts; pause, resume or abort it and follow its progress.
A workflow campaign runs a single published version of a workflow for each contact you upload; later edits to the workflow do not change it. Contacts are uploaded as entries; when the campaign starts, each valid entry becomes a workflow execution. A campaign can be paused, resumed or aborted, and its counts and node-by-node funnel can be read at any time. See Campaigns and workflows.
All routes need Authorization: Bearer <key>. Reading needs the viewer role; everything else needs member. Errors use the workflow error body {"detail": {"code": "...", "message": "..."}} described in Workflows. An unknown id answers 404 with code not_found and message Campaign not found.
| Operation | Method and path |
|---|---|
| Create a campaign | POST /workflow-campaigns |
| List campaigns | GET /workflow-campaigns |
| Get a campaign | GET /workflow-campaigns/{campaign_id} |
| Delete a campaign | DELETE /workflow-campaigns/{campaign_id} |
| Upload entries | POST /workflow-campaigns/{campaign_id}/entries |
| List entries | GET /workflow-campaigns/{campaign_id}/entries |
| Download the entries template | GET /workflow-campaigns/{campaign_id}/entries/template |
| Start a campaign | POST /workflow-campaigns/{campaign_id}:start |
| Pause a campaign | POST /workflow-campaigns/{campaign_id}:pause |
| Resume a campaign | POST /workflow-campaigns/{campaign_id}:resume |
| Abort a campaign | POST /workflow-campaigns/{campaign_id}:abort |
| List a campaign's executions | GET /workflow-campaigns/{campaign_id}/executions |
| Get the campaign report | GET /workflow-campaigns/{campaign_id}/report |
Lifecycle
Campaign statuses, in order:
draft: created; entries can be uploaded.scheduled: started, waiting for itsscheduled_ator for the worker to pick it up.running: entries are being turned into executions.paused: no execution takes its next step until you resume. When the wallet runs out, the platform pauses the campaign itself and setspaused_reasontobalance-low.completed: no entry is pending and every execution has finished.aborted: stopped for good.
Campaigns have a kind. Those you create are batch. The platform also keeps one api campaign per workflow version to group single runs made with Run a workflow for one contact; api campaigns cannot be started or given entries.
The action routes put the verb after a colon, as in /workflow-campaigns/{campaign_id}:start. They take no body.
Operations
Create a campaign
POST /workflow-campaigns
Creates a campaign in draft, locked to a single published workflow version.
Auth: API key or session token. Role: member and up.
This route accepts an Idempotency-Key header; see Idempotency.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | body | string (UUID) | yes | The workflow. |
name | body | string | yes | 1 to 120 characters. |
version | body | integer | no | The published version to pin. Default: the latest. |
binding | body | object | no | agent_overrides and number_overrides, each an object keyed by agent node id. |
scheduled_at | body | string | no | ISO 8601 timestamp with an offset. After you start the campaign, it waits until this time. |
Request
curl -X POST https://api.sevennadi.si/workflow-campaigns \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"workflow_id": "2ab95da4-50dd-4492-a355-793ec7d810c5", "name": "October follow-ups"}'Response 201 Created
{
"id": "7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9",
"name": "October follow-ups",
"workflow_id": "2ab95da4-50dd-4492-a355-793ec7d810c5",
"workflow_version": 1,
"kind": "batch",
"status": "draft",
"entries_count": 0,
"success_count": 0,
"failure_count": 0,
"neutral_count": 0,
"terminal_count": 0,
"created_at": "2026-10-13T08:30:00Z",
"updated_at": "2026-10-13T08:30:00Z"
}scheduled_at and paused_reason appear only when set.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Workflow not found |
| 422 | validation_failed | workflow_id is required |
| 422 | validation_failed | name must be 1 to 120 characters |
| 422 | validation_failed | Unknown field <name> |
| 422 | validation_failed | version must be an integer or null or Version <n> is not published |
| 422 | validation_failed | binding holds agent_overrides and number_overrides |
| 422 | validation_failed | scheduled_at must be a timezone-aware ISO timestamp |
| 422 | no_published_version | The workflow has no published version to pin |
List campaigns
GET /workflow-campaigns
Returns the account's campaigns with their counts.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workflow_id | query | string (UUID) | no | Only campaigns of this workflow. |
status | query | string | no | draft, scheduled, running, paused, completed or aborted. |
kind | query | string | no | batch or api. |
sort_by | query | string | no | created_at (default), updated_at, name, entries_count, success_count or failure_count. |
order | query | string | no | asc or desc (default). |
limit | query | integer | no | 1 to 100. Default 20. |
offset | query | integer | no | Rows to skip. Default 0. |
Request
curl "https://api.sevennadi.si/workflow-campaigns?status=running&sort_by=updated_at" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"items": [
{
"id": "7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9",
"name": "October follow-ups",
"workflow_id": "2ab95da4-50dd-4492-a355-793ec7d810c5",
"workflow_version": 1,
"kind": "batch",
"status": "running",
"entries_count": 18,
"success_count": 12,
"failure_count": 3,
"neutral_count": 0,
"terminal_count": 15,
"created_at": "2026-10-13T08:30:00Z",
"updated_at": "2026-10-13T09:10:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}Errors
| Status | Code | Message |
|---|---|---|
| 422 | validation_failed | For example limit: Input should be less than or equal to 100 |
Get a campaign
GET /workflow-campaigns/{campaign_id}
Returns one campaign with its entries counted by status and its validation failures counted by code.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9 \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"campaign": {
"id": "7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9",
"name": "October follow-ups",
"workflow_id": "2ab95da4-50dd-4492-a355-793ec7d810c5",
"workflow_version": 1,
"kind": "batch",
"status": "draft",
"entries_count": 18,
"success_count": 0,
"failure_count": 0,
"neutral_count": 0,
"terminal_count": 0,
"created_at": "2026-10-13T08:30:00Z",
"updated_at": "2026-10-13T08:35:00Z"
},
"entry_counts": {"pending": 18, "failed_validation": 2},
"validation_errors": {"invalid_phone": 2}
}entries_count leaves out entries that failed validation.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
Delete a campaign
DELETE /workflow-campaigns/{campaign_id}
Deletes a campaign. Only a draft campaign with no entries and no executions can be deleted.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl -X DELETE https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9 \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 204 No Content
No body.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 409 | invalid_state | Only a draft campaign with no entries and no executions can be deleted (with campaign_status) |
Upload entries
POST /workflow-campaigns/{campaign_id}/entries
Adds contacts to a campaign. Send a CSV file as the raw request body with Content-Type: text/csv, or a JSON array of objects with Content-Type: application/json. This is not a multipart upload.
Each row is checked against the fields of the pinned workflow version. Valid rows become pending entries. Invalid rows are kept as failed_validation entries so you can see them; uploading a corrected row with the same reference_id replaces the failed one. You may upload while the campaign is draft, scheduled or running. In a running campaign, new entries are dispatched at once.
- A CSV needs a header row that names each column once, and must contain every required field as a column.
- A column or key that the workflow does not know is refused for the whole upload.
- A row without
reference_idgets one derived from its values, so an identical row uploaded twice is ignored. - Each upload: up to 150,000 rows, and 32 MB at most.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Content-Type | header | string | yes | text/csv or application/json. |
| body | body | CSV text or JSON array | yes | The contacts. |
Request
curl -X POST https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9/entries \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-H "Content-Type: text/csv" \
--data-binary @contacts.csvResponse 200 OK
{
"accepted": 18,
"corrected": 0,
"failed": 2,
"entries_count": 18,
"failures": [
{"row": 7, "reference_id": "loan-1048", "code": "invalid_phone", "message": "mobile_number: expected an E.164 phone number, e.g. +919876543210"},
{"row": 12, "reference_id": "loan-1042", "code": "duplicate_reference_id", "message": "reference_id loan-1042 is already in this campaign"}
],
"warnings": []
}For CSV, row counts the header as row 1. failures and warnings list at most 1000 items each. A warning with code missing_default_recipient marks an accepted row that has no mobile_number although the workflow dials it.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 409 | invalid_state | Entries can only be added while the campaign is draft, scheduled or running |
| 413 | payload_too_large | Uploads are capped at 32 MB or Uploads are capped at 150,000 rows |
| 422 | validation_failed | Send text/csv or application/json |
| 422 | validation_failed | The CSV must be UTF-8, The CSV is empty, The CSV header must name each column once, Malformed CSV: <detail> |
| 422 | validation_failed | The body is not valid JSON or The body must be a JSON array of entry objects |
| 422 | unknown_field | Unknown field <name> (with fields) |
| 422 | missing_required_field | Missing required column <name> (with fields) |
List entries
GET /workflow-campaigns/{campaign_id}/entries
Returns a campaign's entries.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
status | query | string | no | pending, failed_validation, dispatched or completed. |
sort_by | query | string | no | created_at (default), reference_id or status. |
order | query | string | no | asc or desc (default). |
limit | query | integer | no | 1 to 100. Default 20. |
offset | query | integer | no | Rows to skip. Default 0. |
Request
curl "https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9/entries?status=failed_validation" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"items": [
{
"id": "e1f2a3b4-c5d6-4e7f-8091-a2b3c4d5e6f7",
"reference_id": "loan-1048",
"mobile_number": "98765",
"name": "Ravi",
"custom_fields": {"due_date": "2026-11-04"},
"status": "failed_validation",
"validation_error": "invalid_phone",
"created_at": "2026-10-13T08:35:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}A dispatched entry carries the execution_id of its execution. An entry can also have the status skipped: it was held back at dispatch by the do-not-call list, missing consent or the daily frequency cap, and validation_error holds the reason. The status filter does not accept skipped; list without a filter to see those entries.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 422 | validation_failed | A message naming the bad query parameter |
Download the entries template
GET /workflow-campaigns/{campaign_id}/entries/template
Returns a CSV header row for the campaign's workflow version: the fields the start node declares, or the four system fields if it declares none.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl -o template.csv https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9/entries/template \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK with Content-Type: text/csv
reference_id,mobile_number,name,emailErrors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
Start a campaign
POST /workflow-campaigns/{campaign_id}:start
Starts a draft campaign. It becomes scheduled, then running as soon as the worker picks it up, or at scheduled_at if that is in the future. Starting a campaign that is already scheduled succeeds and changes nothing.
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.
Warning: Starting a campaign places real calls when the platform is connected to live telephony.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl -X POST "https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9:start" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{"status": "scheduled"}Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 409 | invalid_state | Only batch campaigns are started |
| 409 | invalid_state | The campaign already started (status <status>) |
| 409 | invalid_state | The campaign has no pending entries |
Every invalid_state error also carries campaign_status.
Pause a campaign
POST /workflow-campaigns/{campaign_id}:pause
Pauses a scheduled or running campaign. Calls already in progress finish; executions do not take their next step until you resume.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl -X POST "https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9:pause" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{"status": "paused"}Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 409 | invalid_state | Only a scheduled or running campaign can be paused |
Resume a campaign
POST /workflow-campaigns/{campaign_id}:resume
Resumes a paused campaign. A campaign that had not started yet goes back to scheduled; otherwise it returns to running, entries uploaded during the pause are dispatched and held executions continue.
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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl -X POST "https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9:resume" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{"status": "running"}status is the campaign's status after the resume: running, scheduled, or completed if nothing was left to do.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 409 | invalid_state | Only a paused campaign can be resumed |
Abort a campaign
POST /workflow-campaigns/{campaign_id}:abort
Stops a campaign for good. Executions that are under way end with the status aborted before their next external action, and send their webhook. Executions that had not begun end without a webhook. Entries that were never dispatched stay pending. An aborted campaign cannot be resumed.
Auth: API key or session token. Role: member and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl -X POST "https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9:abort" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{"status": "aborted"}Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 409 | invalid_state | The campaign is already completed or The campaign is already aborted |
List a campaign's executions
GET /workflow-campaigns/{campaign_id}/executions
Returns the executions of a campaign in summary form.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
status | query | string | no | pending, running, completed, failed, cancelled or aborted. |
outcome | query | string | no | success, failure, neutral, or none for executions without an outcome. |
sort_by | query | string | no | created_at (default), updated_at, status or outcome. |
order | query | string | no | asc or desc (default). |
limit | query | integer | no | 1 to 100. Default 20. |
offset | query | integer | no | Rows to skip. Default 0. |
Request
curl "https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9/executions?outcome=failure" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"items": [
{
"id": "exec:7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9:loan-1044",
"campaign_id": "7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9",
"status": "completed",
"current_node_id": "n_end_unreachable",
"termination_reason": "unreachable",
"outcome": "failure",
"call_count": 3,
"created_at": "2026-10-13T09:00:00Z",
"updated_at": "2026-10-13T12:40:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |
| 422 | validation_failed | A message naming the bad query parameter |
Get the campaign report
GET /workflow-campaigns/{campaign_id}/report
Returns totals for a campaign: executions by status, executions by termination reason, and a funnel that counts how often each node ended in each status.
Auth: API key or session token. Role: viewer and up.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | string (UUID) | yes | The campaign. |
Request
curl https://api.sevennadi.si/workflow-campaigns/7d6c5b4a-3928-4170-8695-a4b3c2d1e0f9/report \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"executions": {"completed": 15, "running": 3},
"termination_reasons": {"reached": 12, "unreachable": 3},
"funnel": [
{"node_id": "n_start", "status": "completed", "count": 18},
{"node_id": "n_call", "status": "completed", "count": 21},
{"node_id": "n_retry", "status": "completed", "count": 6},
{"node_id": "n_end_reached", "status": "completed", "count": 12},
{"node_id": "n_end_unreachable", "status": "completed", "count": 3}
]
}The funnel follows the order of the nodes in the definition. A node that ran several times for one contact, such as a retried call, is counted once per attempt.
Errors
| Status | Code | Message |
|---|---|---|
| 404 | not_found | Campaign not found |