Kenso

Campaigns

Create and update campaigns, launch, pause, resume, archive and duplicate them, assign their inboxes and LinkedIn accounts, and run scoring and enrichment.

Campaigns and sequences31 endpointsKen-Version: 2026-11-02AuthenticationWorkspacesErrors

Includes archived campaigns and excludes approval-test drafts. Optional status is a comma-separated OR filter. q is a case-insensitive substring of the name, or of the owner's first name, last name, full name, or email. Optional kind is sales or recruiting. Each item carries leadCount, from one cached count of the workspace. It usually lags new or removed leads by under 15 minutes; on a very large workspace the first list after a quiet period can show an older count, which the next list corrects. A campaign that count has not seen is counted live, and a single-campaign read (GET /v1/campaigns/{campaignId}) counts live for a current number. leadCount is omitted when that count does not finish in time on a very large workspace. Ordered by id. Read-only. Requires ReadAccess.

Parameters

Name
In
Type
Required
Description
Status
In: query
string
optional
Q
In: query
string
optional
Limit
In: query
integer (int32)
optional
Cursor
In: query
string
optional
Kind
In: query
string
optional
Sales or recruiting; omit for both.
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a list of CampaignModel. Each item has these fields. The response also carries pagination.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X GET "https://api.ken.so/v1/campaigns" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": [
    {
      "id": 4821,
      "name": "Q4 SaaS founders",
      "status": "active",
      "channel": "email",
      "contactLimit": 2500,
      "contactLimitPeriod": "total",
      "contactLimitUsed": 1840,
      "contactLimitReached": false,
      "contactCount": 1840,
      "leadCount": 2115,
      "segmentCount": 2,
      "inboxCount": 6,
      "createdAt": "2026-09-23T15:02:11Z",
      "launchedAt": "2026-09-29T13:00:00Z",
      "sourceStatus": "done",
      "sendingStatus": "sending",
      "processingStatus": [
        "finished"
      ],
      "sendingDays": [
        1,
        2,
        3,
        4,
        5
      ],
      "sendingStartTime": "09:00",
      "sendingEndTime": "17:00",
      "sendingTimezone": "America/New_York",
      "maxEmailsPerDay": 300,
      "ownerMemberId": 1093,
      "stopOnReply": true,
      "stopCompanyOnReply": true,
      "groupSameCompanySends": true,
      "newLeadsFirst": false,
      "sourceLanguage": "en",
      "languageMode": "source_only",
      "enabledLanguages": [],
      "formalityOverrides": {},
      "languageFallback": "source",
      "rampUpDays": 5,
      "trackClicks": false,
      "trackOpens": false,
      "unsubscribeLink": true,
      "protectAccountPlays": true,
      "excludedCountries": [],
      "kind": "sales",
      "mode": "batch",
      "autoLaunchWhenReady": false,
      "approvalsWaiting": 0,
      "forecast": {
        "plannedSendCount": 3680,
        "sentCount": 2208,
        "remainingSendCount": 1472,
        "progress": 0.6,
        "estimatedFinishAt": "2026-10-20T20:40:00Z",
        "effectiveDailyCapacity": 300,
        "calculatedAt": "2026-10-14T09:35:00Z"
      }
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

Returns 201 and Location. Requires ReadAccess, matching the legacy create. Reserves the campaignCreate abuse quota. A sales campaign starts with stopCompanyOnReply on. Optional kind is sales (default) or recruiting. A recruiting campaign needs the recruiting flag for the workspace, else 400 validation_failed with detail code feature_disabled; it starts with stopCompanyOnReply, groupSameCompanySends and same-company Cc off, and as a Draft, so its AI build can start. Optional roleId names a live role of the workspace (else 404 not_found) and is only for a recruiting campaign (else 400 with detail code not_recruiting).

Parameters

Name
In
Type
Required
Description
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
name
string
optional
contactLimit
integer (int32)
optional
kind
string
optional
sales (default) or recruiting. A recruiting campaign needs the recruiting flag.
roleId
integer (int64)
optional
The primary role of a recruiting campaign: a live role of this workspace.

Response 201

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X POST "https://api.ken.so/v1/campaigns" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Q4 SaaS founders",
  "contactLimit": 2500,
  "kind": "sales"
}'
Response 201
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

A missing or cross-workspace id is not_found. Read-only. Requires ReadAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Absent fields stay unchanged. status is rejected. Unknown JSON members are a 400. A schedule or daily-cap change writes a planner request row (nothing consumes it since DEV-1821; gap-fill picks up the change on its next pass); rampUpDays, stopCompanyOnReply and groupSameCompanySends do not, as in the legacy update. rampUpDays is 0 (no ramp) to 30 scheduled sending days, else 400 validation_failed. stopCompanyOnReply is the same stored value as stopSequenceForCompanyOnReply in the LinkedIn settings; it is written after the other fields. ownerMemberId must be an active member of the workspace, else 400 validation_failed with detail code not_a_member. Changing stopOnReply, trackClicks, trackOpens, or unsubscribeLink needs the campaign_send_settings flag for the workspace, else 400 validation_failed with detail code feature_disabled per field and nothing is written. Sending the stored value is accepted without the flag. protectAccountPlays turns skipping people in active account plays on or off for this campaign; people already stopped stay stopped. Requires WriteAccess. newLeadsFirst true gives first emails the campaign's daily capacity ahead of its follow-ups; it takes effect on the next gap-fill pass. While it is true the response carries awaitingFirstEmailCount. bookingMeetingTypeId names a live, enabled round robin meeting type of this workspace that takes this campaign's bookings when the account owner and the sender can't; null clears it. kind cannot be changed: any value is 400 validation_failed with detail code kind_immutable. roleId names a live role of this workspace for a recruiting campaign and needs the recruiting flag; null clears it. On a sales campaign it is 400 with detail code not_recruiting. excludedCountries replaces the countries this campaign never sends to: ISO 3166-1 alpha-2 codes or country names, stored as upper-case codes; [] clears it. An entry that names no country is 400 validation_failed with detail code invalid_country and nothing is written. Imports drop a contact whose country is listed and the sequencer skips one already on the campaign, from its next pass. The language settings (sourceLanguage, languageModesource_only or auto, enabledLanguages, formalityOverrides locale to formal or informal, and languageFallbacksource or hold) need the multilingual_outreach flag for the workspace, else 400 validation_failed with detail code feature_disabled per field sent, even for the stored value. A locale must be a market pack that is not a draft and sourceLanguage may also be a language with a primary pack (en), else detail code unknown_language or draft_language; any other bad value is invalid_value. null, [] or {} clears enabledLanguages or formalityOverrides. mode is batch or trigger, else 400 validation_failed. Batch to trigger converts the campaign to signal-based sending; trigger to batch is 400 validation_failed with detail code mode_one_way and nothing is written. autoLaunchWhenReady true launches the campaign once it is ready, with no further confirmation; changing it needs the launch permission (403 forbidden), is 400 once the campaign launched or for a trigger or approval-test campaign, and 409 conflict once auto-launch has run. modetrigger on a batch campaign is 400 validation_failed on autoLaunchWhenReady, and nothing is written, when it comes with autoLaunchWhenReady true, or without it while the campaign is set to auto-launch: send false in the same request to turn that off.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
name
string
optional
contactLimit
integer (int32)
optional
status
string
optional
Present only so a caller who sends status is rejected. Lifecycle changes are actions.
sendingDays
array of integer (int32)
optional
0 = Sunday through 6 = Saturday. Omit to leave the stored days unchanged.
sendingStartTime
string
optional
sendingEndTime
string
optional
sendingTimezone
string
optional
maxEmailsPerDay
integer (int32)
optional
ownerMemberId
integer (int64)
optional
New campaign owner: the id of an active member of this workspace. Omit (or send null) to leave the owner unchanged. The owner cannot be cleared.
stopOnReply
boolean
optional
trackClicks
boolean
optional
trackOpens
boolean
optional
unsubscribeLink
boolean
optional
protectAccountPlays
boolean
optional
Skip people in active account plays. Omit to leave it unchanged.
rampUpDays
integer (int32)
optional
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. Omit to leave it unchanged.
stopCompanyOnReply
boolean
optional
A reply also stops every other contact at the lead's company. The same stored value as stopSequenceForCompanyOnReply in the LinkedIn settings. Omit to leave it unchanged.
groupSameCompanySends
boolean
optional
Coworkers at one company share a send day. Omit to leave it unchanged.
newLeadsFirst
boolean
optional
First emails take the campaign's capacity ahead of its follow-ups. Omit to leave it unchanged.
bookingMeetingTypeId
integer (int64)
optional
replyTo
string
optional
excludedCountries
array of string
optional
The countries this campaign never sends to, as ISO 3166-1 alpha-2 codes or country names ("DE", "Germany"). Replaces the stored list; an empty list clears it. Omit (or send null) to leave it unchanged.
kind
string
optional
roleId
integer (int64)
optional
sourceLanguage
string
optional
A market pack locale (fr-FR) or a language with a primary pack (en).
languageMode
string
optional
source_only or auto.
enabledLanguages
array of string
optional
formalityOverrides
map of string
optional
languageFallback
string
optional
source or hold.
mode
string
optional
batch or trigger. Batch to trigger converts the campaign; trigger to batch is refused (mode_one_way). Omit to leave it unchanged.
autoLaunchWhenReady
boolean
optional
Launch automatically once the campaign is ready. Needs the launch permission; refused once the campaign launched or auto-launch already ran. Omit to leave it unchanged.

