Reference

Campaigns

Orchestrate sends across an audience, sequence, and inbox set. Lifecycle: draft → start (with optional dry-run) → pause / resume.

List campaigns

get /v1/campaigns
curl -X GET "https://api.clement.so/v1/campaigns" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Query parameters

  • limit integer
  • starting_after string
  • ending_before string
  • status string

Response200

OK

Property Type Description
object required enum (1)
"list"
data required array<Campaign>
has_more required boolean
next_cursor required string | null
prev_cursor required string | null
Example response
{
  "object": "list",
  "data": [
    {
      "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
      "object": "campaign",
      "mode": "live",
      "name": "Q2 outbound — Tier 1 prospects",
      "status": "running",
      "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
      "schedule": {
        "timezone": "America/New_York",
        "daysOfWeek": [
          1,
          2,
          3,
          4,
          5
        ],
        "startHour": 9,
        "endHour": 17
      },
      "sending_inbox_ids": [
        "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
      ],
      "sending_inbox_tags": [
        "outbound"
      ],
      "track_opens": false,
      "track_clicks": true,
      "inject_unsubscribe_footer": true,
      "send_status": "sending",
      "send_status_at": "2026-05-11T14:22:00Z",
      "paused_reason": null,
      "created_at": "2026-05-02T16:30:00Z",
      "updated_at": "2026-05-02T16:35:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": "string",
  "prev_cursor": "string"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Create a campaign (snapshots the source sequence's steps)

post /v1/campaigns

Copies the steps of `sequence_id` into per-campaign rows so future edits to the sequence template don't affect this campaign. The new campaign starts in state `draft`; call POST /v1/campaigns/{id}/start to launch.

curl -X POST "https://api.clement.so/v1/campaigns" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "name": "Alice Liddell",
      "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L5",
      "sending_inbox_ids": [
        "string"
      ],
      "sending_inbox_tags": [
        "string"
      ],
      "schedule": {
        "timezone": "string",
        "daysOfWeek": [
          0
        ],
        "startHour": 0,
        "endHour": 0
      },
      "track_opens": false,
      "track_clicks": false,
      "inject_unsubscribe_footer": false
    }'

Request body

Property Type Description
name required string
sequence_id required string
sending_inbox_ids array<string> Explicit sending inboxes. Required unless `sending_inbox_tags` is set.
sending_inbox_tags array<string> Send from every active inbox carrying one of these tags (case-insensitive), in addition to `sending_inbox_ids`.
schedule object
Show properties
Property Type Description
timezone string
daysOfWeek array<integer>
startHour integer
endHour integer
track_opens boolean
track_clicks boolean
inject_unsubscribe_footer boolean

Response201

Created

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Retrieve a campaign (with sequence steps embedded)

get /v1/campaigns/{id}
curl -X GET "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Response200

OK

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Update a campaign

patch /v1/campaigns/{id}

Mutable while in `draft` or `paused`; mutating a `running` campaign is rejected with 409 — pause first.

curl -X PATCH "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "name": "Alice Liddell",
      "sending_inbox_ids": [
        "string"
      ],
      "sending_inbox_tags": [
        "string"
      ],
      "schedule": {
        "timezone": "string",
        "daysOfWeek": [
          0
        ],
        "startHour": 0,
        "endHour": 0
      },
      "track_opens": false,
      "track_clicks": false,
      "inject_unsubscribe_footer": false
    }'

Path parameters

  • id string required

Request body

Property Type Description
name string
sending_inbox_ids array<string> May be empty when the campaign sends by `sending_inbox_tags`.
sending_inbox_tags array<string> | null Send from every active inbox carrying one of these tags (case-insensitive), in addition to `sending_inbox_ids`.
schedule object
Show properties
Property Type Description
timezone string
daysOfWeek array<integer>
startHour integer
endHour integer
track_opens boolean
track_clicks boolean
inject_unsubscribe_footer boolean

Response200

Updated

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Delete a campaign

delete /v1/campaigns/{id}

Cascades to its snapshot sequence_steps and campaign_contacts. Queued (not yet sent) sends of its contacts are canceled; sends already handed to the provider complete. Historical sends keep their email snapshot. Confirmation required when status is `running`.

curl -X DELETE "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Query parameters

  • confirm string

Response200

Deleted

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

List contacts attached to this campaign with progress

get /v1/campaigns/{id}/contacts
curl -X GET "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/contacts" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Query parameters

  • limit integer
  • starting_after string
  • ending_before string
  • status string
  • email string

Response200

OK

Property Type Description
object required enum (1)
"list"
data required array<CampaignContactProgress>
has_more required boolean
next_cursor required string | null
prev_cursor required string | null
Example response
{
  "object": "list",
  "data": [
    {
      "id": "string",
      "object": "campaign_contact",
      "contact_id": "con_01HZ8X3J9K7P2QV6R5N4M3T2L2",
      "contact_email": "alice@example.com",
      "current_step": 0,
      "status": "string",
      "next_send_at": "2026-04-29T10:00:00Z",
      "assigned_inbox_id": "ass_01HZ8X3J9K7P2QV6R5N4M3T2L1"
    }
  ],
  "has_more": false,
  "next_cursor": "string",
  "prev_cursor": "string"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Attach contacts (or an audience) to a campaign

post /v1/campaigns/{id}/contacts

Idempotent on `(campaign, contact)`. Pass either `contact_ids` (up to 1,000) or `audience_id` (all contacts in the audience, snapshotted now). Unsubscribed, bounced, suppressed and `invalid`/`risky`-verified contacts are skipped and counted by reason. A contact previously removed from this campaign resumes where it stopped, unless it replied. Attaching new contacts to a `completed` campaign puts it back to `running` (emits `campaign.resumed`).

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/contacts" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "contact_ids": [
        "string"
      ],
      "audience_id": "aud_01HZ8X3J9K7P2QV6R5N4M3T2L3",
      "skip_if_in_other_campaign": false
    }'

Path parameters

  • id string required

Request body

Property Type Description
contact_ids array<string>
audience_id string
skip_if_in_other_campaign boolean Skip contacts already enrolled (in any status but `removed`) in another campaign of this workspace.

Response200

Result

Property Type Description
object required enum (1)
"attach_result"
attached required integer Contacts the campaign gained, including `restored` ones.
skipped required integer Everything not attached: `already_enrolled` plus every skip reason.
restored required integer Previously removed from this campaign: back to `active` at the step where they stopped.
already_enrolled required integer
not_found required integer Requested contacts that don't exist in this workspace and mode.
skipped_reasons required CampaignEnrollSkipReasons
Show properties
Property Type Description
unsubscribed required integer
bounced required integer
suppressed required integer Blacklisted, or on the workspace suppression list.
verification required integer Verification result `invalid` or `risky`.
replied required integer Previously removed from this campaign after replying to it; never re-added.
in_other_campaign required integer Only with `skip_if_in_other_campaign`.
Example response
{
  "object": "attach_result",
  "attached": 0,
  "skipped": 0,
  "restored": 0,
  "already_enrolled": 0,
  "not_found": 0,
  "skipped_reasons": {
    "unsubscribed": 0,
    "bounced": 0,
    "suppressed": 0,
    "verification": 0,
    "replied": 0,
    "in_other_campaign": 0
  }
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Move or copy contacts to another campaign

post /v1/campaigns/{id}/contacts/move

Enrolls the contacts in `to_campaign_id` under that campaign's rules (fresh start at step 0; same skip reasons as attaching), then removes them from this campaign — only those now in the target, so a skipped contact stays where it was. `keep_in_source: true` copies instead.

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/contacts/move" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "to_campaign_id": "to__01HZ8X3J9K7P2QV6R5N4M3T2L1",
      "contact_ids": [
        "string"
      ],
      "status": "active",
      "keep_in_source": false,
      "skip_if_in_other_campaign": false
    }'

Path parameters

  • id string required

Request body

Property Type Description
to_campaign_id required string
contact_ids array<string>
status enum (6) Without `contact_ids`: take every enrollment in this status.
"active""replied""bounced""unsubscribed""finished""failed"
keep_in_source boolean Copy instead of move: the source enrollments stay as they are.
skip_if_in_other_campaign boolean Skip contacts enrolled in a campaign other than the source and the target.

Response200

Result

Property Type Description
object required enum (1)
"move_result"
attached required integer Contacts the campaign gained, including `restored` ones.
skipped required integer Everything not attached: `already_enrolled` plus every skip reason.
restored required integer Previously removed from this campaign: back to `active` at the step where they stopped.
already_enrolled required integer
not_found required integer Requested contacts that don't exist in this workspace and mode.
skipped_reasons required CampaignEnrollSkipReasons
Show properties
Property Type Description
unsubscribed required integer
bounced required integer
suppressed required integer Blacklisted, or on the workspace suppression list.
verification required integer Verification result `invalid` or `risky`.
replied required integer Previously removed from this campaign after replying to it; never re-added.
in_other_campaign required integer Only with `skip_if_in_other_campaign`.
removed_from_source required integer Active source enrollments set to `removed` because the contact is now in the target. Closed ones (replied, finished, …) stay as history.
canceled_sends required integer
Example response
{
  "object": "move_result",
  "attached": 0,
  "skipped": 0,
  "restored": 0,
  "already_enrolled": 0,
  "not_found": 0,
  "skipped_reasons": {
    "unsubscribed": 0,
    "bounced": 0,
    "suppressed": 0,
    "verification": 0,
    "replied": 0,
    "in_other_campaign": 0
  },
  "removed_from_source": 0,
  "canceled_sends": 0
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Remove contacts from a campaign (bulk)

post /v1/campaigns/{id}/contacts/remove

Bulk form of `DELETE /v1/campaigns/{id}/contacts/{contact_id}`, up to 1,000 ids.

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/contacts/remove" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "contact_ids": [
        "string"
      ]
    }'

