---
title: "Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail, If / else, Wait for them to accept). A workflow (stepModel workflow, or steps with key, next, yesNext or noNext) branches: condition and wait_for_connection steps continue on yesNext or noNext, other steps on next; each points at another step's key or end. Steps are checked first: a field the step type would drop, missing text, {TOKEN} single braces or paths that don't hold together are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only"
method: PUT
path: "/campaigns/{sequenceId}/steps"
tags: ["campaigns"]
---

# Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail, If / else, Wait for them to accept). A workflow (stepModel workflow, or steps with key, next, yesNext or noNext) branches: condition and wait_for_connection steps continue on yesNext or noNext, other steps on next; each points at another step's key or end. Steps are checked first: a field the step type would drop, missing text, {TOKEN} single braces or paths that don't hold together are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only

`PUT /campaigns/{sequenceId}/steps`

## Request

- Base URL: `https://www.dench.com/api/v1`
- URL: `https://www.dench.com/api/v1/campaigns/{sequenceId}/steps`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer

Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail, If / else, Wait for them to accept). A workflow (stepModel workflow, or steps with key, next, yesNext or noNext) branches: condition and wait_for_connection steps continue on yesNext or noNext, other steps on next; each points at another step's key or end. Steps are checked first: a field the step type would drop, missing text, {TOKEN} single braces or paths that don't hold together are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only.

## Path parameters

- `sequenceId` string, required

## Request body

- object
  - `steps` object[], required: Every step, in send order (a workflow may list them in any order; they are stored in path order). Rejected, not trimmed: a field the step type would drop (onlyIf off email/post-like steps, a body on a visit, reply-in-thread on the first email, condition fields its check ignores), a missing subject or required body, an overlength note/message/InMail subject, out-of-range wait/withdrawal/post-age/acceptance values, {TOKEN} single braces, which would be sent as typed, and workflow paths that don't hold together (duplicate keys, a target that isn't a key or end, a condition or wait_for_connection without both yesNext and noNext, or any other step with them).
    - `type` 'wait' | 'email' | 'linkedin_connection' | 'linkedin_message' | 'linkedin_visit' | 'linkedin_like_post' | 'linkedin_inmail' | 'condition' | 'wait_for_connection', required: Step type. condition (If / else) and wait_for_connection (Wait for them to accept) send nothing; they continue on yesNext or noNext and make the campaign a workflow.
    - `waitHours` number: wait steps: hours to wait before the next step.
    - `emailMailboxAccountId` string: Legacy email step sender. Pool campaigns choose senders in emailMailboxAccountIds on the campaign, not on individual steps.
    - `emailSubject` string: email steps: subject; {{Column}} merge tokens allowed.
    - `emailHtmlBody` string: email steps: HTML body. Sent instead of emailTextBody when both are set, so when you edit the text, update or drop this too.
    - `emailTextBody` string: email steps: plain-text body; used as the email when there is no emailHtmlBody.
    - `emailReplyToPrevious` boolean: email steps: thread under the previous email step instead of a new subject.
    - `onlyIf` 'always' | 'connected' | 'not_connected': email and linkedin_like_post only: always (default), connected or not_connected. InMail automatically skips connected people; do not set a condition on it.
    - `linkedinAccountId` string: Legacy LinkedIn step sender (also on wait_for_connection, and on a condition that checks linkedin_connected). Pool campaigns use the campaign's linkedinAccountIds instead.
    - `linkedinMessageBody` string: linkedin_connection: the invite note (optional, up to 300 chars; sent without it if LinkedIn refuses the note); linkedin_message: the required message (up to 3000 chars); linkedin_inmail: the required message (up to 1900 chars).
    - `linkedinMessageMode` 'skip_if_not_connected' | 'wait_until_connected': linkedin_message: what to do when the invite is still pending.
    - `connectionTimeoutHours` number: linkedin_message with linkedinMessageMode=wait_until_connected: wait 1-2160 hours for acceptance (default 720); after a connection step this mode is the default in a linear campaign. wait_for_connection: wait 24-2160 hours (1-90 days, default 720) before taking the Not accepted path.
    - `likeReaction` 'like' | 'celebrate' | 'support' | 'love' | 'insightful' | 'funny': linkedin_like_post only: the reaction to leave (default like).
    - `likeMaxPostAgeDays` integer: linkedin_like_post only: select the newest eligible original post from the last 1-365 days (default 30).
    - `likeSkipPreviousWait` boolean: linkedin_like_post only: before the immediately preceding wait starts, check for eligible posts. If none exist, skip that wait and the like step, keeping following waits. Defaults to false when omitted.
    - `likeSkipNextWait` boolean: linkedin_like_post only: skip consecutive following wait steps whenever the reaction step is skipped (default true). False keeps those waits.
    - `likeCount` integer: linkedin_like_post only: how many recent posts to react to, 1-3 (default 1).
    - `inmailSubject` string: linkedin_inmail only: optional subject, up to 200 characters; merge tokens allowed. Clear with an empty string.
    - `withdrawAfterHours` number, nullable: linkedin_connection only: withdraw a still-pending invitation sent by this campaign after 168-2160 hours (7-90 days). Omitted means off on creation; null turns it off on edit.
    - `key` string: Workflow steps: this step's id, unique in the campaign, which next, yesNext and noNext point at (not "end"). Left out, one is made.
    - `next` string: Workflow steps: where a step that isn't a condition or wait_for_connection goes; left out, the following step: another step's key, or "end" to stop there. Fixed once the campaign launches.
    - `yesNext` string: Workflow steps: condition and wait_for_connection only, required: where the Yes (Accepted) path goes: another step's key, or "end" to stop there. Fixed once the campaign launches.
    - `noNext` string: Workflow steps: condition and wait_for_connection only, required: where the No (Not accepted) path goes: another step's key, or "end" to stop there. Fixed once the campaign launches.
    - `check` 'linkedin_connected' | 'has_email' | 'opened_last_email' | 'field_value': condition only, required: linkedin_connected, has_email, opened_last_email (needs an email before it on every path) or field_value. Fixed once the campaign launches.
    - `conditionField` string: condition with check=field_value: the lead field to check, like "Job Title" or "Source" (the campaign itself changes Status, Last touch and LinkedIn connection). Fixed once the campaign launches.
    - `conditionOperator` 'is' | 'is_not' | 'is_empty' | 'is_not_empty' | 'contains': condition with check=field_value: is, is_not, contains, is_empty or is_not_empty (all case-insensitive). Fixed once the campaign launches.
    - `conditionValue` string: condition with check=field_value and is, is_not or contains: the text to compare with. Editable after launch.
  - `stepModel` 'linear' | 'workflow': linear: steps run in order. workflow: steps have keys and paths (next, yesNext, noNext) and may include condition and wait_for_connection. Left out, a workflow stays one and steps with keys, paths or those types make one; nothing turns a workflow back into a list. When replacing a workflow's steps, send workflow, so steps without keys or paths are checked here the way they will be saved.
  - `allowSingleBraces` boolean: Send {single braces} exactly as typed instead of rejecting them. Merge tokens always use {{double braces}}.

## Response `200`

Successful response

- object
  - `ok` boolean, required
  - `stepCount` number, required

## Other responses

- `400`: Invalid request
- `401`: Missing or invalid authentication
- `404`: Operation or resource not found
- `500`: Server error

---

[API](https://skmtc.dev/dench/apis/dench-api.md) - [All operations](https://skmtc.dev/dench/apis/dench-api/llms.txt) - [OpenAPI document](https://skmtc.dev/dench/apis/dench-api/revisions/4a145b38f19c?raw)
