Kenso

CRM connections

Connect a CRM and manage the connection.

CRM7 endpointsKen-Version: 2026-11-02AuthenticationWorkspacesErrors

List the workspace's CRM connection

GET/v1/crm-connectionscrm-connections.list

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

Try it on developer.ken.so

At most one live connection (connected, needs_reconnect or paused). Empty when none.

Parameters

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

Response 200

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

Field
Type
Description
id
integer (int64)
provider
string
status
string
pausedReason
string
externalAccountName
string
instanceUrl
string
connectedAsEmail
string
connectedAt
string (date-time)
disconnectedAt
string (date-time)
lastImportStartedAt
string (date-time)
lastImportCompletedAt
string (date-time)
lastInboundAt
string (date-time)
lastOutboundAt
string (date-time)
lastError
string
lastErrorAt
string (date-time)
syncObjects
map of CrmSyncObjectModel
settings
CrmSettingsModel
capabilities
array of string
Request
curl -X GET "https://api.ken.so/v1/crm-connections" \
  -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,
      "provider": "string",
      "status": "string",
      "pausedReason": "string",
      "externalAccountName": "string",
      "instanceUrl": "https://example.com",
      "connectedAsEmail": "[email protected]",
      "connectedAt": "2026-09-23T15:02:11Z",
      "disconnectedAt": "2026-09-23T15:02:11Z",
      "lastImportStartedAt": "2026-09-23T15:02:11Z",
      "lastImportCompletedAt": "2026-09-23T15:02:11Z",
      "lastInboundAt": "2026-09-23T15:02:11Z",
      "lastOutboundAt": "2026-09-23T15:02:11Z",
      "lastError": "string",
      "lastErrorAt": "2026-09-23T15:02:11Z",
      "syncObjects": {
        "key": {
          "enabled": true,
          "enabledAt": "2026-09-23T15:02:11Z"
        }
      },
      "settings": {
        "createRecordsWhen": "string",
        "salesforcePersonMode": "string",
        "emailLogging": "string",
        "pipelineExternalId": "string",
        "stageMap": {
          "key": "string"
        },
        "importDaily": true,
        "skipOpenOpportunities": true,
        "blockCustomers": true,
        "pauseOnRepActivityDays": 1,
        "staticListPerCampaign": true,
        "listSources": [
          {
            "listExternalId": "string",
            "campaignId": 812,
            "keepInSync": true
          }
        ],
        "inboundPeople": {
          "enabled": true,
          "filters": [
            {}
          ],
          "leadStatus": "string",
          "defaultOwnerMemberId": 812,
          "enabledAt": "2026-09-23T15:02:11Z"
        }
      },
      "capabilities": [
        "string"
      ]
    }
  ],
  "pagination": {
    "nextCursor": "eyJpZCI6ODE1fQ",
    "hasMore": true,
    "total": 1,
    "isTotalCapped": true
  }
}

Connect a CRM

POST/v1/crm-connectionscrm-connections.create

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

Try it on developer.ken.so

OAuth providers (hubspot, salesforce, ...) answer 200 with authorizeUrl: send the browser there, and finish with POST /v1/crm-connections/oauth/complete. ApiKey providers take apiKey (and baseUrl when self-hosted) and answer 201 with the connection. The CRM the workspace is already connected to can be authorized again (a lost token, a missing scope): the same CRM account keeps its connection. 402 plan_required on the Free plan, 409 crm_already_connected when another CRM is connected, 404 when the provider is not available to the workspace. Requires the integrations permission.

Parameters

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

Request body

application/json, optional.

Field
Type
Required
Description
provider
string
optional
apiKey
string
optional
baseUrl
string
optional
returnPath
string
optional

Response 201

Also answers 200. data is a CrmConnectionModel.