Path parameters

  • id string required

Request body

Property Type Description
contact_ids required array<string>

Response200

Result

Property Type Description
object required enum (1)
"remove_result"
removed required integer
not_active required integer Enrolled but already closed (replied, finished, …) or removed: left alone.
not_enrolled required integer
canceled_sends required integer
Example response
{
  "object": "remove_result",
  "removed": 0,
  "not_active": 0,
  "not_enrolled": 0,
  "canceled_sends": 0
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Remove a contact from a campaign

delete /v1/campaigns/{id}/contacts/{contact_id}

Sets an `active` enrollment to `removed` and cancels its queued (`scheduled`) send. A send already being handed to the provider (`sending`) can't be recalled. The enrollment and its sends stay for history; removed leads don't count as replied or finished. Idempotent for an already-removed contact; 409 `enrollment_not_active` when the lead already replied, finished, bounced, unsubscribed or failed.

curl -X DELETE "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/contacts/con_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required
  • contact_id string required

Response200

Removed

Property Type Description
id required string
object required enum (1)
"campaign_contact"
contact_id required string
contact_email required string (email)
current_step required integer
status required string `active`, `replied`, `bounced`, `unsubscribed`, `finished`, `failed`, or `removed` (taken out of the campaign).
next_send_at required string (date-time) | null
assigned_inbox_id required string | null
Example response
{
  "id": "string",
  "object": "campaign_contact",
  "contact_id": "con_01HZ8X3J9K7P2QV6R5N4M3T2L2",
  "contact_email": "alice@example.com",
  "current_step": 0,
  "status": "string",
  "next_send_at": "2026-04-29T10:00:00Z",
  "assigned_inbox_id": "ass_01HZ8X3J9K7P2QV6R5N4M3T2L1"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Duplicate a campaign

post /v1/campaigns/{id}/duplicate

Creates a `draft` copy with the same settings, schedule, sending inboxes/tags and steps (variants included, picked winners cleared). Leads are not copied; enroll them with POST /v1/campaigns/{id}/contacts. The copy is named `<name> (copy)` unless `name` is given.

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/duplicate" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "name": "Alice Liddell"
    }'

Path parameters

  • id string required

Request body

Property Type Description
name string

Response201

Created

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Pause a running campaign

post /v1/campaigns/{id}/pause
curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/pause" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Response200

Paused

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Preview a campaign's emails for a lead

get /v1/campaigns/{id}/preview

Renders every version of every step exactly as the scheduler would for one lead: variables and fallbacks, spintax, the sender's signature, and `Re: <previous subject>` for blank follow-up subjects. `contact_id` must be enrolled in the campaign; without it, variables render empty. The sender is `inbox_id`, else the lead's assigned inbox, else the first active sending inbox.

curl -X GET "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/preview" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Query parameters

  • contact_id string
  • inbox_id string

Response200

OK

Property Type Description
object required enum (1)
"campaign_preview"
campaign_id required string
contact_id required string | null
contact_email required string | null
inbox_id required string | null The sender rendered as (signature, sender variables).
inbox_email required string | null
steps required array<object>
Show properties
Property Type Description
step_id required string
step_order required integer
versions required array<object>
Show properties
Property Type Description
variant_id required string | null Null = the step's own content (no A/B variants).
label required string `A`, `B`, … as the dashboard names versions.
disabled required boolean
picked required boolean The version the scheduler would send this lead.
subject required string
body_html required string
body_text required string
Example response
{
  "object": "campaign_preview",
  "campaign_id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L4",
  "contact_id": "con_01HZ8X3J9K7P2QV6R5N4M3T2L2",
  "contact_email": "string",
  "inbox_id": "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1",
  "inbox_email": "string",
  "steps": [
    {
      "step_id": "ste_01HZ8X3J9K7P2QV6R5N4M3T2L1",
      "step_order": 0,
      "versions": [
        {
          "variant_id": "var_01HZ8X3J9K7P2QV6R5N4M3T2L1",
          "label": "string",
          "disabled": false,
          "picked": false,
          "subject": "Quick question about your stack",
          "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
          "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?"
        }
      ]
    }
  ]
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Resume a paused campaign

post /v1/campaigns/{id}/resume

Same preflight as start (409 `campaign_not_ready`). Resuming after a bounce-protection pause gives the campaign 24 hours and 50 sends before the bounce check looks again, counting only sends since the resume.

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/resume" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Response200

Resumed

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Start a campaign (or preview with ?dry_run=true)

post /v1/campaigns/{id}/start

Transitions `draft` or `paused` → `running`. Refused with 409 `campaign_not_ready` (every problem listed) when the campaign has no steps, the first step has no subject or body, no active sending inbox resolves from `sending_inbox_ids` / `sending_inbox_tags`, or no contact is active. Pass `?dry_run=true` to return the projected schedule (which inboxes, when, to whom — first 10 sends) without committing.

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/start" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Query parameters

  • dry_run string

Response200

Running (or projected schedule under dry_run)

Type: object.

Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Campaign aggregate stats

get /v1/campaigns/{id}/stats
curl -X GET "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/stats" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required

Response200

OK

Property Type Description
object required enum (1)
"campaign_stats"
sent required integer
failed required integer
opened required integer
clicked required integer
replied required integer
bounced required integer
unsubscribed required integer
contacts required object
Show properties
Property Type Description
total required integer
active required integer
replied required integer
bounced required integer
unsubscribed required integer
finished required integer
failed required integer
removed required integer Taken out of the campaign; not part of `total`.
Example response
{
  "object": "campaign_stats",
  "sent": 0,
  "failed": 0,
  "opened": 0,
  "clicked": 0,
  "replied": 0,
  "bounced": 0,
  "unsubscribed": 0,
  "contacts": {
    "total": 0,
    "active": 0,
    "replied": 0,
    "bounced": 0,
    "unsubscribed": 0,
    "finished": 0,
    "failed": 0,
    "removed": 0
  }
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Add a step to a campaign

post /v1/campaigns/{id}/steps

Appends, or inserts at `step_order` (only after the steps leads have already reached). Finished leads don't receive a newly appended step, and a completed campaign stays completed.

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/steps" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "step_order": 0,
      "delay_days": 0,
      "delay_minutes": 0,
      "subject": "Quick question about your stack",
      "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
      "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
      "variants": [
        {
          "id": "string",
          "subject": "Quick question about your stack",
          "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
          "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
          "disabled": false
        }
      ],
      "winning_variant_id": "win_01HZ8X3J9K7P2QV6R5N4M3T2L1"
    }'

Path parameters

  • id string required

Request body

Property Type Description
step_order integer 0-based position. Omit to append; an existing position inserts before that step.
delay_days integer Wait in whole days. Prefer `delay_minutes`.
delay_minutes integer Wait after the previous step's send, in minutes (sends still respect the campaign's schedule window). Takes precedence over `delay_days`.
subject string May be blank (or omitted) on a follow-up step: the email then goes out in the same thread as `Re: <subject of the previous email>`. The first step needs one.
body_html string
body_text string
variants array<SequenceStepVariant>
winning_variant_id string

Response201

Created

Property Type Description
id required string
object required enum (1)
"sequence_step"
step_order required integer
delay_days required integer
delay_minutes required integer Wait after the previous step, in minutes (authoritative over `delay_days`).
subject required string Blank on a follow-up = sent as `Re: <previous subject>` in the same thread.
body_html required string
body_text required string
variants required array<object> | null
winning_variant_id required string | null
Example response
{
  "id": "string",
  "object": "sequence_step",
  "step_order": 0,
  "delay_days": 0,
  "delay_minutes": 0,
  "subject": "Quick question about your stack",
  "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
  "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
  "variants": [
    {
      "id": "string",
      "subject": "Quick question about your stack",
      "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
      "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
      "disabled": false
    }
  ],
  "winning_variant_id": "win_01HZ8X3J9K7P2QV6R5N4M3T2L1"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Reorder a campaign's steps

post /v1/campaigns/{id}/steps/reorder

Pass every step id in the new order. Steps active leads have already reached must keep their position (409 otherwise).

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/steps/reorder" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "step_ids": [
        "string"
      ]
    }'

