curl --request PATCH \
--url https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"delays": [
{
"step": 100,
"delay_days": 182
}
],
"scope": "new_and_unstarted",
"expected_updated_at": "2023-11-07T05:31:56Z"
}
'import requests
url = "https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays"
payload = {
"delays": [
{
"step": 100,
"delay_days": 182
}
],
"scope": "new_and_unstarted",
"expected_updated_at": "2023-11-07T05:31:56Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
delays: [{step: 100, delay_days: 182}],
scope: 'new_and_unstarted',
expected_updated_at: '2023-11-07T05:31:56Z'
})
};
fetch('https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'delays' => [
[
'step' => 100,
'delay_days' => 182
]
],
'scope' => 'new_and_unstarted',
'expected_updated_at' => '2023-11-07T05:31:56Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays"
payload := strings.NewReader("{\n \"delays\": [\n {\n \"step\": 100,\n \"delay_days\": 182\n }\n ],\n \"scope\": \"new_and_unstarted\",\n \"expected_updated_at\": \"2023-11-07T05:31:56Z\"\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"delays\": [\n {\n \"step\": 100,\n \"delay_days\": 182\n }\n ],\n \"scope\": \"new_and_unstarted\",\n \"expected_updated_at\": \"2023-11-07T05:31:56Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"delays\": [\n {\n \"step\": 100,\n \"delay_days\": 182\n }\n ],\n \"scope\": \"new_and_unstarted\",\n \"expected_updated_at\": \"2023-11-07T05:31:56Z\"\n}"
response = http.request(request)
puts response.read_body{
"campaign_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "draft",
"version": "2023-11-07T05:31:56Z",
"scope": "new_and_unstarted",
"applied": true,
"cohorts": {
"leads_not_started": 0,
"leads_in_flight": 0,
"leads_waiting_acceptance": 0,
"leads_sending_now": 0,
"leads_finished": 0,
"earliest_in_flight_step": 123,
"leads_pinned": 0,
"leads_already_pinned": 0,
"leads_protection_unknown": 0
},
"already_applied": false,
"version_conflict": false,
"blocking": [
"<string>"
],
"changes": [
{
"step": 123,
"channel": "<string>",
"delay_days": 123,
"is_change": true,
"action_type": "<string>",
"current_delay_days": 123,
"current_delay_malformed": false,
"leads_affected": 0,
"leads_protected": 0,
"leads_waiting_acceptance_protected": 0,
"leads_past_step": 0
}
],
"steps": [
{
"step": 123,
"channel": "<string>",
"editable": true,
"action_type": "<string>",
"delay_days": 123,
"stored_delay_days": 123
}
],
"notes": [
"<string>"
]
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Update Campaign Step Delays
Apply a wait (delay) change to a launched (active/paused) campaign.
New and not-yet-started leads (and every future enrollment) get the new waits;
people already in the sequence keep the waits they were enrolled under,
because the same transaction freezes those onto them. Writes ONLY the
delay_days of the named steps plus that freeze, under an updated_at CAS
(expected_updated_at is required). Never rewrites a scheduled action, never
resends a sent step, never changes the shape of the sequence.
The CAS is checked BEFORE the no-change shortcut: a stale expected_updated_at
is 409 campaigns_delay_edit_version_conflict even when the stored waits
already equal your request, because matching values are not a receipt that
YOUR submission committed. already_applied=true therefore means only “your
version was current and the waits already matched” — nothing was written.
If an apply’s outcome is unknown (503 campaigns_delay_edit_unknown, or any
response you never received), reconcile READ-ONLY with
POST .../step-delays/preview and report the observed waits: do not re-send
the same body and do not retry on a timer. A new write needs an explicit user
action against a freshly previewed version.
curl --request PATCH \
--url https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"delays": [
{
"step": 100,
"delay_days": 182
}
],
"scope": "new_and_unstarted",
"expected_updated_at": "2023-11-07T05:31:56Z"
}
'import requests
url = "https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays"
payload = {
"delays": [
{
"step": 100,
"delay_days": 182
}
],
"scope": "new_and_unstarted",
"expected_updated_at": "2023-11-07T05:31:56Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
delays: [{step: 100, delay_days: 182}],
scope: 'new_and_unstarted',
expected_updated_at: '2023-11-07T05:31:56Z'
})
};
fetch('https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'delays' => [
[
'step' => 100,
'delay_days' => 182
]
],
'scope' => 'new_and_unstarted',
'expected_updated_at' => '2023-11-07T05:31:56Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays"
payload := strings.NewReader("{\n \"delays\": [\n {\n \"step\": 100,\n \"delay_days\": 182\n }\n ],\n \"scope\": \"new_and_unstarted\",\n \"expected_updated_at\": \"2023-11-07T05:31:56Z\"\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"delays\": [\n {\n \"step\": 100,\n \"delay_days\": 182\n }\n ],\n \"scope\": \"new_and_unstarted\",\n \"expected_updated_at\": \"2023-11-07T05:31:56Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/campaigns/{campaign_id}/step-delays")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"delays\": [\n {\n \"step\": 100,\n \"delay_days\": 182\n }\n ],\n \"scope\": \"new_and_unstarted\",\n \"expected_updated_at\": \"2023-11-07T05:31:56Z\"\n}"
response = http.request(request)
puts response.read_body{
"campaign_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "draft",
"version": "2023-11-07T05:31:56Z",
"scope": "new_and_unstarted",
"applied": true,
"cohorts": {
"leads_not_started": 0,
"leads_in_flight": 0,
"leads_waiting_acceptance": 0,
"leads_sending_now": 0,
"leads_finished": 0,
"earliest_in_flight_step": 123,
"leads_pinned": 0,
"leads_already_pinned": 0,
"leads_protection_unknown": 0
},
"already_applied": false,
"version_conflict": false,
"blocking": [
"<string>"
],
"changes": [
{
"step": 123,
"channel": "<string>",
"delay_days": 123,
"is_change": true,
"action_type": "<string>",
"current_delay_days": 123,
"current_delay_malformed": false,
"leads_affected": 0,
"leads_protected": 0,
"leads_waiting_acceptance_protected": 0,
"leads_past_step": 0
}
],
"steps": [
{
"step": 123,
"channel": "<string>",
"editable": true,
"action_type": "<string>",
"delay_days": 123,
"stored_delay_days": 123
}
],
"notes": [
"<string>"
]
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path Parameters
Body
Request body for the delay preview and the delay apply routes.
Requested wait changes (unique step numbers).
1 - 20 elementsShow child attributes
Show child attributes
Cohort the edit applies to. Only 'new_and_unstarted' exists: already-started leads always keep the waits they were enrolled under.
new_and_unstarted The campaign's updated_at the edit was composed against. REQUIRED on apply (optimistic concurrency); optional on preview, where a mismatch is reported as version_conflict instead of failing.
Response
Successful Response
Preview/apply result for a launched-campaign delay edit.
Campaign ID
Campaign status at read time
draft, launching, scoring, review_ready, scheduled, active, draining, paused, completed, failed campaigns.updated_at AFTER this call; send back as expected_updated_at.
Applied scope (echo)
new_and_unstarted True only when this call wrote the new waits
Campaign-wide cohort snapshot
Show child attributes
Show child attributes
True when the stored sequence already carried the requested waits, so nothing was written. On an apply the version CAS has already passed, so it means 'nothing has changed since the version you sent' — it is NOT a receipt that an earlier submission of yours committed.
Preview only: expected_updated_at is no longer the current version.
Machine reasons an apply would refuse right now ('version_conflict', 'send_in_progress', 'protection_incomplete', 'protection_unknown'); empty means clear.
Per-step analysis of the request
Show child attributes
Show child attributes
The full sequence. delay_days is the proposed sequence on a preview and the persisted one on an apply; stored_delay_days is always what is persisted.
Show child attributes
Show child attributes
Short human-readable strings safe to render verbatim.