Outcome
Replace every step of an exact draft sequence version.
Before you start
- Verify the environment, credential, authorized organization and the published action permission workspace:write.
- Discover an accessible Engage workspace and bind resourceType=workspace and its resourceId separately from action input. Fixture identifiers grant no access.
- Approve the exact mutation and its effects before CLI --confirm or MCP confirm:true. This documentation does not authorize outreach.
Discover the target
- Discover the sequence through sequences.list using the retained campaignId, then read sequences.get in the same authorized workspace. Inspect sequence.status, draft, published and versions before planning.
- Select draft.id and read sequences.versions.get. Retain the complete prior ordered steps; the input replaces the entire step set rather than appending.
topolo whoami --json
topolo actions get app_topolo_engage.sequences.versions.steps.set --json
topolo workspaces list topolo-engage --json
Input fields
| Field | Required | Meaning and constraints |
|---|---|---|
id | Yes | Required sequence identifier discovered through sequences.list in the selected workspace, trimmed non-empty string up to 128 characters. |
versionId | Yes | Required version identifier from sequences.get versions[], draft or published, trimmed non-empty string up to 128 characters. It must belong to the supplied sequence; version number is not versionId. |
steps | Yes | Required array of 1–10 steps in execution order. Every step needs delayDays integer 0–90, relative to enrollment for the first step and previous step thereafter. Email (type omitted or email): subjectTemplate trimmed ≤200, bodyTemplate trimmed 1–10,000 and required threadWithPrevious boolean. Tasks (linkedin_task/call_task): instructions trimmed 1–2,000 and optional dueDays integer 0–30. WhatsApp: templateName lowercase letters/digits/underscore 1–512, language such as en_US, and up to 10 ordered allowed merge-field variables. Use the strict current nested schema; do not mix variant fields. |
Validate, plan and execute
Set ENGAGE_WORKSPACE_ID to the identifier of your selected workspace. Prepare payload.json from the current input schema and discovered identifiers. Use the same payload for validation, planning and execution.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions validate app_topolo_engage.sequences.versions.steps.set --data @payload.json --json
topolo actions plan app_topolo_engage.sequences.versions.steps.set --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --confirm --json
Review the plan and obtain approval for the exact effects before executing the unchanged request.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions call app_topolo_engage.sequences.versions.steps.set --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --confirm --json
For MCP, use topolo_get_action, topolo_validate_action, topolo_plan_action and topolo_call_action. Pass the selected resource context separately; confirm:true is required for mutations.
Worked requests and responses
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 4,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: the run ledger > records a scheduled pass: the heartbeat and one row per workspace touched. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"type": "whatsapp_template",
"delayDays": 0,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
},
{
"delayDays": 2,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "whatsapp_template",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0,
"whatsappTemplate": {
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
}
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 2,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: the runner on a WhatsApp template step > sends the approved template to an opted-in contact on channel whatsapp and moves on. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"type": "linkedin_task",
"delayDays": 0,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
},
{
"delayDays": 3,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false
},
{
"type": "call_task",
"delayDays": 2,
"instructions": "Call the main office."
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "linkedin_task",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 3,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_3",
"position": 3,
"delayDays": 2,
"type": "call_task",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "Call the main office.",
"dueDays": 0
}
]
}
}
Source test: task steps in sequences > stores linkedin_task and call_task steps with instructions and a due offset, and copies them into a new version. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_2",
"versionId": "eng_seqv_2",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_2",
"sequenceId": "eng_seq_2",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_3",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: engagement analytics > reports first-touch rates by campaign: each contact once, bounced never replied, test campaigns excluded. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_3",
"versionId": "eng_seqv_3",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_3",
"sequenceId": "eng_seq_3",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_4",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_5",
"position": 2,
"delayDays": 4,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: engagement analytics > reports first-touch rates by campaign: each contact once, bounced never replied, test campaigns excluded. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_2",
"versionId": "eng_seqv_2",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_2",
"sequenceId": "eng_seq_2",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_3",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_4",
"position": 2,
"delayDays": 4,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: the run ledger > records a scheduled pass: the heartbeat and one row per workspace touched. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 3,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 3,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: task steps in sequences > refuses an email that threads with no earlier email, and task steps without instructions. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 1,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
},
{
"delayDays": 1,
"subjectTemplate": "",
"bodyTemplate": "Last one, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 1,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_3",
"position": 3,
"delayDays": 1,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Last one, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: runner with the Nexus transport contract > threads every follow-up from the stored RFC Message-IDs: In-Reply-To the previous, References root … previous. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_3",
"versionId": "eng_seqv_3",
"steps": [
{
"type": "linkedin_task",
"delayDays": 0,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
},
{
"delayDays": 3,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false
},
{
"type": "email",
"delayDays": 1,
"subjectTemplate": "",
"bodyTemplate": "Following up.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_3",
"sequenceId": "eng_seq_3",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_2",
"position": 1,
"delayDays": 0,
"type": "linkedin_task",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
},
{
"id": "eng_stp_3",
"position": 2,
"delayDays": 3,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_4",
"position": 3,
"delayDays": 1,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: task steps in sequences > refuses an email that threads with no earlier email, and task steps without instructions. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 1,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 1,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: runner with the Nexus transport contract > stores a send without provider ids, and threads only on stored RFC Message-IDs. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 3,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false
},
{
"type": "linkedin_task",
"delayDays": 0,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 3,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 0,
"type": "linkedin_task",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
}
]
}
}
Source test: the runner on a task step > not_interested stops the enrollment, records the negative outcome and a suppression ledger event. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"type": "whatsapp_template",
"delayDays": 0,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "whatsapp_template",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0,
"whatsappTemplate": {
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
}
}
]
}
}
Source test: the runner on a WhatsApp template step > the last step skipped starts the closing period. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"type": "linkedin_task",
"delayDays": 1,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 1,
"type": "linkedin_task",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
}
]
}
}
Source test: the runner on a task step > never renders a task step ahead for approval. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Hi {{first_name}}",
"bodyTemplate": "Hello.",
"threadWithPrevious": false
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Hi {{first_name}}",
"bodyTemplate": "Hello.",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: message approval > a message rendered after a change is approved under the new version; an unstored version falls back to the current one. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false
},
{
"type": "whatsapp_template",
"delayDays": 0,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 0,
"type": "whatsapp_template",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0,
"whatsappTemplate": {
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
}
}
]
}
}
Source test: the runner on a WhatsApp template step > respects the emergency stop, a paused campaign, approval and the touch limit. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 1,
"type": "whatsapp_template",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0,
"whatsappTemplate": {
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"sender_name"
]
}
}
]
}
}
Source test: the runner on a WhatsApp template step > render-ahead leaves WhatsApp steps alone. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-05T09:00:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 4,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: render ahead for approval > queues a message due tomorrow today, sends it in the window once approved, and never renders twice. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"type": "whatsapp_template",
"delayDays": 0,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name"
]
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "whatsapp_template",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0,
"whatsappTemplate": {
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name"
]
}
}
]
}
}
Source test: seat WhatsApp number > a WhatsApp step sends from the seat's own number over the workspace connection. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_2",
"versionId": "eng_seqv_2",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_2",
"sequenceId": "eng_seq_2",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 4,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: enrollment routes > requires an active campaign, a published sequence and a seat. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Hi {{first_name}}",
"bodyTemplate": "Hello {{first_name}}, see bit.ly/x",
"threadWithPrevious": false
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Hi {{first_name}}",
"bodyTemplate": "Hello {{first_name}}, see bit.ly/x",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: sequences.copy_check and publish enforcement > refuses to publish a version with copy errors and allows warnings. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_2",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_3",
"position": 2,
"delayDays": 4,
"type": "email",
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true,
"instructions": "",
"dueDays": 0
}
]
}
}
Source test: sequences.copy_check and publish enforcement > refuses to publish a version with copy errors and allows warnings. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}, a short note about the webinar.",
"threadWithPrevious": false
},
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name"
]
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 0,
"type": "email",
"subjectTemplate": "Hello {{first_name}}",
"bodyTemplate": "Hi {{first_name}}, a short note about the webinar.",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0
},
{
"id": "eng_stp_2",
"position": 2,
"delayDays": 1,
"type": "whatsapp_template",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0,
"whatsappTemplate": {
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name"
]
}
}
]
}
}
Source test: WhatsApp template steps in sequences > stores the template with its variables, copies it into a new version and gets no email copy findings. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 200
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name"
]
}
]
}
Actual local response
{
"version": {
"id": "eng_seqv_1",
"sequenceId": "eng_seq_1",
"version": 1,
"status": "draft",
"publishedAt": null,
"createdAt": "2026-10-06T08:30:00.000Z",
"steps": [
{
"id": "eng_stp_1",
"position": 1,
"delayDays": 1,
"type": "whatsapp_template",
"subjectTemplate": "",
"bodyTemplate": "",
"threadWithPrevious": false,
"instructions": "",
"dueDays": 0,
"whatsappTemplate": {
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name"
]
}
}
]
}
}
Source test: WhatsApp template steps in sequences > refuses unknown merge fields, bad template names and languages, and more than ten variables. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Effects and response meaning
- Atomically deletes and replaces the draft’s steps, assigning new step ids and positions in supplied order. Requires a non-archived sequence and draft version; published versions are immutable.
- Email templates allow first_name, last_name, company, title and sender_name merge fields. Non-threaded email requires a subject; first step cannot thread and later threading requires a prior email. Saving steps performs these structural checks, not the full publish-time copy QA.
- Manual LinkedIn/call task steps describe human work; WhatsApp steps reference template/language and ordered variables. Saving these steps is not provider approval, opt-in proof or delivery. Nothing is sent until separately authorized enrollment/runner work.
Verify the result
- Read sequences.versions.get and compare the complete ordered content and types with the reviewed input. Expect regenerated step ids. Run sequences.copy_check for QA before a separately reviewed publish.
Recover
After an uncertain response, read the exact draft and compare all steps before retrying. Repeating set generates new step ids, and an expected-version guard is absent. Restoring retained prior contents is a separately approved full replacement only while this version is still a draft; published versions require a new draft.
Refused responses
Captured sequences.versions.steps.set · HTTP 422
Action input
{
"id": "eng_seq_3",
"versionId": "eng_seqv_3",
"steps": [
{
"type": "linkedin_task",
"delayDays": 0,
"instructions": "Send a connection request mentioning the webinar.",
"dueDays": 2
},
{
"type": "email",
"delayDays": 1,
"subjectTemplate": "",
"bodyTemplate": "Following up.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"error": {
"code": "invalid_steps",
"message": "Some steps cannot be saved.",
"details": {
"problems": [
{
"position": 2,
"code": "no_email_to_thread"
}
]
}
}
}
Source test: task steps in sequences > refuses an email that threads with no earlier email, and task steps without instructions. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [
{
"type": "call_task",
"delayDays": 2,
"instructions": " "
}
],
"id": "eng_seq_3",
"versionId": "eng_seqv_3"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"steps",
0,
"instructions"
],
"message": "Too small: expected string to have >=1 characters"
}
]
}
}
Source test: task steps in sequences > refuses an email that threads with no earlier email, and task steps without instructions. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 409
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"error": {
"code": "state_conflict",
"message": "Published versions are immutable; create a new draft version."
}
}
Source test: sequence routes > creates a sequence with an empty draft, edits steps, publishes and freezes the version. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 404
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_2",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"error": {
"code": "not_found",
"message": "Sequence version was not found."
}
}
Source test: sequence routes > lists, filters, renames and scopes sequences. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Action input
{
"id": "eng_seq_3",
"versionId": "eng_seqv_3",
"steps": [
{
"type": "email",
"delayDays": 1,
"subjectTemplate": "",
"bodyTemplate": "Following up.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"error": {
"code": "invalid_steps",
"message": "Some steps cannot be saved.",
"details": {
"problems": [
{
"position": 1,
"code": "first_step_cannot_thread"
}
]
}
}
}
Source test: task steps in sequences > refuses an email that threads with no earlier email, and task steps without instructions. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [
{
"type": "call_task",
"delayDays": 2,
"instructions": " Call the main office. ",
"subjectTemplate": "x"
}
],
"id": "eng_seq_3",
"versionId": "eng_seqv_3"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"code": "unrecognized_keys",
"keys": [
"subjectTemplate"
],
"path": [
"steps",
0
],
"message": "Unrecognized key: \"subjectTemplate\""
}
]
}
}
Source test: task steps in sequences > refuses an email that threads with no earlier email, and task steps without instructions. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Hi",
"bodyTemplate": "Hello {{nickname}}",
"threadWithPrevious": true
},
{
"delayDays": 2,
"subjectTemplate": "",
"bodyTemplate": "Again",
"threadWithPrevious": false
}
]
}
Actual local response
{
"error": {
"code": "invalid_steps",
"message": "Some steps cannot be saved.",
"details": {
"problems": [
{
"position": 1,
"code": "first_step_cannot_thread"
},
{
"position": 1,
"code": "unknown_merge_field",
"field": "nickname"
},
{
"position": 2,
"code": "subject_required"
}
]
}
}
}
Source test: sequence routes > validates steps: no threading on step 1, subjects unless threaded, known merge fields, at least one step to publish. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [],
"id": "eng_seq_1",
"versionId": "eng_seqv_1"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"origin": "array",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"steps"
],
"message": "Too small: expected array to have >=1 items"
}
]
}
}
Source test: sequence routes > validates steps: no threading on step 1, subjects unless threaded, known merge fields, at least one step to publish. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 409
Action input
{
"id": "eng_seq_1",
"versionId": "eng_seqv_1",
"steps": [
{
"delayDays": 0,
"subjectTemplate": "Quick question, {{first_name}}",
"bodyTemplate": "Hi {{first_name}},\n\nHow is {{company}} handling outreach?\n\n{{sender_name}}",
"threadWithPrevious": false
},
{
"delayDays": 4,
"subjectTemplate": "",
"bodyTemplate": "Following up, {{first_name}}.",
"threadWithPrevious": true
}
]
}
Actual local response
{
"error": {
"code": "state_conflict",
"message": "Archived sequences cannot be changed."
}
}
Source test: sequence routes > allows one live sequence per campaign and refuses archived campaigns. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"phone"
]
}
],
"id": "eng_seq_1",
"versionId": "eng_seqv_1"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"code": "invalid_union",
"errors": [
[
{
"code": "invalid_value",
"values": [
"email"
],
"path": [
"type"
],
"message": "Invalid input: expected \"email\""
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"subjectTemplate"
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"bodyTemplate"
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "boolean",
"code": "invalid_type",
"path": [
"threadWithPrevious"
],
"message": "Invalid input: expected boolean, received undefined"
},
{
"code": "unrecognized_keys",
"keys": [
"templateName",
"language",
"variables"
],
"path": [],
"message": "Unrecognized keys: \"templateName\", \"language\", \"variables\""
}
],
[
{
"code": "invalid_value",
"values": [
"linkedin_task",
"call_task"
],
"path": [
"type"
],
"message": "Invalid option: expected one of \"linkedin_task\"|\"call_task\""
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"instructions"
],
"message": "Invalid input: expected string, received undefined"
},
{
"code": "unrecognized_keys",
"keys": [
"templateName",
"language",
"variables"
],
"path": [],
"message": "Unrecognized keys: \"templateName\", \"language\", \"variables\""
}
],
[
{
"code": "invalid_value",
"values": [
"first_name",
"last_name",
"company",
"title",
"sender_name"
],
"path": [
"variables",
0
],
"message": "Invalid option: expected one of \"first_name\"|\"last_name\"|\"company\"|\"title\"|\"sender_name\""
}
]
],
"path": [
"steps",
0
],
"message": "Invalid input"
}
]
}
}
Source test: WhatsApp template steps in sequences > refuses unknown merge fields, bad template names and languages, and more than ten variables. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "Webinar Invite",
"language": "en_GB",
"variables": [
"first_name"
]
}
],
"id": "eng_seq_1",
"versionId": "eng_seqv_1"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"origin": "string",
"code": "invalid_format",
"format": "regex",
"pattern": "/^[a-z0-9_]{1,512}$/",
"path": [
"steps",
0,
"templateName"
],
"message": "Invalid string: must match pattern /^[a-z0-9_]{1,512}$/"
}
]
}
}
Source test: WhatsApp template steps in sequences > refuses unknown merge fields, bad template names and languages, and more than ten variables. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "webinar_invite",
"language": "english",
"variables": [
"first_name"
]
}
],
"id": "eng_seq_1",
"versionId": "eng_seqv_1"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"origin": "string",
"code": "invalid_format",
"format": "regex",
"pattern": "/^[a-z]{2,3}(_[A-Z]{2})?$/",
"path": [
"steps",
0,
"language"
],
"message": "Invalid string: must match pattern /^[a-z]{2,3}(_[A-Z]{2})?$/"
}
]
}
}
Source test: WhatsApp template steps in sequences > refuses unknown merge fields, bad template names and languages, and more than ten variables. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name",
"first_name",
"first_name",
"first_name",
"first_name",
"first_name",
"first_name",
"first_name",
"first_name",
"first_name",
"first_name"
]
}
],
"id": "eng_seq_1",
"versionId": "eng_seqv_1"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"origin": "array",
"code": "too_big",
"maximum": 10,
"inclusive": true,
"path": [
"steps",
0,
"variables"
],
"message": "Too big: expected array to have <=10 items"
}
]
}
}
Source test: WhatsApp template steps in sequences > refuses unknown merge fields, bad template names and languages, and more than ten variables. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured sequences.versions.steps.set · HTTP 422
Refused input; do not execute
{
"steps": [
{
"type": "whatsapp_template",
"delayDays": 1,
"templateName": "webinar_invite",
"language": "en_GB",
"variables": [
"first_name"
],
"subjectTemplate": "x"
}
],
"id": "eng_seq_1",
"versionId": "eng_seqv_1"
}
Actual local response
{
"error": {
"code": "invalid_action_input",
"message": "Input for sequences.versions.steps.set is invalid.",
"details": [
{
"code": "unrecognized_keys",
"keys": [
"subjectTemplate"
],
"path": [
"steps",
0
],
"message": "Unrecognized key: \"subjectTemplate\""
}
]
}
}
Source test: WhatsApp template steps in sequences > refuses unknown merge fields, bad template names and languages, and more than ten variables. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Error recovery
- HTTP 403
permission_denied: Stop and inspect the permission required by this action in the current credential. Do not broaden permissions or switch organization automatically. - HTTP 422
invalid_action_input: Correct required fields, enums and nested step bounds against the current input schema, then validate the exact intended payload again. - HTTP 422
invalid_steps: Inspect details.problems and the current step schema. Correct the exact draft before validating again; a version must contain steps to publish, thread rules must hold and unknown merge fields are refused. - HTTP 409
state_conflict: Read sequences.get and the exact version. Archived sequences cannot change, only one non-archived sequence per campaign and one draft per sequence may exist, and published versions cannot be edited. Re-plan only a legal, separately approved operation. - HTTP 404
not_found: Rediscover the sequence using sequences.list in the same workspace and campaign; use status=archived when needed. Discover versionId from sequences.get and confirm it belongs to the same sequence. Do not guess another workspace or version.
Related actions
Evidence and limits
-
contracts/sequences.mjs -
apps/api/src/sequences/sequences-handlers.ts -
apps/api/src/sequences/sequences-domain.ts -
apps/api/src/sequences/sequences-repository.ts -
apps/api/src/copy-qa/version-steps.ts -
apps/api/src/copy-qa/copy-qa.ts -
Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
-
Widget/bootstrap use actual app handler but mocked worker runtime and app identity.
-
No gateway execution, live permissions, external delivery or deployed proof.
The examples are local route captures with synthetic context and simulated providers. They do not prove live authorization, CLI/MCP gateway execution, production state or delivery.