Kenso

Campaign localizations

The Campaign localizations endpoints of the Kenso API.

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

List the campaign's localized steps

GET/v1/campaigns/{campaignId}/localizationscampaign-localizations.list

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

Try it on developer.ken.so

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

Name
In
Type
Required
Description
campaignId
In: path
integer (int64)
required
Language
In: query
string
optional
One locale, such as fr-FR, in any casing.
StepId
In: query
integer (int64)
optional
One email or LinkedIn step id. An email and a LinkedIn step can share an id: add channel to pick one.
Channel
In: query
string
optional
email or linkedin.
Ken-Version
In: header
string, one of: 2026-11-02, 1.1
required
The API version: a dated version (YYYY-MM-DD), or 1.1, another name for the first dated version. Without it the request uses your API key's pinned version; an unpinned key reaches the legacy shape of the same path. 1.0 selects the legacy shape explicitly.
X-Ken-Workspace-Id
In: header
integer (int64)
optional
Workspace to act in. Required when your credential can reach more than one workspace (otherwise 400 workspace_required).

Response 200

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

Field
Type
Description
id
integer (int64)
channel
string
stepId
integer (int64)
stepOrder
integer (int32)
variant
string
language
string
subjectLine
string
bodyContent
string
status
string
generating
boolean
sourceHash
string
stale
boolean
packVersion
string
mirrorLanguage
string
mirrorSubject
string
mirrorBody
string
qaScore
integer (int32)
qaReport
CampaignLocalizationQaReportModel
The quality check of a localization: whether it passed, and the rule and judge findings.
editedByHuman
boolean
approvedByUserId
integer (int64)
approvedAt
string (date-time)
lastError
string
modifiedAt
string (date-time)
Request
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"
Response 200
{
  "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

POST/v1/campaigns/{campaignId}/localizationscampaign-localizations.generate

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

Try it on developer.ken.so

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

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

Request body

application/json, optional.

Field
Type
Required
Description
languages
array of string
optional
Locales the campaign enables, such as fr-FR.
stepIds
array of integer (int64)
optional
Email or LinkedIn step ids of the campaign. An email and a LinkedIn step can share an id: add channel to pick one.
channel
string
optional
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.

Field
Type
Description
id
integer (int64)
channel
string
stepId
integer (int64)
stepOrder
integer (int32)
variant
string
language
string
subjectLine
string
bodyContent
string
status
string
generating
boolean
sourceHash
string
stale
boolean
packVersion
string
mirrorLanguage
string
mirrorSubject
string
mirrorBody
string
qaScore
integer (int32)
qaReport
CampaignLocalizationQaReportModel
The quality check of a localization: whether it passed, and the rule and judge findings.
editedByHuman
boolean
approvedByUserId
integer (int64)
approvedAt
string (date-time)
lastError
string
modifiedAt
string (date-time)
Request
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"
}'
Response 202
{
  "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

POST/v1/campaigns/{campaignId}/localizations/{localizationId}/approvecampaign-localizations.approve

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

Try it on developer.ken.so

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

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

Response 200

data is a CampaignLocalizationModel.

Field
Type
Description
id
integer (int64)
channel
string
stepId
integer (int64)
stepOrder
integer (int32)
variant
string
language
string
subjectLine
string
bodyContent
string
status
string
generating
boolean
sourceHash
string
stale
boolean
packVersion
string
mirrorLanguage
string
mirrorSubject
string
mirrorBody
string
qaScore
integer (int32)
qaReport
CampaignLocalizationQaReportModel
The quality check of a localization: whether it passed, and the rule and judge findings.
editedByHuman
boolean
approvedByUserId
integer (int64)
approvedAt
string (date-time)
lastError
string
modifiedAt
string (date-time)
Request
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"
Response 200
{
  "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

POST/v1/campaigns/{campaignId}/localizations/{localizationId}/regeneratecampaign-localizations.regenerate

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

Try it on developer.ken.so

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

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

Request body

application/json, optional.

Field
Type
Required
Description
instruction
string
optional
What to change, in a sentence or two, such as Shorter, and less formal. Up to 2,000 characters.

Response 202

data is a CampaignLocalizationModel.

Field
Type
Description
id
integer (int64)
channel
string
stepId
integer (int64)
stepOrder
integer (int32)
variant
string
language
string
subjectLine
string
bodyContent
string
status
string
generating
boolean
sourceHash
string
stale
boolean
packVersion
string
mirrorLanguage
string
mirrorSubject
string
mirrorBody
string
qaScore
integer (int32)
qaReport
CampaignLocalizationQaReportModel
The quality check of a localization: whether it passed, and the rule and judge findings.
editedByHuman
boolean
approvedByUserId
integer (int64)
approvedAt
string (date-time)
lastError
string
modifiedAt
string (date-time)
Request
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"
}'
Response 202
{
  "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

PATCH/v1/campaigns/{campaignId}/localizations/{localizationId}campaign-localizations.update

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

Try it on developer.ken.so

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

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

Request body

application/json, optional.

Field
Type
Required
Description
subjectLine
string
optional
The localized subject. An email localization only.
bodyContent
string
optional

Response 200

data is a CampaignLocalizationModel.

Field
Type
Description
id
integer (int64)
channel
string
stepId
integer (int64)
stepOrder
integer (int32)
variant
string
language
string
subjectLine
string
bodyContent
string
status
string
generating
boolean
sourceHash
string
stale
boolean
packVersion
string
mirrorLanguage
string
mirrorSubject
string
mirrorBody
string
qaScore
integer (int32)
qaReport
CampaignLocalizationQaReportModel
The quality check of a localization: whether it passed, and the rule and judge findings.
editedByHuman
boolean
approvedByUserId
integer (int64)
approvedAt
string (date-time)
lastError
string
modifiedAt
string (date-time)
Request
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"
}'
Response 200
{
  "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"
  }
}