Response 200

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X PATCH "https://api.ken.so/v1/campaigns/812" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "contactLimit": 1,
  "status": "string",
  "sendingDays": [
    1
  ],
  "sendingStartTime": "string",
  "sendingEndTime": "string",
  "sendingTimezone": "string",
  "maxEmailsPerDay": 1,
  "ownerMemberId": 812,
  "stopOnReply": true,
  "trackClicks": true,
  "trackOpens": true,
  "unsubscribeLink": true,
  "protectAccountPlays": true,
  "rampUpDays": 1,
  "stopCompanyOnReply": true,
  "groupSameCompanySends": true,
  "newLeadsFirst": true,
  "bookingMeetingTypeId": 812,
  "replyTo": "string",
  "excludedCountries": [
    "string"
  ],
  "kind": "string",
  "roleId": 812,
  "sourceLanguage": "string",
  "languageMode": "string",
  "enabledLanguages": [
    "string"
  ],
  "formalityOverrides": {
    "key": "string"
  },
  "languageFallback": "string",
  "mode": "string",
  "autoLaunchWhenReady": true
}'
Response 200
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Soft-delete a campaign

DELETE/v1/campaigns/{campaignId}campaigns.delete

Try it on developer.ken.so

Requires ReadAccess, matching the legacy delete. Returns 204.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 204

Request
curl -X DELETE "https://api.ken.so/v1/campaigns/812" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 204
HTTP/1.1 204 No Content

Launch sending for a campaign

POST/v1/campaigns/{campaignId}/launchcampaigns.launch

Try it on developer.ken.so

Requires WriteAccess. Returns the campaign after the launch command confirms. While the workspace's data is scheduled for deletion or being deleted, returns 409 invalid_state.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/launch" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Archive a campaign

POST/v1/campaigns/{campaignId}/archivecampaigns.archive

Try it on developer.ken.so

Idempotent for an already-archived campaign. A campaign that is still sending returns 409 invalid_state. Requires WriteAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/archive" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Duplicate a campaign inside the current workspace

POST/v1/campaigns/{campaignId}/duplicatecampaigns.duplicate

Try it on developer.ken.so

includeContacts copies contacts. The copy stays in this workspace. Returns 201 and Location. Requires WriteAccess. Reserves the campaign-duplicate abuse quota.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
name
string
optional
includeContacts
boolean
optional

Response 201

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/duplicate" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string",
  "includeContacts": true
}'
Response 201
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Idempotent. A campaign that is already paused returns 200 with the unchanged resource. A subsystem that is not running is left as it is. If any subsystem fails, the action returns that error and the caller can retry. A campaign that is still launching returns 409 invalid_state. Requires WriteAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/pause" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Resume sending, processing, and AI workflows together

POST/v1/campaigns/{campaignId}/resumecampaigns.resume

Try it on developer.ken.so

Idempotent. A campaign that is already running returns 200 with the resource. A subsystem that was not paused is left as it is. If any subsystem fails, the action returns that error and the caller can retry. A campaign that is still launching returns 409 invalid_state, and so does every campaign while the workspace's data is scheduled for deletion or being deleted. Requires WriteAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/resume" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Report whether a campaign can launch and how long sending should take

GET/v1/campaigns/{campaignId}/launch-readinesscampaigns.get-launch-readiness

Try it on developer.ken.so

Replaces GET /v1/campaigns/{id}/can-launch and GET /v1/campaigns/{id}/sending-estimate. A campaign outside the workspace is not_found before either read. Requires ReadAccess. warnings never change canLaunch; localization_incomplete (one per enabled language of an auto campaign) names the steps with no approved translation and whether those contacts get the source text or are held. approval_required means the client was asked and launch answers 409 approval_required until they approve; its kinds lists what waits (copy, lead_list, ...). The warning approval_will_be_requested names what nobody asked about yet: launch asks the client, then refuses until they approve.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a LaunchReadinessModel.

Field
Type
Description
canLaunch
boolean
reasons
array of LaunchReadinessReasonModel
warnings
array of LaunchReadinessReasonModel
Does not affect Ken.Scraper.API.Canonical.LaunchReadinessModel.CanLaunch.
contactLimit
integer (int32)
The sending estimate counts are null when the estimate cannot be computed (see the sending_estimate_unavailable warning).
contactLimitPeriod
string
month when Ken.Scraper.API.Canonical.LaunchReadinessModel.ContactLimit and the counts below cover one month, else total.
emailStepCount
integer (int32)
totalEmailCount
integer (int32)
dailySendingLimit
integer (int32)
estimatedDayCount
integer (int32)
forecast
LaunchForecastModel
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/launch-readiness" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "canLaunch": true,
    "reasons": [
      {
        "code": "string",
        "message": "string",
        "fixUrl": "https://example.com",
        "kinds": [
          "string"
        ]
      }
    ],
    "warnings": [
      {
        "code": "string",
        "message": "string",
        "fixUrl": "https://example.com",
        "kinds": [
          "string"
        ]
      }
    ],
    "contactLimit": 1,
    "contactLimitPeriod": "string",
    "emailStepCount": 1,
    "totalEmailCount": 1,
    "dailySendingLimit": 1,
    "estimatedDayCount": 1,
    "forecast": {
      "status": "string",
      "progress": 0.5,
      "estimatedFinishAt": "2026-09-23T15:02:11Z",
      "confidence": "string",
      "reasonCodes": [
        "string"
      ],
      "statusExplanation": "string",
      "remainingEmailCount": 1,
      "effectiveDailyCapacity": 1,
      "scheduledNext3DayCount": 1,
      "effectiveScheduledNext3DayCount": 1,
      "projectedNext3DayCount": 1,
      "effectiveProjectedNext3DayCount": 1,
      "readyContactCount": 1,
      "assignedTodayCount": 1,
      "acceptedTodayCount": 1,
      "calculatedAt": "2026-09-23T15:02:11Z",
      "isStale": true
    }
  }
}

Report whether a campaign's workflows can start

GET/v1/campaigns/{campaignId}/workflow-readinesscampaigns.get-workflow-readiness

Try it on developer.ken.so

Replaces startWorkflowReadiness on the legacy GET /v1/campaigns/{id}. Runs the same checks that gate starting enrichment, verification, and AI workflows (imported contacts or an active signal, contact limit, name, schedule, email sequence, AI variables) and returns each one with isPassed. Sending launch readiness is GET .../launch-readiness. A campaign outside the workspace is not_found. Read-only. Requires ReadAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a WorkflowReadinessModel.

Field
Type
Description
canStart
boolean
True when every check passed.
checks
array of WorkflowReadinessCheckModel
Every check that applies to the campaign, passed or not.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/workflow-readiness" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "canStart": true,
    "checks": [
      {
        "code": "string",
        "message": "string",
        "isPassed": true
      }
    ]
  }
}

Count a campaign's contacts by email security gateway and mailbox host

GET/v1/campaigns/{campaignId}/mail-providerscampaigns.get-mail-providers

Needs the security_gateways feature on your workspace. Until it is on, calls answer 404.

Try it on developer.ken.so

Each live contact is judged by its first Safe or Verified email, the one the lead list shows. gateways lists every active security gateway and mailboxProviders every active mailbox host and forwarder, 0 included, most contacts first. pendingCount counts contacts whose domain is not checked for a gateway yet. Counts cover at most 100,000 contacts (isPartial). Read-only. A campaign outside the workspace, or a workspace without the security_gateways feature, is not_found. Requires ReadAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignMailProvidersModel.

Field
Type
Description
gateways
array of MailProviderCountModel
Every active gateway in the catalog, 0 included, most contacts first.
mailboxProviders
array of MailProviderCountModel
Every active mailbox host and forwarder in the catalog, 0 included, most contacts first.
pendingCount
integer (int32)
Contacts whose email domain is not checked for a gateway yet.
isPartial
boolean
True when the campaign has more contacts than one read scans (100,000), so the counts are a floor.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/mail-providers" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "gateways": [
      {
        "slug": "string",
        "name": "string",
        "count": 1
      }
    ],
    "mailboxProviders": [
      {
        "slug": "string",
        "name": "string",
        "count": 1
      }
    ],
    "pendingCount": 1,
    "isPartial": true
  }
}

Return one campaign's weekly report

GET/v1/campaigns/{campaignId}/weekly-reportcampaigns.get-weekly-report

Try it on developer.ken.so

weekOf is any date (YYYY-MM-DD, from 2000-01-01 to one year from today) in the wanted Monday-to-Sunday week. timeZone is an IANA name; without it the campaign's sending time zone is used, else UTC. The week's dates are picked in that time zone; its numbers and changed are counted on those UTC days, like the Analytics tab. Without weekOf the report covers the latest week that has ended both there and in UTC. week and toDate are email counts from campaign performance. toDate and segments run from the campaign's creation (at most 366 days back) to the end of the week. changed and next are sentences built from data, not AI. next is as of the request, in the campaign's sending time zone, and is empty for a week older than the default week. A missing or cross-workspace campaign is not_found. Read-only. Requires ReadAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
weekOf
In: query
string
optional
timeZone
In: query
string
optional
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignWeeklyReportModel.

Field
Type
Description
campaignId
integer (int64)
campaignName
string
timeZone
string
IANA zone the week's dates are picked in, or UTC.
week
CampaignWeeklyReportMetricsModel
toDate
CampaignWeeklyReportMetricsModel
segments
array of CampaignWeeklyReportSegmentModel
Per segment, to date.
changed
array of CampaignWeeklyReportChangeModel
What changed during the week, newest first, at most 6.
next
array of CampaignWeeklyReportNextStepModel
What happens next, as of the request. Empty for a week older than the default week.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/weekly-report" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "campaignId": 812,
    "campaignName": "string",
    "timeZone": "string",
    "week": {
      "startDate": "string",
      "endDate": "string",
      "sentCount": 1,
      "contactedCount": 1,
      "replyCount": 1,
      "interestedCount": 1,
      "bounceCount": 1
    },
    "toDate": {
      "startDate": "string",
      "endDate": "string",
      "sentCount": 1,
      "contactedCount": 1,
      "replyCount": 1,
      "interestedCount": 1,
      "bounceCount": 1
    },
    "segments": [
      {
        "segmentId": 812,
        "name": "string",
        "contactCount": 1,
        "sentCount": 1,
        "replyRate": 0.5
      }
    ],
    "changed": [
      {
        "kind": "string",
        "occurredAt": "2026-09-23T15:02:11Z",
        "segmentId": 812,
        "text": "string"
      }
    ],
    "next": [
      {
        "kind": "string",
        "segmentId": 812,
        "remainingCount": 1,
        "finishDate": "string",
        "text": "string"
      }
    ]
  }
}

