> ## Documentation Index
> Fetch the complete documentation index at: https://docs.connectly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Remove Opt-Outs

> Re-enable contacts who consented again — reversing only the suppressions this API recorded ↩️

## Endpoint

```json theme={null}
POST https://api.connectly.ai/v1/businesses/{businessId}/remove/opt_outs
```

| Parameter    | Location | Description                 |
| ------------ | -------- | --------------------------- |
| `businessId` | Path     | Your Connectly business ID. |
| `X-API-Key`  | Header   | Your API key.               |

<Warning>
  **This reverses only what the Opt-Outs API suppressed.** A contact who replied STOP, tapped an unsubscribe button, turned marketing off in WhatsApp itself, or was uploaded through the Audience screen in Connectly stays suppressed. Those contacts are reported under `notOptedOutCount` and messages to them remain blocked.

  Your re-enable list is your own record of who consented with you. It is not evidence that someone who opted out on the channel changed their mind, so Connectly will not act on it as if it were.
</Warning>

***

## Request body

```json theme={null}
{
  "entries": [...]
}
```

### `entries` array (required)

Each object names one WhatsApp contact to re-enable. Maximum 10,000 per request.

| Field    | Type   | Required | Description                                                                                                                                                             |
| -------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client` | string | Yes\*    | Contact's phone number in E.164 format (e.g. `+14155558234`), with or without the leading `+`. \*Not required when `userId` is set.                                     |
| `userId` | string | No       | Business-Scoped User ID (e.g. `US.13491208655302741918`), exactly as Connectly sends it in webhooks. Read only when `client` is empty — if both are set, `client` wins. |

<Note>
  The same contact listed twice counts once.
</Note>

***

## Examples

<AccordionGroup>
  <Accordion title="Re-enable by phone number">
    ```json theme={null}
    {
      "entries": [
        { "client": "+14155558234" },
        { "client": "+14155559345" }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Re-enable by Business-Scoped User ID">
    ```json theme={null}
    {
      "entries": [
        { "userId": "US.13491208655302741918" }
      ]
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Response

The API returns HTTP `200` with a `data` object. Counts are over the contacts you named.

| Field              | Type           | Description                                                                                                                                                 |
| ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `removedCount`     | integer        | Contacts this request re-enabled.                                                                                                                           |
| `notOptedOutCount` | integer        | Contacts that carried no opt-out from this API, so nothing changed. A contact who opted out on the channel itself is counted here and **stays suppressed**. |
| `rejectedCount`    | integer        | Entries dropped as unreadable.                                                                                                                              |
| `rejected`         | array          | The dropped entries with their reasons, up to the first 100. Each carries `client` — the identifier exactly as you sent it — and `reason`.                  |
| `error`            | object \| null | Summary of why entries were rejected, with a count per reason. Absent when nothing was rejected.                                                            |

**Success response**

```json theme={null}
{
  "data": {
    "removedCount": 43,
    "notOptedOutCount": 9955,
    "rejectedCount": 2,
    "rejected": [
      { "client": "not-a-number", "reason": "not a valid identifier for this channel" },
      { "client": "", "reason": "client or userId is required" }
    ],
    "error": {
      "message": "rejected 2 of 10000 entries: not a valid identifier for this channel (1), client or userId is required (1)",
      "type": "ERROR_TYPE_INVALID_ARGUMENT",
      "code": "ERROR_CODE_BAD_REQUEST",
      "userTitle": "Bad request sent",
      "userMessage": "Request or param(s) detected."
    }
  }
}
```

<Note>
  A large `notOptedOutCount` is normal and is not an error. If you send your full consented list every night, every contact you never suppressed through this API lands there.
</Note>

***

## Error responses

Unreadable entries are dropped and counted — they do not fail the batch. The request fails outright only when no entry survives.

| HTTP status | Meaning                                                                   |
| ----------- | ------------------------------------------------------------------------- |
| `400`       | Request body is malformed, `entries` is empty, or no entry could be read. |
| `401`       | Missing or invalid API key.                                               |
| `429`       | Rate limit exceeded.                                                      |
| `500`       | Internal server error.                                                    |

Rejection reasons are the same as [Add opt-outs](https://docs.connectly.ai/opt-outs/add-opt-outs#rejection-reasons).

***

## Rate limiting

This endpoint is limited to **60 requests per minute**. Exceeding this returns HTTP `429 Too Many Requests`.
