---
title: "Batch-patch lead identity on multiple enrollments"
method: POST
path: "/cadences/{id}/enrollments/bulk-relink"
tags: ["Cadences"]
---

# Batch-patch lead identity on multiple enrollments

`POST /cadences/{id}/enrollments/bulk-relink`

## Request

- Base URL: `https://anjal.app`
- URL: `https://anjal.app/cadences/{id}/enrollments/bulk-relink`
- Auth: API key in header `X-API-Key`

Batched form of `PATCH /v1/enrollments/{id}` for the common
recovery flow "we enrolled N leads with the wrong payload
shape; fix all of them at once."

Same merge semantics as the per-row PATCH (only fields present
in each update are touched; explicit `null` clears).

### Partial failure is allowed

A single bad row (terminal state, external-ID conflict,
not-found) does NOT fail the whole batch. The response
contains a `results[]` array mirroring the input order; each
slot is either `{ ok: true, enrollmentId, enrollment }` or
`{ ok: false, enrollmentId, code, error, detail }`. Response
status is always 200; inspect `results[i].ok` per row.

### Limits

- Max 100 updates per call. Iterate client-side for larger
  batches.
- Each `enrollmentId` must belong to the cadence in the path —
  rows pointing at a different cadence are rejected with
  `CADENCE_MISMATCH`.

### `?retryNow=true`

Applies to all rows in the batch. Same semantics as the per-row
endpoint — resets `next_step_due_at` on each successfully-
updated row to NOW so the runner re-attempts on its next tick.

### Worked example

Request:

```
POST /v1/cadences/01HY…/enrollments/bulk-relink?retryNow=true
Content-Type: application/json

{
  "updates": [
    {
      "enrollmentId": "01HZ…aaaa",
      "leadName": "Jane Doe",
      "leadLinkedinUrl": "https://www.linkedin.com/in/jane-doe-1a2b3c/"
    },
    {
      "enrollmentId": "01HZ…bbbb",
      "leadName": "John Smith",
      "leadLinkedinUrl": "https://www.linkedin.com/in/john-smith/"
    }
  ]
}
```

Response:

```json
{
  "ok": true,
  "succeeded": 2,
  "failed": 0,
  "results": [
    { "ok": true, "enrollmentId": "01HZ…aaaa", "enrollment": { … } },
    { "ok": true, "enrollmentId": "01HZ…bbbb", "enrollment": { … } }
  ]
}
```

With one row in a terminal state:

```json
{
  "ok": true,
  "succeeded": 1,
  "failed": 1,
  "results": [
    { "ok": true, "enrollmentId": "01HZ…aaaa", "enrollment": { … } },
    {
      "ok": false,
      "enrollmentId": "01HZ…bbbb",
      "code": "ENROLLMENT_TERMINAL_STATE",
      "error": "Enrollment is in terminal state 'replied'; lead identity cannot be modified.",
      "detail": { "state": "replied" }
    }
  ]
}
```

## Path parameters

- `id` string, uuid, required

## Query parameters

- `retryNow` boolean

## Request body

- object
  - `updates` object[], required
    - `enrollmentId` string, uuid, required
    - `leadName` string, nullable
    - `leadEmail` string, email, nullable
    - `leadLinkedinUrl` string, uri, nullable
    - `leadProfileUrn` string, nullable
    - `leadExternalId` string
    - `inboxId` string, uuid, nullable
    - `sessionId` string, uuid, nullable

## Response `200`

Per-row results. Inspect each `results[i].ok` — overall status code is 200 even when some rows failed.

- object
  - `ok` boolean
  - `succeeded` integer
  - `failed` integer
  - `results` object[]
    - `ok` boolean
    - `enrollmentId` string, uuid
    - `enrollment` object: Present when ok=true. Same shape as the per-row PATCH response.
    - `code` 'NOT_FOUND' | 'CADENCE_MISMATCH' | 'ENROLLMENT_TERMINAL_STATE' | 'LEAD_EXTERNAL_ID_CONFLICT' | 'PATCH_FAILED': Present when ok=false.
    - `error` string
    - `detail` object: Extra context — `{ state }` on terminal, `{ conflictingExternalId }` on ID conflict.

---

[API](https://skmtc.dev/anjal/apis/anjal-api.md) · [All operations](https://skmtc.dev/anjal/apis/anjal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/anjal/apis/anjal-api/revisions/a4044778e294?raw)