List inboxes assigned to a campaign

GET/v1/campaigns/{campaignId}/inboxescampaigns.list-inboxes

Try it on developer.ken.so

Requires ReadAccess. A campaign outside the workspace is not_found.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a list of InboxModel. Each item has these fields. The response also carries pagination.

Field
Type
Description
id
integer (int64)
email
string
firstName
string
lastName
string
displayName
string
signature
string
status
string
role
string
sending (a campaign inbox), replies: the workspace's shared replies@ inbox, the address campaign replies go to, or retired: a campaign inbox staff retired to receive-only. A replies or retired inbox only receives: it never joins a campaign pool or warmup, and Kenso keeps reading its mail.
captureUntil
string (date-time)
When a retired inbox's reply capture window ends, UTC. Nothing happens automatically then: Kenso keeps reading the inbox until staff delete it. Omitted unless the inbox is retired, and on availableForCampaignId lists.
provider
string
domainId
integer (int64)
dailySendLimit
integer (int32)
forwarding
string
forwardingDestination
string
profileImageUrl
string
The mailbox photo to show: the photo Kenso set on the mailbox, else the photo InboxKit reports for it. LinkedIn CDN URLs are dropped. Omitted when there is none, and on availableForCampaignId lists.
profilePhotoSupport
string
Whether Kenso can set this mailbox's sender photo: google (a Kenso-managed Google mailbox), microsoft (a Kenso-managed Microsoft or Azure mailbox), google_account or microsoft_account (a member's own mailbox connected through Kenso sign-in, whose photo only its owner can change) or none. Setting a photo also needs the workspace's inbox_photos flag. Omitted on availableForCampaignId lists.
profilePhotoStatus
string
Whether the photo Kenso set on the mailbox landed: pending (InboxKit queued it for a Microsoft or Azure mailbox and has not applied it yet), applied or failed (InboxKit did not apply it: no photo shows, and Ken.Scraper.API.Canonical.InboxModel.ProfilePhotoError says why). Omitted for photos set before this tracking, when Kenso set none, and on availableForCampaignId lists.
profilePhotoStatusAt
string (date-time)
When Ken.Scraper.API.Canonical.InboxModel.ProfilePhotoStatus was last set, UTC: for pending, when InboxKit took the photo.
profilePhotoError
string
Why the photo failed, only with Ken.Scraper.API.Canonical.InboxModel.ProfilePhotoStatusfailed.
tagIds
array of integer (int64)
createdAt
string (date-time)
updatedAt
string (date-time)
pausedAt
string (date-time)
When a member paused outreach from this inbox, UTC. Null when it is not paused. A paused inbox holds campaign steps, triggers and approved Accounts 1:1 email; warmup and reply sync keep running. Independent of Ken.Scraper.API.Canonical.InboxModel.Status.
isKenSendable
boolean
sendingProvider
string
The sending provider slug (resend, agentmail, ...) when the inbox is an identity on a sending provider connection, which sends through that provider's API. Omitted for every other inbox.
isIngestReady
boolean
ownerMemberId
integer (int64)
The workspace member whose own mailbox this is. Omitted for a shared campaign inbox. An owned inbox never joins a campaign pool or warmup.
isPrimary
boolean
True when the inbox is owned by a member and its address sits on one of the workspace's domains: its email domain or any domain listed under Settings > Company. Derived on read, so it follows a later change to that list. Only a primary inbox sends the member's 1:1 email.
connectionApp
string
Which Kenso OAuth app the Google or Microsoft connection was granted to: ken_mail (campaign mailboxes) or ken_workspace (a member's own mailbox). Omitted for SMTP and unconnected inboxes.
trackOpens
boolean
The owner's Track opens option. Off by default. When on, replies the member sends from this mailbox in the Inbox carry one open pixel; their 1:1 Accounts email never does. Present only on a member's own mailbox (Ken.Scraper.API.Canonical.InboxModel.OwnerMemberId set); omitted for a shared campaign inbox.
usedBy
UsedByModel
What an inbox or LinkedIn account is used for: the live campaigns that send through it, and whether the 1:1 Accounts lane does.
warmup
InboxWarmupSummaryModel
sendBlock
InboxSendBlockModel
healthScore
integer (int32)
The Kenso Health Score, 0-100, from the latest InboxKit health snapshot. Omitted when InboxKit has reported no health for the inbox, and on availableForCampaignId lists.
tenantHost
string
The Microsoft 365 tenant of a Microsoft or Azure inbox, <tenant>.onmicrosoft.com, read hourly from its domain's public DKIM record. Inboxes that share it share one tenant, the unit a Microsoft tenant ban hits. Staff only (super_admin): omitted for every other caller, and omitted until it is resolved.
lastSentAt
string (date-time)
The inbox's last send the provider accepted, UTC. Warmup sends do not count. Omitted when it sent nothing in the last 30 days, or when that read failed.
connection
InboxConnectionModel
Health of Kenso's own connection to a mailbox, served on inbox.connection.
health
InboxHealthModel
health on GET /v1/inboxes/{inboxId}. Mapped by M:Ken.Scraper.API.Canonical.InboxMapper.ApplyHealth(Ken.Scraper.API.Canonical.InboxModel,Ken.Scraping.Application.Queries.EmailOrchestrator.InboxHealthDto).
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/inboxes" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": [
    {
      "id": 5530,
      "email": "[email protected]",
      "firstName": "Alex",
      "lastName": "Rivera",
      "displayName": "Alex Rivera",
      "signature": "Alex Rivera\nNorthwind Outbound",
      "status": "active",
      "role": "sending",
      "provider": "google",
      "domainId": 640,
      "dailySendLimit": 30,
      "profilePhotoSupport": "google",
      "tagIds": [],
      "createdAt": "2026-08-31T10:15:00Z",
      "updatedAt": "2026-10-14T09:41:27Z",
      "isPrimary": false,
      "usedBy": {
        "campaigns": [
          {
            "id": 4821,
            "name": "Q4 SaaS founders",
            "status": "active"
          }
        ],
        "accounts": false
      },
      "warmup": {
        "daysLeft": 0
      },
      "healthScore": 94,
      "lastSentAt": "2026-10-14T09:12:40Z"
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

Replace the inboxes assigned to a campaign

PUT/v1/campaigns/{campaignId}/inboxescampaigns.replace-inboxes

Try it on developer.ken.so

Full replace. Send inboxIds, or assignAllActive to assign every Kenso-sendable inbox. Either one fixes the pool to that list. allInboxes: true turns on live "All inboxes" instead: every inbox a launch would assign (paused inboxes the campaign lacks and blocked Migma senders left out) joins now and inboxes connected later join too; it ignores the other fields and needs the sender_routing flag for the workspace, else 400 validation_failed with detail code feature_disabled. Requires WriteAccess. A campaign outside the workspace is not_found before the write.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
inboxIds
array of integer (int64)
optional
assignAllActive
boolean
optional
provider
string
optional
allInboxes
boolean
optional
True turns on live "All inboxes": every inbox a launch would assign joins now, inboxes connected later join too, and inboxIds, assignAllActive and provider are ignored. Needs the workspace's sender_routing flag, else 400 validation_failed with detail code feature_disabled. Absent or false keeps a saved list: any save without it fixes the pool to the inboxes sent.

Response 200

data is a list of InboxModel. Each item has these fields. The response also carries pagination.

Field
Type
Description
id
integer (int64)
email
string
firstName
string
lastName
string
displayName
string
signature
string
status
string
role
string
sending (a campaign inbox), replies: the workspace's shared replies@ inbox, the address campaign replies go to, or retired: a campaign inbox staff retired to receive-only. A replies or retired inbox only receives: it never joins a campaign pool or warmup, and Kenso keeps reading its mail.
captureUntil
string (date-time)
When a retired inbox's reply capture window ends, UTC. Nothing happens automatically then: Kenso keeps reading the inbox until staff delete it. Omitted unless the inbox is retired, and on availableForCampaignId lists.
provider
string
domainId
integer (int64)
dailySendLimit
integer (int32)
forwarding
string
forwardingDestination
string
profileImageUrl
string
The mailbox photo to show: the photo Kenso set on the mailbox, else the photo InboxKit reports for it. LinkedIn CDN URLs are dropped. Omitted when there is none, and on availableForCampaignId lists.
profilePhotoSupport
string
Whether Kenso can set this mailbox's sender photo: google (a Kenso-managed Google mailbox), microsoft (a Kenso-managed Microsoft or Azure mailbox), google_account or microsoft_account (a member's own mailbox connected through Kenso sign-in, whose photo only its owner can change) or none. Setting a photo also needs the workspace's inbox_photos flag. Omitted on availableForCampaignId lists.
profilePhotoStatus
string
Whether the photo Kenso set on the mailbox landed: pending (InboxKit queued it for a Microsoft or Azure mailbox and has not applied it yet), applied or failed (InboxKit did not apply it: no photo shows, and Ken.Scraper.API.Canonical.InboxModel.ProfilePhotoError says why). Omitted for photos set before this tracking, when Kenso set none, and on availableForCampaignId lists.
profilePhotoStatusAt
string (date-time)
When Ken.Scraper.API.Canonical.InboxModel.ProfilePhotoStatus was last set, UTC: for pending, when InboxKit took the photo.
profilePhotoError
string
Why the photo failed, only with Ken.Scraper.API.Canonical.InboxModel.ProfilePhotoStatusfailed.
tagIds
array of integer (int64)
createdAt
string (date-time)
updatedAt
string (date-time)
pausedAt
string (date-time)
When a member paused outreach from this inbox, UTC. Null when it is not paused. A paused inbox holds campaign steps, triggers and approved Accounts 1:1 email; warmup and reply sync keep running. Independent of Ken.Scraper.API.Canonical.InboxModel.Status.
isKenSendable
boolean
sendingProvider
string
The sending provider slug (resend, agentmail, ...) when the inbox is an identity on a sending provider connection, which sends through that provider's API. Omitted for every other inbox.
isIngestReady
boolean
ownerMemberId
integer (int64)
The workspace member whose own mailbox this is. Omitted for a shared campaign inbox. An owned inbox never joins a campaign pool or warmup.
isPrimary
boolean
True when the inbox is owned by a member and its address sits on one of the workspace's domains: its email domain or any domain listed under Settings > Company. Derived on read, so it follows a later change to that list. Only a primary inbox sends the member's 1:1 email.
connectionApp
string
Which Kenso OAuth app the Google or Microsoft connection was granted to: ken_mail (campaign mailboxes) or ken_workspace (a member's own mailbox). Omitted for SMTP and unconnected inboxes.
trackOpens
boolean
The owner's Track opens option. Off by default. When on, replies the member sends from this mailbox in the Inbox carry one open pixel; their 1:1 Accounts email never does. Present only on a member's own mailbox (Ken.Scraper.API.Canonical.InboxModel.OwnerMemberId set); omitted for a shared campaign inbox.
usedBy
UsedByModel
What an inbox or LinkedIn account is used for: the live campaigns that send through it, and whether the 1:1 Accounts lane does.
warmup
InboxWarmupSummaryModel
sendBlock
InboxSendBlockModel
healthScore
integer (int32)
The Kenso Health Score, 0-100, from the latest InboxKit health snapshot. Omitted when InboxKit has reported no health for the inbox, and on availableForCampaignId lists.
tenantHost
string
The Microsoft 365 tenant of a Microsoft or Azure inbox, <tenant>.onmicrosoft.com, read hourly from its domain's public DKIM record. Inboxes that share it share one tenant, the unit a Microsoft tenant ban hits. Staff only (super_admin): omitted for every other caller, and omitted until it is resolved.
lastSentAt
string (date-time)
The inbox's last send the provider accepted, UTC. Warmup sends do not count. Omitted when it sent nothing in the last 30 days, or when that read failed.
connection
InboxConnectionModel
Health of Kenso's own connection to a mailbox, served on inbox.connection.
health
InboxHealthModel
health on GET /v1/inboxes/{inboxId}. Mapped by M:Ken.Scraper.API.Canonical.InboxMapper.ApplyHealth(Ken.Scraper.API.Canonical.InboxModel,Ken.Scraping.Application.Queries.EmailOrchestrator.InboxHealthDto).
Request
curl -X PUT "https://api.ken.so/v1/campaigns/812/inboxes" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "inboxIds": [
    812
  ],
  "assignAllActive": true,
  "provider": "string",
  "allInboxes": true
}'
Response 200
{
  "success": true,
  "data": [
    {
      "id": 5530,
      "email": "[email protected]",
      "firstName": "Alex",
      "lastName": "Rivera",
      "displayName": "Alex Rivera",
      "signature": "Alex Rivera\nNorthwind Outbound",
      "status": "active",
      "role": "sending",
      "provider": "google",
      "domainId": 640,
      "dailySendLimit": 30,
      "profilePhotoSupport": "google",
      "tagIds": [],
      "createdAt": "2026-08-31T10:15:00Z",
      "updatedAt": "2026-10-14T09:41:27Z",
      "isPrimary": false,
      "usedBy": {
        "campaigns": [
          {
            "id": 4821,
            "name": "Q4 SaaS founders",
            "status": "active"
          }
        ],
        "accounts": false
      },
      "warmup": {
        "daysLeft": 0
      },
      "healthScore": 94,
      "lastSentAt": "2026-10-14T09:12:40Z"
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

List LinkedIn accounts assigned to a campaign

GET/v1/campaigns/{campaignId}/linkedin-accountscampaigns.list-linkedin-accounts

Try it on developer.ken.so

Requires ReadAccess. A campaign outside the workspace is not_found.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a list of LinkedInAccountModel. Each item has these fields. The response also carries pagination.

Field
Type
Description
id
integer (int64)
displayName
string
publicIdentifier
string
profileUrl
string
profileImageUrl
string
headline
string
status
string
timezone
string
workingHours
string
dailyInviteCap
integer (int32)
dailyMessageCap
integer (int32)
dailyVisitCap
integer (int32)
createdAt
string (date-time)
createdDatetime
string (date-time)
updatedAt
string (date-time)
unipileAccountId
string
statusChangedAt
string (date-time)
invitesToday
integer (int32)
Invites sent today, on the seat's own day (the trailing 24 hours when rolling seat budgets are on). Compare with Ken.Scraper.API.Canonical.LinkedInAccountModel.DailyInviteCap. Omitted when the counters could not be read.
messagesToday
integer (int32)
Messages and InMails sent today, on the same day as Ken.Scraper.API.Canonical.LinkedInAccountModel.InvitesToday.
visitsToday
integer (int32)
Profile visits today, on the same day as Ken.Scraper.API.Canonical.LinkedInAccountModel.InvitesToday.
invitesSent30d
integer (int32)
Invites sent in the last 30 days (by invite time). Omitted when it could not be read.
acceptRate
number (double)
Share of Ken.Scraper.API.Canonical.LinkedInAccountModel.InvitesSent30d that were accepted, a fraction from 0 to 1. Omitted when no invite was sent in the window.
usedBy
UsedByModel
What an inbox or LinkedIn account is used for: the live campaigns that send through it, and whether the 1:1 Accounts lane does.
lastActionAt
string (date-time)
When the seat last completed an outbound action: the newest confirmed campaign step or Accounts-lane touch . A person's replies from the Inbox do not count. Omitted when the seat has none or it could not be read.
lastActionType
string
The action at Ken.Scraper.API.Canonical.LinkedInAccountModel.LastActionAt: visit, invite, message, inmail, withdraw, follow, reaction, comment or endorse. Omitted with it.
pausedAt
string (date-time)
When a member paused the account, or null when it is not paused. A paused account sends no outreach (invites, messages, InMails, profile visits, withdrawals, approved Accounts notes); it waits and continues on resume, and so do the autonomous reply agent's sends. A person's replies from the Inbox and inbox sync keep running. Independent of status.
connectedByMemberId
integer (int64)
The member who connected the seat. Touches that send from it wait on this member's approval ; null for a seat connected before that was recorded.
importConnections
boolean
The seat's connections are imported into Accounts as people and accounts. Only the member who connected the seat or a workspace admin switches it. Always present.
importConnectionsChangedAt
string (date-time)
When importConnections last changed. Omitted while it never did.
workingHoursSchedule
LinkedInWeeklySchedule
products
LinkedInAccountProductsModel
invitesLast7Days
integer (int32)
Invites sent in the last 7 days, against Ken.Scraper.API.Canonical.LinkedInAccountModel.WeeklyInviteCap. The safety fields are omitted together when they could not be read.
weeklyInviteCap
integer (int32)
Rolling 7-day invite cap, at most 100.
pendingInvites
integer (int32)
Invitations still pending: the larger of Kenso's own and Unipile's count.
pendingInviteCap
integer (int32)
At or above this many pending invitations the seat sends no new invites.
health
LinkedInAccountHealthModel
rampStage
integer (int32)
Ramp stage (0, 1, 2, then full).
rampInviteCap
integer (int32)
Today's invite cap from the ramp: 5, 10, 20, then the seat's daily invite cap.
safetyEnabled
boolean
False while Kenso shows the safety values but does not enforce the weekly, pending and low-acceptance caps yet.
tagIds
array of integer (int64)
Ids of this workspace's tags on the account, ascending; empty when it has none. Set them with POST /v1/tags/{tagId}/assignments and entityType: linkedin_account. Only GET /v1/linkedin-accounts, GET /v1/linkedin-accounts/{linkedinAccountId} and its PATCH carry it, and they omit it when the tags could not be read. Other lists of this model, such as a campaign's LinkedIn accounts, never carry it.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/linkedin-accounts" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": [
    {
      "id": 812,
      "displayName": "string",
      "publicIdentifier": "string",
      "profileUrl": "https://example.com",
      "profileImageUrl": "https://example.com",
      "headline": "string",
      "status": "string",
      "timezone": "string",
      "workingHours": "string",
      "dailyInviteCap": 1,
      "dailyMessageCap": 1,
      "dailyVisitCap": 1,
      "createdAt": "2026-09-23T15:02:11Z",
      "createdDatetime": "2026-09-23T15:02:11Z",
      "updatedAt": "2026-09-23T15:02:11Z",
      "unipileAccountId": "string",
      "statusChangedAt": "2026-09-23T15:02:11Z",
      "invitesToday": 1,
      "messagesToday": 1,
      "visitsToday": 1,
      "invitesSent30d": 1,
      "acceptRate": 0.5,
      "usedBy": {
        "campaigns": [
          {
            "id": 812,
            "name": "string",
            "status": "string"
          }
        ],
        "accounts": true
      },
      "lastActionAt": "2026-09-23T15:02:11Z",
      "lastActionType": "string",
      "pausedAt": "2026-09-23T15:02:11Z",
      "connectedByMemberId": 812,
      "importConnections": true,
      "importConnectionsChangedAt": "2026-09-23T15:02:11Z",
      "workingHoursSchedule": {
        "mon": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "tue": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "wed": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "thu": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "fri": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "sat": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "sun": {
          "open": true,
          "start": "string",
          "end": "string"
        }
      },
      "products": {
        "premium": true,
        "salesNavigator": true,
        "recruiter": true,
        "checkedAt": "2026-09-23T15:02:11Z"
      },
      "invitesLast7Days": 1,
      "weeklyInviteCap": 1,
      "pendingInvites": 1,
      "pendingInviteCap": 1,
      "health": {
        "status": "string",
        "acceptRate": 0.5,
        "sample": 1,
        "changedAt": "2026-09-23T15:02:11Z"
      },
      "rampStage": 1,
      "rampInviteCap": 1,
      "safetyEnabled": true,
      "tagIds": [
        812
      ]
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

Replace the LinkedIn accounts assigned to a campaign

PUT/v1/campaigns/{campaignId}/linkedin-accountscampaigns.replace-linkedin-accounts

Try it on developer.ken.so

Full replace. Send linkedinAccountIds, or assignAllConnected to assign every Connected LinkedIn seat. Requires WriteAccess. A campaign outside the workspace is not_found before the write.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
linkedInAccountIds
array of integer (int64)
optional
assignAllConnected
boolean
optional

Response 200

data is a list of LinkedInAccountModel. Each item has these fields. The response also carries pagination.

Field
Type
Description
id
integer (int64)
displayName
string
publicIdentifier
string
profileUrl
string
profileImageUrl
string
headline
string
status
string
timezone
string
workingHours
string
dailyInviteCap
integer (int32)
dailyMessageCap
integer (int32)
dailyVisitCap
integer (int32)
createdAt
string (date-time)
createdDatetime
string (date-time)
updatedAt
string (date-time)
unipileAccountId
string
statusChangedAt
string (date-time)
invitesToday
integer (int32)
Invites sent today, on the seat's own day (the trailing 24 hours when rolling seat budgets are on). Compare with Ken.Scraper.API.Canonical.LinkedInAccountModel.DailyInviteCap. Omitted when the counters could not be read.
messagesToday
integer (int32)
Messages and InMails sent today, on the same day as Ken.Scraper.API.Canonical.LinkedInAccountModel.InvitesToday.
visitsToday
integer (int32)
Profile visits today, on the same day as Ken.Scraper.API.Canonical.LinkedInAccountModel.InvitesToday.
invitesSent30d
integer (int32)
Invites sent in the last 30 days (by invite time). Omitted when it could not be read.
acceptRate
number (double)
Share of Ken.Scraper.API.Canonical.LinkedInAccountModel.InvitesSent30d that were accepted, a fraction from 0 to 1. Omitted when no invite was sent in the window.
usedBy
UsedByModel
What an inbox or LinkedIn account is used for: the live campaigns that send through it, and whether the 1:1 Accounts lane does.
lastActionAt
string (date-time)
When the seat last completed an outbound action: the newest confirmed campaign step or Accounts-lane touch . A person's replies from the Inbox do not count. Omitted when the seat has none or it could not be read.
lastActionType
string
The action at Ken.Scraper.API.Canonical.LinkedInAccountModel.LastActionAt: visit, invite, message, inmail, withdraw, follow, reaction, comment or endorse. Omitted with it.
pausedAt
string (date-time)
When a member paused the account, or null when it is not paused. A paused account sends no outreach (invites, messages, InMails, profile visits, withdrawals, approved Accounts notes); it waits and continues on resume, and so do the autonomous reply agent's sends. A person's replies from the Inbox and inbox sync keep running. Independent of status.
connectedByMemberId
integer (int64)
The member who connected the seat. Touches that send from it wait on this member's approval ; null for a seat connected before that was recorded.
importConnections
boolean
The seat's connections are imported into Accounts as people and accounts. Only the member who connected the seat or a workspace admin switches it. Always present.
importConnectionsChangedAt
string (date-time)
When importConnections last changed. Omitted while it never did.
workingHoursSchedule
LinkedInWeeklySchedule
products
LinkedInAccountProductsModel
invitesLast7Days
integer (int32)
Invites sent in the last 7 days, against Ken.Scraper.API.Canonical.LinkedInAccountModel.WeeklyInviteCap. The safety fields are omitted together when they could not be read.
weeklyInviteCap
integer (int32)
Rolling 7-day invite cap, at most 100.
pendingInvites
integer (int32)
Invitations still pending: the larger of Kenso's own and Unipile's count.
pendingInviteCap
integer (int32)
At or above this many pending invitations the seat sends no new invites.
health
LinkedInAccountHealthModel
rampStage
integer (int32)
Ramp stage (0, 1, 2, then full).
rampInviteCap
integer (int32)
Today's invite cap from the ramp: 5, 10, 20, then the seat's daily invite cap.
safetyEnabled
boolean
False while Kenso shows the safety values but does not enforce the weekly, pending and low-acceptance caps yet.
tagIds
array of integer (int64)
Ids of this workspace's tags on the account, ascending; empty when it has none. Set them with POST /v1/tags/{tagId}/assignments and entityType: linkedin_account. Only GET /v1/linkedin-accounts, GET /v1/linkedin-accounts/{linkedinAccountId} and its PATCH carry it, and they omit it when the tags could not be read. Other lists of this model, such as a campaign's LinkedIn accounts, never carry it.
Request
curl -X PUT "https://api.ken.so/v1/campaigns/812/linkedin-accounts" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "linkedInAccountIds": [
    812
  ],
  "assignAllConnected": true
}'
Response 200
{
  "success": true,
  "data": [
    {
      "id": 812,
      "displayName": "string",
      "publicIdentifier": "string",
      "profileUrl": "https://example.com",
      "profileImageUrl": "https://example.com",
      "headline": "string",
      "status": "string",
      "timezone": "string",
      "workingHours": "string",
      "dailyInviteCap": 1,
      "dailyMessageCap": 1,
      "dailyVisitCap": 1,
      "createdAt": "2026-09-23T15:02:11Z",
      "createdDatetime": "2026-09-23T15:02:11Z",
      "updatedAt": "2026-09-23T15:02:11Z",
      "unipileAccountId": "string",
      "statusChangedAt": "2026-09-23T15:02:11Z",
      "invitesToday": 1,
      "messagesToday": 1,
      "visitsToday": 1,
      "invitesSent30d": 1,
      "acceptRate": 0.5,
      "usedBy": {
        "campaigns": [
          {
            "id": 812,
            "name": "string",
            "status": "string"
          }
        ],
        "accounts": true
      },
      "lastActionAt": "2026-09-23T15:02:11Z",
      "lastActionType": "string",
      "pausedAt": "2026-09-23T15:02:11Z",
      "connectedByMemberId": 812,
      "importConnections": true,
      "importConnectionsChangedAt": "2026-09-23T15:02:11Z",
      "workingHoursSchedule": {
        "mon": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "tue": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "wed": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "thu": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "fri": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "sat": {
          "open": true,
          "start": "string",
          "end": "string"
        },
        "sun": {
          "open": true,
          "start": "string",
          "end": "string"
        }
      },
      "products": {
        "premium": true,
        "salesNavigator": true,
        "recruiter": true,
        "checkedAt": "2026-09-23T15:02:11Z"
      },
      "invitesLast7Days": 1,
      "weeklyInviteCap": 1,
      "pendingInvites": 1,
      "pendingInviteCap": 1,
      "health": {
        "status": "string",
        "acceptRate": 0.5,
        "sample": 1,
        "changedAt": "2026-09-23T15:02:11Z"
      },
      "rampStage": 1,
      "rampInviteCap": 1,
      "safetyEnabled": true,
      "tagIds": [
        812
      ]
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

Read a campaign's LinkedIn settings

GET/v1/campaigns/{campaignId}/linkedin-settingscampaigns.get-linkedin-settings

Try it on developer.ken.so

Replaces GET /v1/campaigns/{id}/linkedin-config. A campaign outside the workspace is not_found. Requires ReadAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a LinkedInSettingsModel.

Field
Type
Description
isEnabled
boolean
channelPolicy
string
notAcceptedAfterDays
integer (int32)
conditionWaitDays
integer (int32)
isWithdrawEnabled
boolean
stopLinkedInOnEmailReply
boolean
stopEmailOnLinkedInReply
boolean
stopSequenceForCompanyOnReply
boolean
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/linkedin-settings" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "isEnabled": true,
    "channelPolicy": "string",
    "notAcceptedAfterDays": 1,
    "conditionWaitDays": 1,
    "isWithdrawEnabled": true,
    "stopLinkedInOnEmailReply": true,
    "stopEmailOnLinkedInReply": true,
    "stopSequenceForCompanyOnReply": true
  }
}

Replace a campaign's LinkedIn settings

PUT/v1/campaigns/{campaignId}/linkedin-settingscampaigns.replace-linkedin-settings

Try it on developer.ken.so

channelPolicy is linkedin_only, linkedin_first_email_fallback, email_first_linkedin_reinforce, or parallel. Requires Campaigns edit access (owners, admins and editors by default). A campaign outside the workspace is not_found before the write.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
isEnabled
boolean
optional
channelPolicy
string
optional
notAcceptedAfterDays
integer (int32)
optional
conditionWaitDays
integer (int32)
optional
isWithdrawEnabled
boolean
optional
stopLinkedInOnEmailReply
boolean
optional
stopEmailOnLinkedInReply
boolean
optional
stopSequenceForCompanyOnReply
boolean
optional

Response 200

data is a LinkedInSettingsModel.

Field
Type
Description
isEnabled
boolean
channelPolicy
string
notAcceptedAfterDays
integer (int32)
conditionWaitDays
integer (int32)
isWithdrawEnabled
boolean
stopLinkedInOnEmailReply
boolean
stopEmailOnLinkedInReply
boolean
stopSequenceForCompanyOnReply
boolean
Request
curl -X PUT "https://api.ken.so/v1/campaigns/812/linkedin-settings" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "isEnabled": true,
  "channelPolicy": "string",
  "notAcceptedAfterDays": 1,
  "conditionWaitDays": 1,
  "isWithdrawEnabled": true,
  "stopLinkedInOnEmailReply": true,
  "stopEmailOnLinkedInReply": true,
  "stopSequenceForCompanyOnReply": true
}'
Response 200
{
  "success": true,
  "data": {
    "isEnabled": true,
    "channelPolicy": "string",
    "notAcceptedAfterDays": 1,
    "conditionWaitDays": 1,
    "isWithdrawEnabled": true,
    "stopLinkedInOnEmailReply": true,
    "stopEmailOnLinkedInReply": true,
    "stopSequenceForCompanyOnReply": true
  }
}

Read whether lead scoring is enabled for a campaign

GET/v1/campaigns/{campaignId}/scoringcampaigns.get-scoring

Try it on developer.ken.so

Replaces POST /v1/campaigns/{id}/scoring for reads. A campaign outside the workspace is not_found. Requires ReadAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a ScoringSettingsModel.

Field
Type
Description
isEnabled
boolean
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/scoring" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "isEnabled": true
  }
}

Turn lead scoring on or off for a campaign

PUT/v1/campaigns/{campaignId}/scoringcampaigns.replace-scoring

Try it on developer.ken.so

Requires WriteAccess. A campaign outside the workspace is not_found before the write.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
isEnabled
boolean
optional

Response 200

data is a ScoringSettingsModel.

Field
Type
Description
isEnabled
boolean
Request
curl -X PUT "https://api.ken.so/v1/campaigns/812/scoring" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "isEnabled": true
}'
Response 200
{
  "success": true,
  "data": {
    "isEnabled": true
  }
}

List the AI workflows attached to a campaign

GET/v1/campaigns/{campaignId}/ai-workflowscampaigns.list-ai-workflows

Try it on developer.ken.so

Ids: 1 qualification, 2 personalization, 3 rewriting, 4 segmentation, 6 scoring, 8 line check. Read-only. Requires ReadAccess. A campaign outside the workspace is not_found.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a list of CampaignAiWorkflowModel. Each item has these fields. The response also carries pagination.

Field
Type
Description
id
integer (int64)
Workflow id: 1 qualification, 2 personalization, 3 rewriting, 4 segmentation, 6 scoring, 8 line check.
flow
string
qualification, personalization, rewriting, segmentation, scoring, or line_check.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/ai-workflows" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": [
    {
      "id": 812,
      "flow": "string"
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

Replace the AI workflows attached to a campaign

PUT/v1/campaigns/{campaignId}/ai-workflowscampaigns.replace-ai-workflows

Try it on developer.ken.so

Full replace: send every workflow id the campaign should run in aiWorkflowIds. Ids not listed are detached, and an empty list detaches all of them. Read the current set first to add one workflow. Ids: 1 qualification, 2 personalization, 3 rewriting, 4 segmentation, 6 scoring, 8 line check. Rewriting runs only when the campaign has rewriting prompts. Line check (8) grades personalization lines before they are saved and holds a contact whose lines fail; a campaign gets it with personalization by default, and detaching it is the opt-out. This route does not add it: keep 8 in the list to keep it on. While the campaign is scheduled, launching, or sending, 1, 2, and 4 must stay as they are and 3 may only be added; any other change to them is invalid_state (409) because they gate which contacts may send, so pause first. 6 and 8 can change at any time. No other campaign field changes. Requires WriteAccess. A campaign outside the workspace is not_found before the write.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
aiWorkflowIds
array of integer (int64)
optional
The full set of AI workflow ids to attach: 1 qualification, 2 personalization, 3 rewriting, 4 segmentation, 6 scoring. An empty list detaches every workflow.

Response 200

data is a list of CampaignAiWorkflowModel. Each item has these fields. The response also carries pagination.

Field
Type
Description
id
integer (int64)
Workflow id: 1 qualification, 2 personalization, 3 rewriting, 4 segmentation, 6 scoring, 8 line check.
flow
string
qualification, personalization, rewriting, segmentation, scoring, or line_check.
Request
curl -X PUT "https://api.ken.so/v1/campaigns/812/ai-workflows" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "aiWorkflowIds": [
    812
  ]
}'
Response 200
{
  "success": true,
  "data": [
    {
      "id": 812,
      "flow": "string"
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

Score contacts in a campaign

POST/v1/campaigns/{campaignId}/scoring/runcampaigns.run-scoring

Try it on developer.ken.so

The run finishes in the request and returns a lead_scoring job. Poll it with GET /v1/jobs/{jobId}?type=lead_scoring. Scores are qualityScore on each contact. Requires WriteAccess. A campaign outside the workspace is not_found before the run.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
contactIds
array of integer (int64)
optional
limit
integer (int32)
optional

Response 200

data is a JobModel.

Field
Type
Description
id
integer (int64)
type
string
status
string
progress
JobProgressModel
resource
JobResourceModel
createdAt
string (date-time)
downloadUrl
string
error
JobErrorModel
result
object
Upstream payload for a scraping job get. Omitted for other types and for lists.
domainsListed
integer (int32)
Listed-domain count stored by a completed domain health sweep. Omitted until that count exists.
excludedLibraryContacts
integer (int32)
Contacts from a Kenso library list a contact export left out (they are never exported), including 0. Only on contact_export jobs of kind contacts, campaign and advanced; omitted otherwise.
inboxImport
InboxImportJobModel
Counts and per-row results of an inbox_import job. Passwords are never returned.
personImport
PersonImportJobModel
Counts of a contact_import job with target people: each row created a person, updated (matched) one, or was skipped (no email, LinkedIn URL, or name with a business company domain, or intake refused it).
skipped
ContactImportSkipsModel
Rows a campaign contact_import job skipped, by reason. The duplicate counts name the rule that dropped the row: a contact already in this campaign, a contact in another campaign of the workspace (the campaign dedups against other campaigns), an earlier row of the same import, or the campaign's recently-emailed window.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/scoring/run" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "contactIds": [
    812
  ],
  "limit": 1
}'
Response 200
{
  "success": true,
  "data": {
    "id": 812,
    "type": "string",
    "status": "string",
    "progress": {
      "completed": 1,
      "total": 1
    },
    "resource": {
      "type": "string",
      "id": 812
    },
    "createdAt": "2026-09-23T15:02:11Z",
    "downloadUrl": "https://example.com",
    "error": {
      "code": "string",
      "message": "string"
    },
    "result": null,
    "domainsListed": 1,
    "excludedLibraryContacts": 1,
    "inboxImport": {
      "connectedCount": 1,
      "failedCount": 1,
      "skippedCount": 1,
      "startedAt": "2026-09-23T15:02:11Z",
      "updatedAt": "2026-09-23T15:02:11Z",
      "completedAt": "2026-09-23T15:02:11Z",
      "rows": [
        {
          "index": 1,
          "email": "[email protected]",
          "status": "string",
          "inboxId": 812,
          "errorCode": "string",
          "message": "string"
        }
      ]
    },
    "personImport": {
      "source": "string",
      "createdCount": 1,
      "updatedCount": 1,
      "skippedCount": 1,
      "startedAt": "2026-09-23T15:02:11Z",
      "completedAt": "2026-09-23T15:02:11Z"
    },
    "skipped": {
      "duplicateInCampaign": 1,
      "duplicateInOtherCampaign": 1,
      "duplicateInSource": 1,
      "alreadyContacted": 1,
      "blocklist": 1,
      "failedValidation": 1,
      "salesNavigatorProfileUnresolved": 1,
      "other": 1,
      "missingKey": 1,
      "invalidEmail": 1,
      "noMx": 1,
      "catchAll": 1,
      "personalEmail": 1,
      "roleAddress": 1,
      "companyContacted": 1,
      "companyCap": 1
    }
  }
}

Queue lead enrichment for a campaign

POST/v1/campaigns/{campaignId}/enrichment-runscampaigns.create-enrichment-run

Try it on developer.ken.so

Replaces the legacy campaign enrichment trigger. The request finishes when Search accepts the batches and returns an enrichment job. minConfidence defaults to 0.5. Requires WriteAccess. A campaign outside the workspace is not_found before the run.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
minConfidence
number (float)
optional

Response 200

data is a JobModel.

Field
Type
Description
id
integer (int64)
type
string
status
string
progress
JobProgressModel
resource
JobResourceModel
createdAt
string (date-time)
downloadUrl
string
error
JobErrorModel
result
object
Upstream payload for a scraping job get. Omitted for other types and for lists.
domainsListed
integer (int32)
Listed-domain count stored by a completed domain health sweep. Omitted until that count exists.
excludedLibraryContacts
integer (int32)
Contacts from a Kenso library list a contact export left out (they are never exported), including 0. Only on contact_export jobs of kind contacts, campaign and advanced; omitted otherwise.
inboxImport
InboxImportJobModel
Counts and per-row results of an inbox_import job. Passwords are never returned.
personImport
PersonImportJobModel
Counts of a contact_import job with target people: each row created a person, updated (matched) one, or was skipped (no email, LinkedIn URL, or name with a business company domain, or intake refused it).
skipped
ContactImportSkipsModel
Rows a campaign contact_import job skipped, by reason. The duplicate counts name the rule that dropped the row: a contact already in this campaign, a contact in another campaign of the workspace (the campaign dedups against other campaigns), an earlier row of the same import, or the campaign's recently-emailed window.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/enrichment-runs" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "minConfidence": 0.5
}'
Response 200
{
  "success": true,
  "data": {
    "id": 812,
    "type": "string",
    "status": "string",
    "progress": {
      "completed": 1,
      "total": 1
    },
    "resource": {
      "type": "string",
      "id": 812
    },
    "createdAt": "2026-09-23T15:02:11Z",
    "downloadUrl": "https://example.com",
    "error": {
      "code": "string",
      "message": "string"
    },
    "result": null,
    "domainsListed": 1,
    "excludedLibraryContacts": 1,
    "inboxImport": {
      "connectedCount": 1,
      "failedCount": 1,
      "skippedCount": 1,
      "startedAt": "2026-09-23T15:02:11Z",
      "updatedAt": "2026-09-23T15:02:11Z",
      "completedAt": "2026-09-23T15:02:11Z",
      "rows": [
        {
          "index": 1,
          "email": "[email protected]",
          "status": "string",
          "inboxId": 812,
          "errorCode": "string",
          "message": "string"
        }
      ]
    },
    "personImport": {
      "source": "string",
      "createdCount": 1,
      "updatedCount": 1,
      "skippedCount": 1,
      "startedAt": "2026-09-23T15:02:11Z",
      "completedAt": "2026-09-23T15:02:11Z"
    },
    "skipped": {
      "duplicateInCampaign": 1,
      "duplicateInOtherCampaign": 1,
      "duplicateInSource": 1,
      "alreadyContacted": 1,
      "blocklist": 1,
      "failedValidation": 1,
      "salesNavigatorProfileUnresolved": 1,
      "other": 1,
      "missingKey": 1,
      "invalidEmail": 1,
      "noMx": 1,
      "catchAll": 1,
      "personalEmail": 1,
      "roleAddress": 1,
      "companyContacted": 1,
      "companyCap": 1
    }
  }
}

Re-queue failed enrichment without restarting the campaign

POST/v1/campaigns/{campaignId}/retry-enrichmentcampaigns.retry-enrichment

Try it on developer.ken.so

Deletes failed website and email-finder markers so existing workers pick the contacts up again. Does not restart processing, and does not change qualification, personalization, or sent state. If data or email enrichment is paused, the markers are still cleared and workers wait until resume. Idempotent: clearing nothing is still 200. An archived campaign is validation_failed and is not touched. A campaign outside the workspace is not_found before the retry. Requires WriteAccess.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a RetryEnrichmentModel.

Field
Type
Description
companyStatusesCleared
integer (int32)
scrapeFailuresCleared
integer (int32)
contactStatusesCleared
integer (int32)
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/retry-enrichment" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "companyStatusesCleared": 1,
    "scrapeFailuresCleared": 1,
    "contactStatusesCleared": 1
  }
}

Restart a campaign's processing

POST/v1/campaigns/{campaignId}/restartcampaigns.restart

Try it on developer.ken.so

Re-runs the processing pipeline from the beginning, to recover a campaign that stalled or errored, or to reprocess after a configuration change. On a draft campaign it starts processing behind the same readiness checks as resume, and a draft trigger campaign is 400 validation_failed (start it with resume). An archived campaign is 400 validation_failed. A campaign with autoLaunchWhenReady launches once processing reaches ready. Returns the campaign. Requires WriteAccess and the launch permission. While the workspace's data is scheduled for deletion or being deleted, returns 409 invalid_state.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a CampaignModel.

Field
Type
Description
id
integer (int64)
name
string
status
string
channel
string
contactLimit
integer (int32)
The campaign's contact limit. DEV-3765: a monthly number when Ken.Scraper.API.Canonical.CampaignModel.ContactLimitPeriod is month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.
contactLimitPeriod
string
month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.
contactLimitUsed
integer (int32)
Contacts counted against Ken.Scraper.API.Canonical.CampaignModel.ContactLimit: this month's when monthly, else Ken.Scraper.API.Canonical.CampaignModel.ContactCount.
contactLimitResetsAt
string (date-time)
When a monthly count starts again (the next 1st, 00:00 UTC). Omitted for a total.
fairUseContactLimit
integer (int32)
Lifetime fair-use ceiling of a monthly limit. Omitted for a total.
contactLimitReached
boolean
True while processing is stopped by the contact limit, the fair-use total or the workspace's lead credits.
contactLimitReachedReason
string
Why Ken.Scraper.API.Canonical.CampaignModel.ContactLimitReached is set: monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.
contactCount
integer (int32)
Contacts found with a valid email (campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount.
leadCount
integer (int32)
Every non-deleted contact in the campaign, with or without an email, so a LinkedIn-only campaign counts its leads. Returned on the list (one count for the page, DEV-3034; cached per workspace, DEV-6843, so it usually lags by under 15 minutes, and on a very large workspace the first list after a quiet period can show an older count until the next list) and on the single-campaign responses of /v1/campaigns (get, create, update, duplicate and the lifecycle actions), which count live. Omitted when the count cannot be read, or does not finish within its 1 s budget on a very large workspace.
segmentCount
integer (int32)
inboxCount
integer (int32)
createdAt
string (date-time)
launchedAt
string (date-time)
sourceStatus
string
Stored campaign.status (pipeline/source), not the collapsed lifecycle string.
sendingStatus
string
Snake-case sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.
processingStatus
array of string
Snake-case processing flags. none when the flags value is 0.
sendingDays
array of integer (int32)
sendingStartTime
string
sendingEndTime
string
sendingTimezone
string
maxEmailsPerDay
integer (int32)
sendingError
string
Current sending error (failure class, count in the window, affected inboxes). Set while hard send failures stay above the rate rule; clears itself. Null = none.
sendingErrorAt
string (date-time)
UTC instant the current Ken.Scraper.API.Canonical.CampaignModel.SendingError began. Null with it.
ownerMemberId
integer (int64)
DEV-1753 campaign owner, a workspace member id. The stored owner when one was set, else the member who created the campaign while they are still in the workspace. Omitted when neither.
stopOnReply
boolean
The lead's reply stops their sequence. When false, neutral and positive replies keep it going; a negative reply, an unsubscribe, or a booked meeting still stops it. The four sending switches take effect only with the campaign_send_settings flag on for the workspace; this field is the stored value either way.
stopCompanyOnReply
boolean
A reply also stops every other contact at the lead's company. Change it with PATCH /v1/campaigns/{campaignId} or the LinkedIn settings (the same stored value). New campaigns start with it on; a duplicate keeps its source's value. False when the campaign has no LinkedIn settings.
groupSameCompanySends
boolean
Coworkers at one company share a send day. New campaigns default to true.
newLeadsFirst
boolean
First emails (step 1) take the campaign's daily capacity ahead of its follow-ups, so every lead gets email 1 before older leads get their next step. False (the default) sends due follow-ups first. On a campaign that keeps getting new leads (top-ups, imports), follow-ups can wait as long as it stays on, so turn it off once Ken.Scraper.API.Canonical.CampaignModel.AwaitingFirstEmailCount reaches 0. Every copy starts with it off, unlike the other sending switches: a duplicate, a template saved from the campaign, and a draft made from that template.
sourceLanguage
string
The language the steps are written in, a market pack locale (fr-FR) or a bare language (en, the default). Always on the wire, like the other four language settings. Changing any of them needs the multilingual_outreach flag for the workspace.
languageMode
string
source_only (default) sends every contact the source text. auto sends a contact the approved localization of their outreach language when Ken.Scraper.API.Canonical.CampaignModel.EnabledLanguages lists it.
enabledLanguages
array of string
The market pack locales the campaign localizes into (fr-FR, es-MX). Empty: none.
formalityOverrides
map of string
Locale to formal or informal, over the pack's default. Empty: the packs' defaults.
languageFallback
string
What an auto campaign does for a contact whose language has no approved localization: source (default) sends the source text, hold holds the contact until one is approved.
awaitingFirstEmailCount
integer (int32)
Ready leads still waiting for their first email (no email sent and none accepted for sending). Not counted: leads in a paused segment or held back by the ready checks, company Cc members, bulk-paused leads (they get email 1 once resumed), and leads the sequencer never sends email 1 to (a sequence stop such as a company reply, a hard bounce, an unsubscribe). Still counted: leads waiting for campaign batch approval, leads an enforced account play holds, and leads whose email is live in another campaign, so the count can stop falling above 0 while those wait. Returned on the single-campaign responses while Ken.Scraper.API.Canonical.CampaignModel.NewLeadsFirst is true; omitted otherwise, on the list, and when the count cannot be read.
rampUpDays
integer (int32)
Ramp-up length in scheduled sending days, 0 (no ramp) to 30. New campaigns default to 5.
trackClicks
boolean
Links in sends go through tracking links. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
trackOpens
boolean
Sends carry the open-tracking pixel. Off by default. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
unsubscribeLink
boolean
Sends carry the List-Unsubscribe headers. When false they are left out of a step whose body has the {{unsubscribe_link}} URL; a step without it keeps them, so every send has an opt-out. Stored value (see Ken.Scraper.API.Canonical.CampaignModel.StopOnReply).
protectAccountPlays
boolean
Skip people in active account plays (warming, engaged, meeting_booked). New campaigns default to true. Takes effect only when account plays are on for the workspace.
bookingMeetingTypeId
integer (int64)
The round robin meeting type that takes bookings the owner and sender can't. Omitted when none.
replyTo
string
This campaign's Reply-To, over the workspace's. Omitted when the campaign uses the workspace's (or none). A connected inbox of this workspace; no header is sent from that same inbox.
excludedCountries
array of string
ISO 3166-1 alpha-2 codes (upper case, sorted) of the countries this campaign never sends to. Imports drop a contact whose country is listed, and the sequencer skips one already on the campaign. A contact whose country Kenso cannot read is kept. Empty (the default) blocks none. Always on the wire.
kind
string
sales or recruiting. Set at create; PATCH refuses it.
roleId
integer (int64)
The primary role of a recruiting campaign, or null. Always on the wire.
mode
string
batch (a fixed audience) or trigger (signal-based: contacts arrive over time). A batch campaign can become a trigger campaign; a trigger campaign cannot go back. Always on the wire.
autoLaunchWhenReady
boolean
The campaign launches itself, and starts spending sending credits, once it reaches ready. Changing it needs the launch permission. Always on the wire.
migration
CampaignMigrationOriginModel
The tool, account and campaign an imported campaign came from. sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).
approvalsWaiting
integer (int32)
First-touch emails (leads) waiting for approval in the campaign's pending or rewriting campaign batches. 0 when none.
forecast
CampaignForecastModel
How far a sending or paused campaign is against its planned sends, and when it should finish. From the sequencer's stored forecast, refreshed every 5 minutes from the inboxes' daily capacity (shared with the workspace's other campaigns), maxEmailsPerDay, the sending days, window and time zone, and the step waits.
pausedReason
CampaignPausedReasonModel
Why a campaign's sending is paused, from what Kenso recorded when it stopped. kind: user (a member paused it, or an agent for a member), bounce_guard (the campaign hard-bounce guard), sending_error (the sequencer paused it after repeated send failures), holiday (the US-holiday pause), client_approval (paused for the client's approval of changed copy or a lead list; approval resumes it, or leaves it paused for a manual resume when it cannot send), billing (sending suspended for non-payment), workspace_locked (the workspace was deactivated or locked), other (another system pause), unknown (nothing recorded why, such as a pause from before Kenso recorded them). Clients must tolerate new values. A campaign is never paused for having no inboxes: a broken inbox leaves it sending and shows on the inbox instead.
Request
curl -X POST "https://api.ken.so/v1/campaigns/812/restart" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "id": 4821,
    "name": "Q4 SaaS founders",
    "status": "active",
    "channel": "email",
    "contactLimit": 2500,
    "contactLimitPeriod": "total",
    "contactLimitUsed": 1840,
    "contactLimitReached": false,
    "contactCount": 1840,
    "leadCount": 2115,
    "segmentCount": 2,
    "inboxCount": 6,
    "createdAt": "2026-09-23T15:02:11Z",
    "launchedAt": "2026-09-29T13:00:00Z",
    "sourceStatus": "done",
    "sendingStatus": "sending",
    "processingStatus": [
      "finished"
    ],
    "sendingDays": [
      1,
      2,
      3,
      4,
      5
    ],
    "sendingStartTime": "09:00",
    "sendingEndTime": "17:00",
    "sendingTimezone": "America/New_York",
    "maxEmailsPerDay": 300,
    "ownerMemberId": 1093,
    "stopOnReply": true,
    "stopCompanyOnReply": true,
    "groupSameCompanySends": true,
    "newLeadsFirst": false,
    "sourceLanguage": "en",
    "languageMode": "source_only",
    "enabledLanguages": [],
    "formalityOverrides": {},
    "languageFallback": "source",
    "rampUpDays": 5,
    "trackClicks": false,
    "trackOpens": false,
    "unsubscribeLink": true,
    "protectAccountPlays": true,
    "excludedCountries": [],
    "kind": "sales",
    "mode": "batch",
    "autoLaunchWhenReady": false,
    "approvalsWaiting": 0,
    "forecast": {
      "plannedSendCount": 3680,
      "sentCount": 2208,
      "remainingSendCount": 1472,
      "progress": 0.6,
      "estimatedFinishAt": "2026-10-20T20:40:00Z",
      "effectiveDailyCapacity": 300,
      "calculatedAt": "2026-10-14T09:35:00Z"
    }
  }
}

