Campaigns
Create and update campaigns, launch, pause, resume, archive and duplicate them, assign their inboxes and LinkedIn accounts, and run scoring and enrichment.
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
400 workspace_required).Response 200
data is a list of CampaignModel. Each item has these fields. The response also carries pagination.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
{
"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
}
}Create a campaign in the current workspace
/v1/campaignscampaigns.createReturns 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
400 workspace_required).Request body
application/json, optional.
sales (default) or recruiting. A recruiting campaign needs the recruiting flag.Response 201
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
}'{
"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 one campaign in the current workspace
/v1/campaigns/{campaignId}campaigns.getA missing or cross-workspace id is not_found. Read-only. Requires ReadAccess.
Parameters
400 workspace_required).Response 200
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
{
"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"
}
}
}Update a campaign's name, contact limit, send schedule, ramp-up, owner, sending switches, same-company rules, account-play protection, new-leads-first order, language settings, mode, or auto-launch
/v1/campaigns/{campaignId}campaigns.updateAbsent 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
400 workspace_required).Request body
application/json, optional.
status is rejected. Lifecycle changes are actions.stopSequenceForCompanyOnReply in the LinkedIn settings. Omit to leave it unchanged.fr-FR) or a language with a primary pack (en).source_only or auto.source or hold.batch or trigger. Batch to trigger converts the campaign; trigger to batch is refused (mode_one_way). Omit to leave it unchanged.Response 200
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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
}'{
"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"
}
}
}Requires ReadAccess, matching the legacy delete. Returns 204.
Parameters
400 workspace_required).Response 204
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"
HTTP/1.1 204 No Content
Launch sending for a campaign
/v1/campaigns/{campaignId}/launchcampaigns.launchRequires 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
400 workspace_required).Response 200
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
{
"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 for an already-archived campaign. A campaign that is still sending returns 409 invalid_state. Requires WriteAccess.
Parameters
400 workspace_required).Response 200
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
{
"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
/v1/campaigns/{campaignId}/duplicatecampaigns.duplicateincludeContacts copies contacts. The copy stays in this workspace. Returns 201 and Location. Requires WriteAccess. Reserves the campaign-duplicate abuse quota.
Parameters
400 workspace_required).Request body
application/json, optional.
Response 201
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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
}'{
"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"
}
}
}Pause sending, processing, and AI workflows together
/v1/campaigns/{campaignId}/pausecampaigns.pauseIdempotent. 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
400 workspace_required).Response 200
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
{
"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
/v1/campaigns/{campaignId}/resumecampaigns.resumeIdempotent. 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
400 workspace_required).Response 200
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
{
"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
/v1/campaigns/{campaignId}/launch-readinesscampaigns.get-launch-readinessReplaces 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
400 workspace_required).Response 200
data is a LaunchReadinessModel.
sending_estimate_unavailable warning).month when Ken.Scraper.API.Canonical.LaunchReadinessModel.ContactLimit and the counts below cover one month, else total.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"
{
"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
/v1/campaigns/{campaignId}/workflow-readinesscampaigns.get-workflow-readinessReplaces 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
400 workspace_required).Response 200
data is a WorkflowReadinessModel.
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"
{
"success": true,
"data": {
"canStart": true,
"checks": [
{
"code": "string",
"message": "string",
"isPassed": true
}
]
}
}Count a campaign's contacts by email security gateway and mailbox host
/v1/campaigns/{campaignId}/mail-providerscampaigns.get-mail-providersNeeds the security_gateways feature on your workspace. Until it is on, calls answer 404.
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
400 workspace_required).Response 200
data is a CampaignMailProvidersModel.
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"
{
"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
/v1/campaigns/{campaignId}/weekly-reportcampaigns.get-weekly-reportweekOf 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
400 workspace_required).Response 200
data is a CampaignWeeklyReportModel.
UTC.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"
{
"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
/v1/campaigns/{campaignId}/inboxescampaigns.list-inboxesRequires ReadAccess. A campaign outside the workspace is not_found.
Parameters
400 workspace_required).Response 200
data is a list of InboxModel. Each item has these fields. The response also carries pagination.
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.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.availableForCampaignId lists.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.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.pending, when InboxKit took the photo.failed.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.ken_mail (campaign mailboxes) or ken_workspace (a member's own mailbox). Omitted for SMTP and unconnected inboxes.availableForCampaignId lists.<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.inbox.connection.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).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"
{
"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
/v1/campaigns/{campaignId}/inboxescampaigns.replace-inboxesFull 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
400 workspace_required).Request body
application/json, optional.
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.
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.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.availableForCampaignId lists.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.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.pending, when InboxKit took the photo.failed.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.ken_mail (campaign mailboxes) or ken_workspace (a member's own mailbox). Omitted for SMTP and unconnected inboxes.availableForCampaignId lists.<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.inbox.connection.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).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
}'{
"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
/v1/campaigns/{campaignId}/linkedin-accountscampaigns.list-linkedin-accountsRequires ReadAccess. A campaign outside the workspace is not_found.
Parameters
400 workspace_required).Response 200
data is a list of LinkedInAccountModel. Each item has these fields. The response also carries pagination.
visit, invite, message, inmail, withdraw, follow, reaction, comment or endorse. Omitted with it.status.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.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"
{
"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
/v1/campaigns/{campaignId}/linkedin-accountscampaigns.replace-linkedin-accountsFull 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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a list of LinkedInAccountModel. Each item has these fields. The response also carries pagination.
visit, invite, message, inmail, withdraw, follow, reaction, comment or endorse. Omitted with it.status.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.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
}'{
"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
/v1/campaigns/{campaignId}/linkedin-settingscampaigns.get-linkedin-settingsReplaces GET /v1/campaigns/{id}/linkedin-config. A campaign outside the workspace is not_found. Requires ReadAccess.
Parameters
400 workspace_required).Response 200
data is a LinkedInSettingsModel.
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"
{
"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
/v1/campaigns/{campaignId}/linkedin-settingscampaigns.replace-linkedin-settingschannelPolicy 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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a LinkedInSettingsModel.
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
}'{
"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
/v1/campaigns/{campaignId}/scoringcampaigns.get-scoringReplaces POST /v1/campaigns/{id}/scoring for reads. A campaign outside the workspace is not_found. Requires ReadAccess.
Parameters
400 workspace_required).Response 200
data is a ScoringSettingsModel.
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"
{
"success": true,
"data": {
"isEnabled": true
}
}Turn lead scoring on or off for a campaign
/v1/campaigns/{campaignId}/scoringcampaigns.replace-scoringRequires WriteAccess. A campaign outside the workspace is not_found before the write.
Parameters
400 workspace_required).Request body
application/json, optional.
Response 200
data is a ScoringSettingsModel.
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
}'{
"success": true,
"data": {
"isEnabled": true
}
}List the AI workflows attached to a campaign
/v1/campaigns/{campaignId}/ai-workflowscampaigns.list-ai-workflowsIds: 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
400 workspace_required).Response 200
data is a list of CampaignAiWorkflowModel. Each item has these fields. The response also carries pagination.
qualification, personalization, rewriting, segmentation, scoring, or line_check.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"
{
"success": true,
"data": [
{
"id": 812,
"flow": "string"
}
],
"pagination": {
"nextCursor": "eyJpZCI6ODE1fQ",
"hasMore": true,
"total": 1,
"isTotalCapped": true
}
}Replace the AI workflows attached to a campaign
/v1/campaigns/{campaignId}/ai-workflowscampaigns.replace-ai-workflowsFull 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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a list of CampaignAiWorkflowModel. Each item has these fields. The response also carries pagination.
qualification, personalization, rewriting, segmentation, scoring, or line_check.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
]
}'{
"success": true,
"data": [
{
"id": 812,
"flow": "string"
}
],
"pagination": {
"nextCursor": "eyJpZCI6ODE1fQ",
"hasMore": true,
"total": 1,
"isTotalCapped": true
}
}Score contacts in a campaign
/v1/campaigns/{campaignId}/scoring/runcampaigns.run-scoringThe 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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a JobModel.
inbox_import job. Passwords are never returned.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).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.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
}'{
"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
/v1/campaigns/{campaignId}/enrichment-runscampaigns.create-enrichment-runReplaces 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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a JobModel.
inbox_import job. Passwords are never returned.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).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.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
}'{
"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
/v1/campaigns/{campaignId}/retry-enrichmentcampaigns.retry-enrichmentDeletes 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
400 workspace_required).Response 200
data is a RetryEnrichmentModel.
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"
{
"success": true,
"data": {
"companyStatusesCleared": 1,
"scrapeFailuresCleared": 1,
"contactStatusesCleared": 1
}
}Restart a campaign's processing
/v1/campaigns/{campaignId}/restartcampaigns.restartRe-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
400 workspace_required).Response 200
data is a CampaignModel.
month (new qualified contacts with a Safe email per UTC calendar month), a lifetime total when it is total.month or total, what Ken.Scraper.API.Canonical.CampaignModel.ContactLimit counts.monthly_limit, contact_limit, fair_use or lead_credits. Omitted otherwise.campaign.valid_contacts_found). See Ken.Scraper.API.Canonical.CampaignModel.LeadCount./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.campaign.status (pipeline/source), not the collapsed lifecycle string.sending_status. Sending is sending while Ken.Scraper.API.Canonical.CampaignModel.Status is active.none when the flags value is 0.campaign_send_settings flag on for the workspace; this field is the stored value either way.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.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.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.fr-FR, es-MX). Empty: none.formal or informal, over the pack's default. Empty: the packs' defaults.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.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).sales or recruiting. Set at create; PATCH refuses it.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.ready. Changing it needs the launch permission. Always on the wire.sourceStatus is the campaign's status there at the last sync (active, paused, ... or unknown).maxEmailsPerDay, the sending days, window and time zone, and the step waits.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.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"
{
"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
/v1/campaigns/{campaignId}/sender-routingcampaigns.get-sender-routingDEV-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
400 workspace_required).Response 200
data is a SenderRoutingModel.
PUT /v1/campaigns/{campaignId}/inboxes (allInboxes: true, or inboxIds).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"
{
"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
/v1/campaigns/{campaignId}/sender-routingcampaigns.replace-sender-routingDEV-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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a SenderRoutingModel.
PUT /v1/campaigns/{campaignId}/inboxes (allInboxes: true, or inboxIds).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
]
}
]
}'{
"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
/v1/campaigns/{campaignId}/recipient-providerscampaigns.list-recipient-providersDEV-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
400 workspace_required).Response 200
data is a RecipientProviderCountsModel.
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"
{
"success": true,
"data": {
"providers": [
{
"slug": "string",
"name": "string",
"kind": "string",
"leadCount": 1
}
],
"pendingCount": 1,
"noProviderCount": 1,
"isPartial": true
}
}