---
title: Integration Keys
excerpt: Create, list, inspect, update and delete the CRM integration keys of a customer account.
order: 8
---

An **integration key** holds the credentials CX-Engine uses to connect a customer account to a third-party system (Salesforce, HubSpot, Dynamics, ConnectWise, a webhook…).

All endpoints in this section are **workspace-scoped**, require the `client` OAuth scope and act on a single customer account, selected with the `customer_account` query parameter (see [Selecting the customer account](/en/docs/cx-api/smart-routings#selecting-the-customer-account)).

**Headers:** `Authorization: Bearer {workspace-token}`

> **Credentials are never returned.** Secrets are stored encrypted and are not part of any API response. The `has_keys` attribute only tells you whether credentials are stored.

---

## Permissions

| Endpoint | Required permission |
|---|---|
| `GET /api/integration-keys` | `customer-integration.view` |
| `GET /api/integration-keys/{id}` | `customer-integration.view` |
| `POST /api/integration-keys` | `customer-integration.create` |
| `PUT /api/integration-keys/{id}` | `customer-integration.edit` |
| `DELETE /api/integration-keys/{id}` | `customer-integration.delete` |

The authenticated user must also have access to the selected customer account.

---

## The integration key object

```json
{
  "id": 7,
  "uid": "70c7ee70-3e75-4dd3-a728-f435af607db4",
  "integration_id": null,
  "outsource_provider": "salesforce",
  "customer_id": 12,
  "keyable_type": "customer",
  "keyable_id": 12,
  "enabled": true,
  "requires_action": false,
  "name": "Salesforce production",
  "type": "oauth",
  "created_at": "2025-02-10T10:12:00.000000Z",
  "updated_at": "2025-02-10T10:15:31.000000Z",
  "has_keys": true
}
```

| Field | Description |
|---|---|
| `outsource_provider` | Connected system: `active_campaign`, `atera`, `bluerocktel`, `connectwise`, `dynamics`, `glpi`, `hubspot`, `neoteem`, `salesforce`, `sellsy`, `webhook` |
| `type` | Authentication type: `none`, `token`, `app_token`, `oauth`, `oauth_pat`, `oauth_client_credentials`, `oauth_azure`, `password`, `basic`, `connectwise`, `oauth_password`, `header_token` |
| `uid` | Identifier of the OAuth flow, for OAuth-based keys |
| `requires_action` | `true` when the OAuth authorization still has to be completed |
| `enabled` | Whether the key is used |
| `has_keys` | Whether credentials are stored for this key |
| `integration_id` | Legacy integration identifier, may be `null` |

---

## GET /api/integration-keys

Returns all integration keys of the selected customer as a plain JSON array (not paginated).

**Query parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `customer_account` | string | yes | Customer account code |

**Response `200`** — array of integration key objects

---

## GET /api/integration-keys/{id}

Returns a single integration key.

**Query parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `customer_account` | string | yes | Customer account code |

**Response `200`** — integration key object

**Response `404`** — the key does not belong to the selected customer

---

## POST /api/integration-keys

Connects a new integration key to the selected customer. The body must be a JSON object. The key is always attached to the customer selected with `customer_account`: `customer_id`, `keyable_id`, `keyable_type` and `integration_id` are ignored if sent.

**Query parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `customer_account` | string | yes | Customer account code |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `outsource_provider` | string | yes | Connected system (see the table below) |
| `type` | string | yes | Authentication type, must be one of the types allowed for the provider |
| `name` | string | yes | Display name (2–255 characters) |
| `enabled` | boolean | no | Defaults to `true` |
| `keys` | object | depends on `type` | Credentials (see below) |

**Allowed `type` per provider**

| `outsource_provider` | Allowed `type` |
|---|---|
| `active_campaign` | `header_token` |
| `atera` | `token` |
| `bluerocktel` | `password` |
| `connectwise` | `connectwise` |
| `dynamics` | `oauth_azure`, `oauth_client_credentials` |
| `glpi` | `app_token` |
| `hubspot` | `oauth`, `token` |
| `neoteem` | `basic` |
| `salesforce` | `oauth` |
| `sellsy` | `oauth_pat` |
| `webhook` | `none`, `basic`, `token`, `password`, `oauth_client_credentials`, `oauth_password` |

**`keys` fields per `type`**

| `type` | Required fields | Optional fields |
|---|---|---|
| `none` | — | `api_url` |
| `token` | — | `api_url`, `access_token`, `refresh_token`, `key_placement` (`header` or `request_parameter`), `header_name` (required when `key_placement` is `request_parameter`), `header_prefix` |
| `header_token` | `api_url`, `access_token` | — |
| `app_token` | `api_url`, `client_id`, `client_secret` | — |
| `password`, `basic` | `user_login`, `user_password` | `api_url` |
| `connectwise` | `api_url`, `tenant_id`, `client_id`, `user_login`, `user_password` | — |
| `oauth_client_credentials` | `api_url`, `client_id`, `client_secret` | `tenant_id`, `scope` |
| `oauth_password` | `api_url`, `client_id`, `client_secret`, `user_login`, `user_password` | — |
| `oauth_pat` | `client_id`, `client_secret` | `expires_in`, `expires_at` |
| `oauth_azure` | `api_url` | — |
| `oauth` | — | — |

**Example**

```json
{
  "outsource_provider": "atera",
  "type": "token",
  "name": "Atera production",
  "keys": {
    "api_url": "https://app.atera.com/api/v3",
    "access_token": "your-atera-api-key"
  }
}
```

**Response `201`**

```json
{
  "entity": { "id": 8, "outsource_provider": "atera", "type": "token", "name": "Atera production", "enabled": true, "requires_action": false, "has_keys": true, "...": "..." },
  "action": null
}
```

For OAuth types (`oauth`, `oauth_azure`), the key is created with `requires_action: true` and `action` describes the authorization to complete: open `action.redirect_url` in a browser to grant access.

```json
{
  "entity": { "id": 9, "outsource_provider": "salesforce", "type": "oauth", "requires_action": true, "...": "..." },
  "action": {
    "type": "oauth",
    "provider": "salesforce",
    "uid": "70c7ee70-3e75-4dd3-a728-f435af607db4",
    "requires_action": true,
    "redirect_url": "https://login.salesforce.com/services/oauth2/authorize?..."
  }
}
```

**Response `400`** — validation failure: missing or unknown `outsource_provider`, `type` not allowed for the provider, missing `name`, or invalid `keys` for the type

```json
{
  "response": "bad request: please check body parameters.",
  "hint": ["The selected type is invalid."]
}
```

---

## PUT /api/integration-keys/{id}

Renames, enables or disables an integration key, or replaces its credentials. The body must be a JSON object.

**Query parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `customer_account` | string | yes | Customer account code |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no | Display name (2–255 characters) |
| `enabled` | boolean | no | Enable or disable the key |
| `keys` | object | no | New credentials, validated against the key's current `type` (same fields as for creation). They replace the stored credentials. Not accepted for OAuth keys (`oauth`, `oauth_azure`): delete and recreate the key instead |

The ownership, provider and type of a key cannot be changed: sending `customer_id`, `keyable_id`, `keyable_type`, `integration_id`, `outsource_provider`, `type`, `uid` or `requires_action` returns a `400`.

**Response `201`** — the updated integration key object

**Response `400`** — validation failure (forbidden field, invalid `keys` for the key type)

**Response `422`** — `keys` sent for an OAuth key

**Response `404`** — the key does not belong to the selected customer

---

## DELETE /api/integration-keys/{id}

Deletes an integration key.

**Query parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `customer_account` | string | yes | Customer account code |

**Response `200`**

```json
{ "response": "element deleted" }
```

**Response `404`** — the key does not belong to the selected customer