Return a campaign's sender routing

GET/v1/campaigns/{campaignId}/sender-routingcampaigns.get-sender-routing

Try it on developer.ken.so

DEV-5225. allInboxes is the campaign's inbox mode. Each rule sends the first email to prospects on its mailbox provider or security gateway only from its domains and inboxes; a gateway rule wins over a mailbox provider rule, and prospects no rule matches go from the inboxes no rule claims (everyoneElse). Per rule and for Everyone else: the campaign's inboxes that can send now (Kenso-sendable, not paused), their summed daily limits, and the leads that can still get an email and match now (at most 100,000 leads read; a lead whose provider is still being detected is in no group). warnings: rule_no_inbox, everyone_else_no_inbox, rule_slow (over 5 days at the rule's capacity). Read-only. Needs the workspace's sender_routing flag, else 400 validation_failed with detail code feature_disabled. Requires ReadAccess. A campaign outside the workspace is not_found.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a SenderRoutingModel.

Field
Type
Description
allInboxes
boolean
True: the campaign's inboxes are live ("All inboxes"), so inboxes connected later join it. False: a saved list. Change it with PUT /v1/campaigns/{campaignId}/inboxes (allInboxes: true, or inboxIds).
rules
array of SenderRouteRuleModel
The rules, by provider name.
everyoneElse
SenderRouteGroupModel
warnings
array of SenderRoutingWarningModel
What would make leads wait: see Ken.Scraper.API.Canonical.SenderRoutingWarningModel.Code.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/sender-routing" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "allInboxes": true,
    "rules": [
      {
        "provider": {
          "slug": "string",
          "name": "string",
          "kind": "string"
        },
        "domains": [
          "example.com"
        ],
        "inboxIds": [
          812
        ],
        "sendableInboxCount": 1,
        "dailyCapacity": 1,
        "leadCount": 1
      }
    ],
    "everyoneElse": {
      "sendableInboxCount": 1,
      "dailyCapacity": 1,
      "leadCount": 1
    },
    "warnings": [
      {
        "code": "string",
        "provider": "string",
        "leadCount": 1,
        "days": 1
      }
    ]
  }
}