Field
Type
Description
id
integer (int64)
provider
string
status
string
pausedReason
string
externalAccountName
string
instanceUrl
string
connectedAsEmail
string
connectedAt
string (date-time)
disconnectedAt
string (date-time)
lastImportStartedAt
string (date-time)
lastImportCompletedAt
string (date-time)
lastInboundAt
string (date-time)
lastOutboundAt
string (date-time)
lastError
string
lastErrorAt
string (date-time)
syncObjects
map of CrmSyncObjectModel
settings
CrmSettingsModel
capabilities
array of string
Request
curl -X POST "https://api.ken.so/v1/crm-connections" \
  -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 '{
  "provider": "string",
  "apiKey": "string",
  "baseUrl": "https://example.com",
  "returnPath": "string"
}'
Response 201
{
  "success": true,
  "data": {
    "id": 812,
    "provider": "string",
    "status": "string",
    "pausedReason": "string",
    "externalAccountName": "string",
    "instanceUrl": "https://example.com",
    "connectedAsEmail": "[email protected]",
    "connectedAt": "2026-09-23T15:02:11Z",
    "disconnectedAt": "2026-09-23T15:02:11Z",
    "lastImportStartedAt": "2026-09-23T15:02:11Z",
    "lastImportCompletedAt": "2026-09-23T15:02:11Z",
    "lastInboundAt": "2026-09-23T15:02:11Z",
    "lastOutboundAt": "2026-09-23T15:02:11Z",
    "lastError": "string",
    "lastErrorAt": "2026-09-23T15:02:11Z",
    "syncObjects": {
      "key": {
        "enabled": true,
        "enabledAt": "2026-09-23T15:02:11Z"
      }
    },
    "settings": {
      "createRecordsWhen": "string",
      "salesforcePersonMode": "string",
      "emailLogging": "string",
      "pipelineExternalId": "string",
      "stageMap": {
        "key": "string"
      },
      "importDaily": true,
      "skipOpenOpportunities": true,
      "blockCustomers": true,
      "pauseOnRepActivityDays": 1,
      "staticListPerCampaign": true,
      "listSources": [
        {
          "listExternalId": "string",
          "campaignId": 812,
          "keepInSync": true
        }
      ],
      "inboundPeople": {
        "enabled": true,
        "filters": [
          {
            "field": "string",
            "op": "string",
            "value": "string"
          }
        ],
        "leadStatus": "string",
        "defaultOwnerMemberId": 812,
        "enabledAt": "2026-09-23T15:02:11Z"
      }
    },
    "capabilities": [
      "string"
    ]
  }
}

Finish an OAuth connect

POST/v1/crm-connections/oauth/completecrm-connections.complete-oauth

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

Try it on developer.ken.so

The CRM sends the browser to the web app's /crm/oauth/callback page, which posts the code, state and any provider error here as the signed-in user. Send X-Ken-Workspace-Id as the part of state before the first dot. Only the person who started the connect, in that workspace, within 10 minutes, can finish it: anything else is 400 invalid_state and changes nothing. Returns the connection, its provider and the returnPath sent with the connect. Errors: 400 provider_error (consent denied), 400 exchange_failed, 402 plan_required, 409 crm_already_connected, 409 crm_account_mismatch, 503 provider_unavailable. Requires the integrations permission.

Parameters

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

Request body

application/json, optional.

Field
Type
Required
Description
code
string
optional
state
string
optional
error
string
optional
errorDescription
string
optional
accountsServer
string
optional

Response 200

data is a CrmOAuthCompletionModel.

Field
Type
Description
provider
string
returnPath
string
connection
CrmConnectionModel
Request
curl -X POST "https://api.ken.so/v1/crm-connections/oauth/complete" \
  -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 '{
  "code": "string",
  "state": "string",
  "error": "string",
  "errorDescription": "string",
  "accountsServer": "string"
}'
Response 200
{
  "success": true,
  "data": {
    "provider": "string",
    "returnPath": "string",
    "connection": {
      "id": 812,
      "provider": "string",
      "status": "string",
      "pausedReason": "string",
      "externalAccountName": "string",
      "instanceUrl": "https://example.com",
      "connectedAsEmail": "[email protected]",
      "connectedAt": "2026-09-23T15:02:11Z",
      "disconnectedAt": "2026-09-23T15:02:11Z",
      "lastImportStartedAt": "2026-09-23T15:02:11Z",
      "lastImportCompletedAt": "2026-09-23T15:02:11Z",
      "lastInboundAt": "2026-09-23T15:02:11Z",
      "lastOutboundAt": "2026-09-23T15:02:11Z",
      "lastError": "string",
      "lastErrorAt": "2026-09-23T15:02:11Z",
      "syncObjects": {
        "key": {
          "enabled": true,
          "enabledAt": "2026-09-23T15:02:11Z"
        }
      },
      "settings": {
        "createRecordsWhen": "string",
        "salesforcePersonMode": "string",
        "emailLogging": "string",
        "pipelineExternalId": "string",
        "stageMap": {
          "key": "string"
        },
        "importDaily": true,
        "skipOpenOpportunities": true,
        "blockCustomers": true,
        "pauseOnRepActivityDays": 1,
        "staticListPerCampaign": true,
        "listSources": [
          {
            "listExternalId": "string",
            "campaignId": 812,
            "keepInSync": true
          }
        ],
        "inboundPeople": {
          "enabled": true,
          "filters": [
            {}
          ],
          "leadStatus": "string",
          "defaultOwnerMemberId": 812,
          "enabledAt": "2026-09-23T15:02:11Z"
        }
      },
      "capabilities": [
        "string"
      ]
    }
  }
}

