Steps
From the Campaigns API reference. Requests use the base URL https://app.warmerly.com/api/v1 and an API key, see Authentication.
Steps are the ordered sequence of touches (email, LinkedIn) leads move through, with a delay before each one.
#List steps
GET /campaigns/{id}/stepssteps is the sequence in send order. This is the field to read. The response also carries nodes, edges and entryStepId, the same steps as a graph, which the dashboard's visual editor uses; you can ignore them.
A step's waitDays / waitHours is the delay before that step sends, counted from the previous step (the first step's delay is counted from when the lead enters the campaign).
{
"steps": [
{
"id": "c1b2a3d4-1111-4b7a-8e2a-1a2b3c4d5e6f",
"campaignId": "b6a1e7b0-2f2b-4b7a-8e2a-1a2b3c4d5e6f",
"stepOrder": 1,
"kind": "email",
"subject": "Quick question about {{companyName}}",
"bodyText": "Hi {{firstName}}, ...",
"bodyHtml": null,
"waitDays": 0,
"waitHours": 0,
"variants": [],
"createdAt": "2026-06-01T09:00:00.000Z"
},
{
"id": "c1b2a3d4-3333-4b7a-8e2a-1a2b3c4d5e6f",
"campaignId": "b6a1e7b0-2f2b-4b7a-8e2a-1a2b3c4d5e6f",
"stepOrder": 2,
"kind": "email",
"subject": "Following up",
"bodyText": "Hi {{firstName}}, just bumping this...",
"bodyHtml": null,
"waitDays": 3,
"waitHours": 0,
"variants": [],
"createdAt": "2026-06-01T09:00:00.000Z"
}
],
"nodes": ["…the same steps…"],
"edges": ["…how they connect…"],
"entryStepId": "c1b2a3d4-1111-4b7a-8e2a-1a2b3c4d5e6f"
}Each step is the stored step row, so it has further fields than shown (for example aiPersonalize, variantMinSampleSize, and the editor's positionX / positionY), and variants lists any A/B variants. kind is one of email, linkedin_connection, linkedin_message.
#Replace steps
PUT /campaigns/{id}/stepsReplace-all: send the whole sequence, in order, as steps (up to 50 entries). stepOrder is assigned from array position, don't send it yourself.
| Field | Type | Required |
|---|---|---|
kind | "email" \| "wait" \| "linkedin_connection" \| "linkedin_message" | no (default "email") |
subject | string or null | no (needed for an email step to send) |
bodyText | string or null | no (needed for an email step to send) |
bodyHtml | string or null | no |
waitDays | integer (0-365) | no (default 0) |
waitHours | integer (0-23) | no (default 0) |
Two ways to space steps out, and you can mix them:
- put
waitDays/waitHourson the step itself, or - insert a
{ "kind": "wait", "waitDays": 3 }entry between two steps.
A wait entry is not stored as a step of its own. Its delay is added to the next step, so GET returns the example below as two steps, the second with waitDays: 3. A wait at the very end is dropped (there is nothing after it to delay).
Subjects and bodies support merge tags: {{firstName}}, {{lastName}}, {{companyName}}, {{email}}, the sender's {{senderFirstName}} / {{senderName}}, and any customVars key you set on the campaign's leads.
Editing a running campaign: the Nth email step you send keeps the id of the campaign's existing Nth email step, so rewriting the copy of a live sequence does not move leads off the step they are on. On an active or paused campaign, removing a step that leads are currently waiting at is refused with a 409 (step_in_use, with the step ids and lead counts in error.details) and nothing is changed.
curl -X PUT https://app.warmerly.com/api/v1/campaigns/b6a1e7b0-2f2b-4b7a-8e2a-1a2b3c4d5e6f/steps -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -H "Content-Type: application/json" -d '{
"steps": [
{ "kind": "email", "subject": "Quick question about {{companyName}}", "bodyText": "Hi {{firstName}}, ..." },
{ "kind": "wait", "waitDays": 3 },
{ "kind": "email", "subject": "Following up", "bodyText": "Hi {{firstName}}, just bumping this..." }
]
}'Returns the saved sequence in the same shape as GET /campaigns/{id}/steps.
| Status | error.code | When |
|---|---|---|
400 | bad_request | The body is not a valid steps array (the details name the field) |
404 | not_found | The campaign doesn't exist or isn't yours |
409 | step_in_use | You removed a step leads are currently waiting at, on an active or paused campaign |