# Deal contacts

Manage the deal-to-contacts links (many-to-many). The `contactIds` field on the deal itself is read-only — `PATCH /v1/deals/:id` does not change it. To link, replace, or unlink contacts, use the endpoints below. To read the contacts together with the deal, use `GET /v1/deals/:id?include=contact` (see "Including related entities").

Requires the `crm` scope.

## Link fields

| Field | Type | Description |
|----------|-----|---------|
| `contactId` | number | Contact ID. Catalog: `GET /v1/contacts` |
| `isPrimary` | boolean | Primary contact of the deal. If none is flagged, the first becomes primary |
| `sort` | number | Sort order |
| `roleId` | number | Contact role ID (when used) |

## Get deal contacts

`GET /v1/deals/:id/contacts`

Returns the array of linked contacts.

```bash
curl "https://vibecode.bitrix24.com/v1/deals/741/contacts" \
  -H "X-Api-Key: YOUR_API_KEY"
```

Response:

```json
{
  "success": true,
  "data": [
    { "contactId": 9, "sort": 10, "isPrimary": true, "roleId": 0 },
    { "contactId": 17, "sort": 20, "isPrimary": false, "roleId": 0 }
  ]
}
```

## Add a single contact

`POST /v1/deals/:id/contacts`

Links one contact to the deal without touching the already-linked ones.

| Parameter | Type | Required | Description |
|----------|-----|:-----:|---------|
| `contactId` | number | yes | Contact ID |
| `isPrimary` | boolean | no | Make it primary |
| `sort` | number | no | Sort order |

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/deals/741/contacts" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactId": 9, "isPrimary": true, "sort": 10 }'
```

Returns the updated deal contact list (as in `GET`).

## Replace the whole contact list

`PUT /v1/deals/:id/contacts`

Fully replaces the deal contact list. Contacts not present in `items` are unlinked. Pass `"items": []` to unlink all.

| Parameter | Type | Required | Description |
|----------|-----|:-----:|---------|
| `items` | array | yes | Array of contacts (an empty array unlinks all) |
| `items[].contactId` | number | yes | Contact ID |
| `items[].isPrimary` | boolean | no | Primary contact |
| `items[].sort` | number | no | Sort order |

```bash
curl -X PUT "https://vibecode.bitrix24.com/v1/deals/741/contacts" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "contactId": 16530, "isPrimary": true, "sort": 10 },
      { "contactId": 16532, "isPrimary": false, "sort": 20 }
    ]
  }'
```

Returns the updated deal contact list (as in `GET`).

## Delete a single contact

`DELETE /v1/deals/:id/contacts/:contactId`

Unlinks one contact. If the primary one is removed, the first remaining becomes primary.

```bash
curl -X DELETE "https://vibecode.bitrix24.com/v1/deals/741/contacts/16530" \
  -H "X-Api-Key: YOUR_API_KEY"
```

Returns `204 No Content`.

## Errors

| Code | When |
|-----|-------|
| `INVALID_PARAMS` | In `PUT`, the `items` field is not an array, or an item has no `contactId` |
| `SCOPE_DENIED` | The key has no `crm` scope |
| `WRITE_BLOCKED_READONLY_KEY` | The key is in READONLY mode — writes (`POST`/`PUT`/`DELETE`) are blocked |