Path parameters

  • id string required

Request body

Property Type Description
step_ids required array<string> Every step id of the sequence, in the new order. On a campaign, steps active leads have already reached keep their position.

Response200

Reordered

Property Type Description
id required string
object required enum (1)
"campaign"
mode required enum (2)
"live""test"
name required string
status required enum (4)
"draft""running""paused""completed"
sequence_id required string | null
schedule required object
Show properties
Property Type Description
timezone required string
daysOfWeek required array<integer>
startHour required integer
endHour required integer
sending_inbox_ids required array<string>
sending_inbox_tags required array<string> Inbox tags the rotation also sends from: every active inbox carrying one of these tags, in addition to `sending_inbox_ids`. Inboxes tagged later join automatically.
track_opens required boolean
track_clicks required boolean
inject_unsubscribe_footer required boolean
send_status required enum (8) | null Read-only. The scheduler's last verdict on why this campaign is or isn't sending: `sending`, `outside_schedule`, `no_inbox_available` (every sender at its cap or throttled), `waiting_on_followup_delays`, `quota_exceeded`, `not_entitled`, `no_active_leads`, or `bounce_protection`. Null until the scheduler has looked at it.
"sending""outside_schedule""no_inbox_available""waiting_on_followup_delays""quota_exceeded""not_entitled""no_active_leads""bounce_protection"
send_status_at required string (date-time) | null Read-only. When `send_status` last changed.
paused_reason required string | null Read-only. `bounce_protection` when the system paused the campaign because its hard-bounce rate passed 5% (at least 100 sends); null for a manual pause. Cleared on resume.
created_at required string (date-time)
updated_at required string (date-time)
sequence_steps array<SequenceStep>
Example response
{
  "id": "cmp_01HZ8X3J9K7P2QV6R5N4M3T2L7",
  "object": "campaign",
  "mode": "live",
  "name": "Q2 outbound — Tier 1 prospects",
  "status": "running",
  "sequence_id": "seq_01HZ8X3J9K7P2QV6R5N4M3T2L6",
  "schedule": {
    "timezone": "America/New_York",
    "daysOfWeek": [
      1,
      2,
      3,
      4,
      5
    ],
    "startHour": 9,
    "endHour": 17
  },
  "sending_inbox_ids": [
    "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1"
  ],
  "sending_inbox_tags": [
    "outbound"
  ],
  "track_opens": false,
  "track_clicks": true,
  "inject_unsubscribe_footer": true,
  "send_status": "sending",
  "send_status_at": "2026-05-11T14:22:00Z",
  "paused_reason": null,
  "created_at": "2026-05-02T16:30:00Z",
  "updated_at": "2026-05-02T16:35:00Z"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Update a campaign step

patch /v1/campaigns/{id}/steps/{step_id}

Allowed while the campaign runs. Content, delay and variant edits apply to emails rendered after the change; already queued sends keep their content and leads already waiting keep their computed send time. Moving the step (`step_order`) follows the reorder rules.

curl -X PATCH "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/steps/ste_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "step_order": 0,
      "delay_days": 0,
      "delay_minutes": 0,
      "subject": "Quick question about your stack",
      "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
      "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
      "variants": [
        {
          "id": "string",
          "subject": "Quick question about your stack",
          "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
          "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
          "disabled": false
        }
      ],
      "winning_variant_id": "win_01HZ8X3J9K7P2QV6R5N4M3T2L1"
    }'