Replace a campaign's sender routing rules

PUT/v1/campaigns/{campaignId}/sender-routingcampaigns.replace-sender-routing

Try it on developer.ken.so

DEV-5225. Full replace: rules is every rule, [] clears them. Each rule names an active mailbox provider or security gateway provider slug (one rule per provider) and at least one whole sending domain in domains (lowercased when saved; inboxes added on it later join) or single inboxIds. Every domain must be the address domain of a workspace inbox and every inbox a workspace inbox, else 400 validation_failed with detail codes unknown_provider, unsupported_provider, duplicate_provider, empty_rule, unknown_domain or unknown_inbox. Applies to first emails planned from now on and to queued first emails at send time; follow-ups stay on the first email's inbox. Returns the routing as GET does. Needs the sender_routing flag (feature_disabled). Requires WriteAccess. A campaign outside the workspace is not_found before the write.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Request body

application/json, optional.

Field
Type
Required
Description
rules
array of ReplaceCampaignSenderRouteRule
optional
At most one rule per provider. Required.

Response 200

data is a SenderRoutingModel.

Field
Type
Description
allInboxes
boolean
True: the campaign's inboxes are live ("All inboxes"), so inboxes connected later join it. False: a saved list. Change it with PUT /v1/campaigns/{campaignId}/inboxes (allInboxes: true, or inboxIds).
rules
array of SenderRouteRuleModel
The rules, by provider name.
everyoneElse
SenderRouteGroupModel
warnings
array of SenderRoutingWarningModel
What would make leads wait: see Ken.Scraper.API.Canonical.SenderRoutingWarningModel.Code.
Request
curl -X PUT "https://api.ken.so/v1/campaigns/812/sender-routing" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
  "rules": [
    {
      "provider": "string",
      "domains": [
        "example.com"
      ],
      "inboxIds": [
        812
      ]
    }
  ]
}'
Response 200
{
  "success": true,
  "data": {
    "allInboxes": true,
    "rules": [
      {
        "provider": {
          "slug": "string",
          "name": "string",
          "kind": "string"
        },
        "domains": [
          "example.com"
        ],
        "inboxIds": [
          812
        ],
        "sendableInboxCount": 1,
        "dailyCapacity": 1,
        "leadCount": 1
      }
    ],
    "everyoneElse": {
      "sendableInboxCount": 1,
      "dailyCapacity": 1,
      "leadCount": 1
    },
    "warnings": [
      {
        "code": "string",
        "provider": "string",
        "leadCount": 1,
        "days": 1
      }
    ]
  }
}

