Campaign localizations
The Campaign localizations endpoints of the Kenso API.
List the campaign's localized steps
/v1/campaigns/{campaignId}/localizationscampaign-localizations.listNeeds the multilingual_outreach feature on your workspace. Until it is on, calls answer 404.
One item per live email or LinkedIn step and language, email first, then by step order, variant and language. language keeps one locale (any casing); stepId keeps the rows of the email or LinkedIn step with that id; channel (email or linkedin) keeps one channel's rows, and picks one when an email and a LinkedIn step share an id. stale is true when the row is marked stale, or was written from older step text or an older market pack: generate it again. generating is true while the localization job has it queued. status is draft, approved, stale, needs_review or failed. One page: hasMore is always false. Requires the Campaigns page.
Parameters
fr-FR, in any casing.channel to pick one.email or linkedin.400 workspace_required).Response 200
data is a list of CampaignLocalizationModel. Each item has these fields. The response also carries pagination.
curl -X GET "https://api.ken.so/v1/campaigns/812/localizations" \ -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,
"channel": "string",
"stepId": 812,
"stepOrder": 1,
"variant": "string",
"language": "string",
"subjectLine": "string",
"bodyContent": "string",
"status": "string",
"generating": true,
"sourceHash": "string",
"stale": true,
"packVersion": "string",
"mirrorLanguage": "string",
"mirrorSubject": "string",
"mirrorBody": "string",
"qaScore": 1,
"qaReport": {
"passed": true,
"errors": [
{
"code": "string",
"message": "string"
}
]
},
"editedByHuman": true,
"approvedByUserId": 812,
"approvedAt": "2026-09-23T15:02:11Z",
"lastError": "string",
"modifiedAt": "2026-09-23T15:02:11Z"
}
],
"pagination": {
"nextCursor": "eyJpZCI6ODE1fQ",
"hasMore": true,
"total": 1,
"isTotalCapped": true
}
}Queue the writing of the campaign's localized steps
/v1/campaigns/{campaignId}/localizationscampaign-localizations.generateNeeds the multilingual_outreach feature on your workspace. Until it is on, calls answer 404.
Body { "languages": ["fr-FR"], "stepIds": [11], "channel": "email" }, all optional: absent or empty means every language the campaign enables and every active email or LinkedIn step with text (not a Studio step, not an AI connection note). channel keeps one channel's steps, and picks one when an email and a LinkedIn step share an id. A missing localization is created as a draft; a stale, failed or needs-review one, and one written from older step text or an older market pack, is queued again. A current draft or approved localization, and one already queued, stay as they are. A localization a person edited is queued again only when stepIds names its step, so a new market pack never rewrites a person's text unasked. Answers 202 with every localization of the campaign, as the list does; the localization job writes the queued ones (generating true until it does). A language the campaign does not enable, a campaign with none, and a step with no text to localize are 400 validation_failed. While the localization job is not on for the workspace, nothing is queued: 409 invalid_state with detail localization_disabled. Requires Campaigns edit.
Parameters
400 workspace_required).Request body
application/json, optional.
fr-FR.channel to pick one.email or linkedin: only that channel's steps. Absent means both.Response 202
data is a list of CampaignLocalizationModel. Each item has these fields. The response also carries pagination.
curl -X POST "https://api.ken.so/v1/campaigns/812/localizations" \
-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 '{
"languages": [
"string"
],
"stepIds": [
812
],
"channel": "string"
}'{
"success": true,
"data": [
{
"id": 812,
"channel": "string",
"stepId": 812,
"stepOrder": 1,
"variant": "string",
"language": "string",
"subjectLine": "string",
"bodyContent": "string",
"status": "string",
"generating": true,
"sourceHash": "string",
"stale": true,
"packVersion": "string",
"mirrorLanguage": "string",
"mirrorSubject": "string",
"mirrorBody": "string",
"qaScore": 1,
"qaReport": {
"passed": true,
"errors": [
{
"code": "string",
"message": "string"
}
]
},
"editedByHuman": true,
"approvedByUserId": 812,
"approvedAt": "2026-09-23T15:02:11Z",
"lastError": "string",
"modifiedAt": "2026-09-23T15:02:11Z"
}
],
"pagination": {
"nextCursor": "eyJpZCI6ODE1fQ",
"hasMore": true,
"total": 1,
"isTotalCapped": true
}
}Approve a localized step for sending
/v1/campaigns/{campaignId}/localizations/{localizationId}/approvecampaign-localizations.approveNeeds the multilingual_outreach feature on your workspace. Until it is on, calls answer 404.
Only a current draft that passed its quality check is approved: its rules passed, and its judge score reached the market pack's threshold (a localization a person edited is approved on the rules alone). Otherwise 409 localization_not_approvable, with details[].codenot_draft, stale (written from older step text or an older market pack: generate it again), qa_failed or score_below_threshold. While the localization job has it queued, 409 localization_generating. Records the caller as the approver (none for an API key with no user) and the time. channel (email or linkedin) is needed only when an email and a LinkedIn localization of the campaign share the id (400 channel_required otherwise). Requires Campaigns edit.
Parameters
400 workspace_required).Response 200
data is a CampaignLocalizationModel.
curl -X POST "https://api.ken.so/v1/campaigns/812/localizations/812/approve" \ -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,
"channel": "string",
"stepId": 812,
"stepOrder": 1,
"variant": "string",
"language": "string",
"subjectLine": "string",
"bodyContent": "string",
"status": "string",
"generating": true,
"sourceHash": "string",
"stale": true,
"packVersion": "string",
"mirrorLanguage": "string",
"mirrorSubject": "string",
"mirrorBody": "string",
"qaScore": 1,
"qaReport": {
"passed": true,
"errors": [
{
"code": "string",
"message": "string"
}
]
},
"editedByHuman": true,
"approvedByUserId": 812,
"approvedAt": "2026-09-23T15:02:11Z",
"lastError": "string",
"modifiedAt": "2026-09-23T15:02:11Z"
}
}Write a localized step again
/v1/campaigns/{campaignId}/localizations/{localizationId}/regeneratecampaign-localizations.regenerateNeeds the multilingual_outreach feature on your workspace. Until it is on, calls answer 404.
Body { "instruction": "Shorter, and less formal." }, optional (up to 2,000 characters): the localization job rewrites the current text with it, or writes it anew without one. The localization becomes a draft (an approval is withdrawn, so a live campaign stops sending it: contacts routed to it are held or get the source text, as the campaign's language fallback says, until it is approved again) and generating is true until the job has written it. This rewrites text a person edited. Answers 202 with the localization. While the job has it queued, 409 localization_generating. A language the campaign no longer enables and a step with no text are 400 validation_failed. While the localization job is not on for the workspace, 409 invalid_state with detail localization_disabled. channel as for approve. Requires Campaigns edit.
Parameters
400 workspace_required).Request body
application/json, optional.
Shorter, and less formal. Up to 2,000 characters.Response 202
data is a CampaignLocalizationModel.
curl -X POST "https://api.ken.so/v1/campaigns/812/localizations/812/regenerate" \
-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 '{
"instruction": "string"
}'{
"success": true,
"data": {
"id": 812,
"channel": "string",
"stepId": 812,
"stepOrder": 1,
"variant": "string",
"language": "string",
"subjectLine": "string",
"bodyContent": "string",
"status": "string",
"generating": true,
"sourceHash": "string",
"stale": true,
"packVersion": "string",
"mirrorLanguage": "string",
"mirrorSubject": "string",
"mirrorBody": "string",
"qaScore": 1,
"qaReport": {
"passed": true,
"errors": [
{
"code": "string",
"message": "string"
}
]
},
"editedByHuman": true,
"approvedByUserId": 812,
"approvedAt": "2026-09-23T15:02:11Z",
"lastError": "string",
"modifiedAt": "2026-09-23T15:02:11Z"
}
}Edit a localized step's text
/v1/campaigns/{campaignId}/localizations/{localizationId}campaign-localizations.updateNeeds the multilingual_outreach feature on your workspace. Until it is on, calls answer 404.
Body { "subjectLine": "...", "bodyContent": "..." }; send one or both (a LinkedIn localization has no subject). The text is stored as sent, with the unsubscribe link text set to the market pack's, and checked by the rules with no model call: qaReport holds their result. editedByHuman becomes true and the localization a draft (an approval is withdrawn, so a live campaign stops sending it until it is approved again, as for regenerate); the English mirror and the judge score stay as they were, for the text before the edit. An edited draft whose rules pass can be approved. While the localization job has it queued, 409 localization_generating. A blank body, and a blank subject for a step that has one, are 400 validation_failed. While campaign images are on for the workspace, an email text with image tokens a sequence save would refuse (more than two, one in the subject or in a handwritten letter, or one not on its own line or in its own paragraph) is 400 validation_failed with the sequence save's message, and nothing is stored. channel as for approve. Requires Campaigns edit.
Parameters
400 workspace_required).Request body
application/json, optional.
Response 200
data is a CampaignLocalizationModel.
curl -X PATCH "https://api.ken.so/v1/campaigns/812/localizations/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 '{
"subjectLine": "string",
"bodyContent": "string"
}'{
"success": true,
"data": {
"id": 812,
"channel": "string",
"stepId": 812,
"stepOrder": 1,
"variant": "string",
"language": "string",
"subjectLine": "string",
"bodyContent": "string",
"status": "string",
"generating": true,
"sourceHash": "string",
"stale": true,
"packVersion": "string",
"mirrorLanguage": "string",
"mirrorSubject": "string",
"mirrorBody": "string",
"qaScore": 1,
"qaReport": {
"passed": true,
"errors": [
{
"code": "string",
"message": "string"
}
]
},
"editedByHuman": true,
"approvedByUserId": 812,
"approvedAt": "2026-09-23T15:02:11Z",
"lastError": "string",
"modifiedAt": "2026-09-23T15:02:11Z"
}
}