Path parameters

  • id string required
  • step_id string required

Request body

Property Type Description
step_order integer Move the step to this 0-based position.
delay_days integer Wait in whole days. Prefer `delay_minutes`.
delay_minutes integer Wait after the previous step's send, in minutes (sends still respect the campaign's schedule window). Takes precedence over `delay_days`.
subject string May be blank (or omitted) on a follow-up step: the email then goes out in the same thread as `Re: <subject of the previous email>`. The first step needs one.
body_html string
body_text string
variants array<SequenceStepVariant> | null Replaces the step's variants. Ids are stable: keep an id to edit that variant, drop it to delete the variant (its sends keep their stats). null or [] removes A/B.
winning_variant_id string | null

Response200

Updated

Property Type Description
id required string
object required enum (1)
"sequence_step"
step_order required integer
delay_days required integer
delay_minutes required integer Wait after the previous step, in minutes (authoritative over `delay_days`).
subject required string Blank on a follow-up = sent as `Re: <previous subject>` in the same thread.
body_html required string
body_text required string
variants required array<object> | null
winning_variant_id required string | null
Example response
{
  "id": "string",
  "object": "sequence_step",
  "step_order": 0,
  "delay_days": 0,
  "delay_minutes": 0,
  "subject": "Quick question about your stack",
  "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
  "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
  "variants": [
    {
      "id": "string",
      "subject": "Quick question about your stack",
      "body_html": "<p>Hi {{first_name}}, would love to chat — does Tuesday work?</p>",
      "body_text": "Hi {{first_name}}, would love to chat — does Tuesday work?",
      "disabled": false
    }
  ],
  "winning_variant_id": "win_01HZ8X3J9K7P2QV6R5N4M3T2L1"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Delete a campaign step

delete /v1/campaigns/{id}/steps/{step_id}

Refused with 409 once active leads have reached the step. Sends of the step keep their content and stats.

curl -X DELETE "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/steps/ste_01HZ8X3J9K7P2QV6R5N4M3T2L1" \
  -H "Authorization: Bearer $CLEMENT_API_KEY"

Path parameters

  • id string required
  • step_id string required

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.

Send a test email of one campaign step

post /v1/campaigns/{id}/test_send

Sends one rendered step (optionally one variant, rendered for `contact_id`) from one of the campaign's sending inboxes to a member of the workspace. Live campaigns only. It's an ad-hoc send: it never moves a lead or shows in campaign stats, but counts against the monthly sends quota. 20 per member per hour, shared with the dashboard.

curl -X POST "https://api.clement.so/v1/campaigns/obj_01HZ8X3J9K7P2QV6R5N4M3T2L1/test_send" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
      "step_id": "ste_01HZ8X3J9K7P2QV6R5N4M3T2L1",
      "to": "alice@example.com",
      "variant_id": "var_01HZ8X3J9K7P2QV6R5N4M3T2L1",
      "inbox_id": "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1",
      "contact_id": "con_01HZ8X3J9K7P2QV6R5N4M3T2L2"
    }'

Path parameters

  • id string required

Request body

Property Type Description
step_id required string
variant_id string Required when the step has variants (ids from the step's `variants`).
to required string (email) A workspace member's email address.
inbox_id string Defaults to the first active sending inbox.
contact_id string Render with this enrolled lead's variables.

Response201

Queued

Property Type Description
object required enum (1)
"campaign_test_send"
send_id required string
to required string
inbox_id required string
subject required string
Example response
{
  "object": "campaign_test_send",
  "send_id": "snd_01HZ8X3J9K7P2QV6R5N4M3T2L8",
  "to": "string",
  "inbox_id": "inb_01HZ8X3J9K7P2QV6R5N4M3T2L1",
  "subject": "Quick question about your stack"
}

Errors

  • 400 Invalid request
  • 401 Authentication failed
  • 403 Insufficient scope
  • 404 Not found
  • 409 Conflict
  • 422 Validation failed
  • 429 Rate limited

See Errors for the full envelope and per-code reference.