CRM connections
Connect a CRM and manage the connection.
List the workspace's CRM connection
/v1/crm-connectionscrm-connections.listNeeds the ingestion feature on your workspace. Until it is on, calls answer 404.
At most one live connection (connected, needs_reconnect or paused). Empty when none.
Parameters
400 workspace_required).Response 200
data is a list of CrmConnectionModel. Each item has these fields. The response also carries pagination.
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"
{
"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
/v1/crm-connectionscrm-connections.createNeeds the ingestion feature on your workspace. Until it is on, calls answer 404.
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
400 workspace_required).Request body
application/json, optional.
Response 201
Also answers 200. data is a CrmConnectionModel.
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"
}'{
"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
/v1/crm-connections/oauth/completecrm-connections.complete-oauthNeeds the ingestion feature on your workspace. Until it is on, calls answer 404.
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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a CrmOAuthCompletionModel.
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"
}'{
"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
/v1/crm-connections/{connectionId}crm-connections.getNeeds the ingestion feature on your workspace. Until it is on, calls answer 404.
Any status, disconnected included. Another workspace's id is not_found.
Parameters
400 workspace_required).Response 200
data is a CrmConnectionModel.
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"
{
"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
/v1/crm-connections/{connectionId}crm-connections.updateNeeds the ingestion feature on your workspace. Until it is on, calls answer 404.
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
400 workspace_required).Request body
application/json, optional.
Response 200
data is a CrmConnectionModel.
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
}
}
}'{
"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
/v1/crm-connections/{connectionId}crm-connections.deleteNeeds the ingestion feature on your workspace. Until it is on, calls answer 404.
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
400 workspace_required).Response 204
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"
HTTP/1.1 204 No Content
Preview what turning sync on would do
/v1/crm-connections/{connectionId}/previewcrm-connections.previewNeeds the ingestion feature on your workspace. Until it is on, calls answer 404.
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
400 workspace_required).Response 200
data is a CrmPreviewModel.
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"
{
"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"
}
}