Get a CRM connection

GET/v1/crm-connections/{connectionId}crm-connections.get

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

Try it on developer.ken.so

Any status, disconnected included. Another workspace's id is not_found.

Parameters

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

Response 200

data is a CrmConnectionModel.

Field
Type
Description
id
integer (int64)
provider
string
status
string
pausedReason
string
externalAccountName
string
instanceUrl
string
connectedAsEmail
string
connectedAt
string (date-time)
disconnectedAt
string (date-time)
lastImportStartedAt
string (date-time)
lastImportCompletedAt
string (date-time)
lastInboundAt
string (date-time)
lastOutboundAt
string (date-time)
lastError
string
lastErrorAt
string (date-time)
syncObjects
map of CrmSyncObjectModel
settings
CrmSettingsModel
capabilities
array of string
Request
curl -X GET "https://api.ken.so/v1/crm-connections/812" \
  -H "Authorization: Bearer $KEN_API_KEY" \
  -H "Ken-Version: 2026-11-02" \
  -H "X-Ken-Workspace-Id: $KEN_WORKSPACE_ID"
Response 200
{
  "success": true,
  "data": {
    "id": 812,
    "provider": "string",
    "status": "string",
    "pausedReason": "string",
    "externalAccountName": "string",
    "instanceUrl": "https://example.com",
    "connectedAsEmail": "[email protected]",
    "connectedAt": "2026-09-23T15:02:11Z",
    "disconnectedAt": "2026-09-23T15:02:11Z",
    "lastImportStartedAt": "2026-09-23T15:02:11Z",
    "lastImportCompletedAt": "2026-09-23T15:02:11Z",
    "lastInboundAt": "2026-09-23T15:02:11Z",
    "lastOutboundAt": "2026-09-23T15:02:11Z",
    "lastError": "string",
    "lastErrorAt": "2026-09-23T15:02:11Z",
    "syncObjects": {
      "key": {
        "enabled": true,
        "enabledAt": "2026-09-23T15:02:11Z"
      }
    },
    "settings": {
      "createRecordsWhen": "string",
      "salesforcePersonMode": "string",
      "emailLogging": "string",
      "pipelineExternalId": "string",
      "stageMap": {
        "key": "string"
      },
      "importDaily": true,
      "skipOpenOpportunities": true,
      "blockCustomers": true,
      "pauseOnRepActivityDays": 1,
      "staticListPerCampaign": true,
      "listSources": [
        {
          "listExternalId": "string",
          "campaignId": 812,
          "keepInSync": true
        }
      ],
      "inboundPeople": {
        "enabled": true,
        "filters": [
          {
            "field": "string",
            "op": "string",
            "value": "string"
          }
        ],
        "leadStatus": "string",
        "defaultOwnerMemberId": 812,
        "enabledAt": "2026-09-23T15:02:11Z"
      }
    },
    "capabilities": [
      "string"
    ]
  }
}

Update sync settings and switch sync objects on or off

PATCH/v1/crm-connections/{connectionId}crm-connections.update

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

Try it on developer.ken.so

Omitted fields keep their value. Turning an object on needs the first import completed (409 import_pending) and CRM writes enabled (409 crm_sync_disabled). pipelineExternalId must exist in the CRM; stageMap values must be live Ken opportunity stage keys. Requires the integrations permission.

Parameters

Name
In
Type
Required
Description
connectionId
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
settings
UpdateCrmSettingsBody
optional
syncObjects
map of CrmSyncObjectPatch
optional

Response 200

data is a CrmConnectionModel.