Count a campaign's leads by mailbox provider and security gateway

GET/v1/campaigns/{campaignId}/recipient-providerscampaigns.list-recipient-providers

Try it on developer.ken.so

DEV-5225. Every active mailbox provider and security gateway with the campaign's leads behind it (0 included), most leads first: the leads that can still get an email, by the domain of the address they are sent to. A lead counts once under its mailbox provider and once under its security gateway. pendingCount: provider still being detected. noProviderCount: no listed provider. Counts cover at most 100,000 leads (isPartial). Read-only. Needs the sender_routing flag (feature_disabled). Requires ReadAccess. A campaign outside the workspace is not_found.

Parameters

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

data is a RecipientProviderCountsModel.

Field
Type
Description
providers
array of RecipientProviderCountModel
Every active mailbox provider and security gateway, 0 included, most leads first. A lead counts once under its mailbox provider and once under its security gateway.
pendingCount
integer (int32)
Leads whose provider is still being detected.
noProviderCount
integer (int32)
Leads whose domain has no listed provider (self-hosted mail, no MX, an unknown host).
isPartial
boolean
True when the campaign has more than 100,000 leads that can still get an email; counts cover the first 100,000 by id.
Request
curl -X GET "https://api.ken.so/v1/campaigns/812/recipient-providers" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "providers": [
      {
        "slug": "string",
        "name": "string",
        "kind": "string",
        "leadCount": 1
      }
    ],
    "pendingCount": 1,
    "noProviderCount": 1,
    "isPartial": true
  }
}