Field
Type
Description
id
integer (int64)
provider
string
status
string
pausedReason
string
externalAccountName
string
instanceUrl
string
connectedAsEmail
string
connectedAt
string (date-time)
disconnectedAt
string (date-time)
lastImportStartedAt
string (date-time)
lastImportCompletedAt
string (date-time)
lastInboundAt
string (date-time)
lastOutboundAt
string (date-time)
lastError
string
lastErrorAt
string (date-time)
syncObjects
map of CrmSyncObjectModel
settings
CrmSettingsModel
capabilities
array of string
Request
curl -X PATCH "https://api.ken.so/v1/crm-connections/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 '{
  "settings": {
    "createRecordsWhen": "string",
    "salesforcePersonMode": "string",
    "emailLogging": "string",
    "pipelineExternalId": "string",
    "stageMap": {
      "key": "string"
    },
    "importDaily": true,
    "skipOpenOpportunities": true,
    "blockCustomers": true,
    "pauseOnRepActivityDays": 1,
    "staticListPerCampaign": true,
    "listSources": [
      {
        "listExternalId": "string",
        "campaignId": 812,
        "keepInSync": true
      }
    ],
    "inboundPeople": {
      "enabled": true,
      "filters": [
        {
          "field": "string",
          "op": "string",
          "value": "string"
        }
      ],
      "leadStatus": "string",
      "defaultOwnerMemberId": 812
    }
  },
  "syncObjects": {
    "key": {
      "enabled": true
    }
  }
}'
Response 200
{
  "success": true,
  "data": {
    "id": 812,
    "provider": "string",
    "status": "string",
    "pausedReason": "string",
    "externalAccountName": "string",
    "instanceUrl": "https://example.com",
    "connectedAsEmail": "[email protected]",
    "connectedAt": "2026-09-23T15:02:11Z",
    "disconnectedAt": "2026-09-23T15:02:11Z",
    "lastImportStartedAt": "2026-09-23T15:02:11Z",
    "lastImportCompletedAt": "2026-09-23T15:02:11Z",
    "lastInboundAt": "2026-09-23T15:02:11Z",
    "lastOutboundAt": "2026-09-23T15:02:11Z",
    "lastError": "string",
    "lastErrorAt": "2026-09-23T15:02:11Z",
    "syncObjects": {
      "key": {
        "enabled": true,
        "enabledAt": "2026-09-23T15:02:11Z"
      }
    },
    "settings": {
      "createRecordsWhen": "string",
      "salesforcePersonMode": "string",
      "emailLogging": "string",
      "pipelineExternalId": "string",
      "stageMap": {
        "key": "string"
      },
      "importDaily": true,
      "skipOpenOpportunities": true,
      "blockCustomers": true,
      "pauseOnRepActivityDays": 1,
      "staticListPerCampaign": true,
      "listSources": [
        {
          "listExternalId": "string",
          "campaignId": 812,
          "keepInSync": true
        }
      ],
      "inboundPeople": {
        "enabled": true,
        "filters": [
          {
            "field": "string",
            "op": "string",
            "value": "string"
          }
        ],
        "leadStatus": "string",
        "defaultOwnerMemberId": 812,
        "enabledAt": "2026-09-23T15:02:11Z"
      }
    },
    "capabilities": [
      "string"
    ]
  }
}

Disconnect the CRM

DELETE/v1/crm-connections/{connectionId}crm-connections.delete

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

Try it on developer.ken.so

Stops every sync and deletes Ken's token. Ken's records, the CRM links and the CRM's own data stay. 204, also when already disconnected. Requires the integrations permission.

Parameters

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

Response 204

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

Preview what turning sync on would do

GET/v1/crm-connections/{connectionId}/previewcrm-connections.preview

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

Try it on developer.ken.so

Records matched by the import, CRM records Ken would create now under the create rule, and CRM field values Ken would change, with samples. Nothing is written. 409 import_pending until the first import finished.

Parameters

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

Response 200

data is a CrmPreviewModel.

Field
Type
Description
matched
CrmObjectCountsModel
read
CrmObjectCountsModel
wouldCreate
CrmWouldCreateModel
fieldUpdates
CrmFieldUpdatesModel
unmatchedOwners
array of CrmUnmatchedOwnerModel
importCompletedAt
string (date-time)
Request
curl -X GET "https://api.ken.so/v1/crm-connections/812/preview" \
  -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": {
    "matched": {
      "companies": 1,
      "contacts": 1,
      "deals": 1,
      "owners": 1
    },
    "read": {
      "companies": 1,
      "contacts": 1,
      "deals": 1,
      "owners": 1
    },
    "wouldCreate": {
      "contacts": 1,
      "companies": 1,
      "createRecordsWhen": "string",
      "complete": true,
      "excludedLibraryContacts": 1
    },
    "fieldUpdates": {
      "count": 1,
      "recordsChecked": 1,
      "complete": true,
      "samples": [
        {
          "entityRef": "string",
          "objectType": "string",
          "crmField": "string",
          "kenField": "string",
          "from": "string",
          "to": "string"
        }
      ]
    },
    "unmatchedOwners": [
      {
        "externalId": "string",
        "email": "[email protected]",
        "name": "string"
      }
    ],
    "importCompletedAt": "2026-09-23T15:02:11Z"
  }
}