> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ada.cx/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ada.cx/_mcp/server.

# Bulk End-User Deletion API

The Bulk End-User Deletion API erases all personal data associated with one or more end users across Ada's systems. A single request can carry up to 1000 identifiers and is processed as an asynchronous job. Submit a request to receive a `job_id`, then poll the job to track progress and review the outcome for each identifier.

Download OpenAPI spec

This API supports compliance with data deletion requirements under applicable laws, such as GDPR.

This API replaces the single-record [Data Compliance API](/reference/data-compliance/overview), which is now deprecated. Use the Bulk End-User Deletion API for all end-user data deletion.

## Identifiers

Each request supplies a list of identifiers. Every identifier has a `type`, a `value`, and — for `variable_match` only — a `variable` naming the stored variable to match against.

| Type             | Description                                                                                                                                                             |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`          | Matches end users by email address. Values are normalized and matched case-insensitively.                                                                               |
| `chatter_id`     | Matches by a legacy Ada end-user record ID.                                                                                                                             |
| `external_id`    | Matches by the ID you assign to an end user. Values are trimmed and lowercased at intake.                                                                               |
| `end_user_id`    | Matches by Ada's end-user identifier.                                                                                                                                   |
| `variable_match` | Matches every end user whose **meta variable** named `variable` currently equals `value` — for example an end user's `email`, `language`, or any custom `metadata` key. |

> **Info**
>
> **What `variable_match` matches:** `variable` names a **meta variable** on the end user. When you set a profile through the [End Users API](/reference/end-users/overview), both the profile fields (`first_name`, `last_name`, `display_name`, `avatar`, `email`, `language`) and every key in the `metadata` object are stored as meta variables — `variable_match` can target any of them. It matches on the end user's latest value for that variable (exact match). Global or session variables set during a conversation are not matched.

> **Info**
>
> **Limits.** A single request accepts at most **1000 identifiers**, and the request body may be at most **512 KB** — a larger body returns `413`. Each identifier may match at most **1000 end users** — this ceiling is shared by `variable_match` and by the fan-out of an `email` match. An identifier that matches more than 1000 end users is reported as `exceeds_limit` and erases nothing; a `variable_match` that matches zero end users is reported as `not_found`.

## Dispositions

The job records a disposition for every submitted identifier. Inspect the per-identifier dispositions to understand exactly what was erased.

| Disposition     | Meaning                                                                                        |
| --------------- | ---------------------------------------------------------------------------------------------- |
| `accepted`      | The identifier matched end users who were queued for erasure.                                  |
| `not_found`     | No end users matched the identifier.                                                           |
| `invalid`       | The identifier was malformed or unusable. `reason` is populated.                               |
| `exceeds_limit` | The identifier matched more than 1000 end users, so nothing was erased. `reason` is populated. |
| `duplicate`     | The identifier repeated another identifier in the same request.                                |

Each disposition also reports `matched_chatters`, the number of end users matched by that identifier. A non-accepted disposition may carry a `reason` explaining the outcome: it is always set for `invalid` and `exceeds_limit`, and for `not_found` on ID-based identifiers, but can be null for a `not_found` `email` or `variable_match` selector — so don't rely on `reason` being present based on `disposition` alone.

## Job lifecycle

A job moves through the following states:

| Status        | Meaning                                                                                                             |
| ------------- | ------------------------------------------------------------------------------------------------------------------- |
| `queued`      | The job was accepted and is waiting to be processed. Non-terminal.                                                  |
| `in_progress` | Erasure is underway. Non-terminal.                                                                                  |
| `completed`   | Every matched end user was erased.                                                                                  |
| `partial`     | Not every identifier was accepted or fully erased. This includes the case where **zero** identifiers were accepted. |
| `failed`      | The job could not be processed. `failure_reason` is set.                                                            |

Because `partial` covers a range of outcomes — including none accepted — do not rely on the top-level status alone. Inspect the per-identifier `disposition` array to see what happened to each identifier.

On a `failed` job, `failure_reason` carries an opaque, machine-readable diagnostic. Its values are subject to change, so treat it as a signal rather than a stable contract; to understand outcomes per identifier, use each identifier's `disposition` and `reason`.

> **Note**
>
> `accepted` and `completed` mean different things. An `accepted` disposition means an identifier's end users were queued and submitted to Ada's erasure engine — not that they are erased yet. The job stays `in_progress` while the engine erases across every data store, and only reaches `completed` once Ada confirms that erasure finished. Because the engine runs downstream on a daily cycle, reaching `completed` can take up to 30 days. This delay is expected behavior.

## Resubmitting a request

Two independent windows govern what happens when the same identifier is submitted more than once. They have different lengths and different effects.

### 24-hour job idempotency

Set `client_reference` to make a request idempotent. An identical request — the same `client_reference` and the same set of identifiers — resubmitted within 24 hours returns the original job instead of creating a new one.

Requests that omit `client_reference` are not deduplicated. Each one creates a new job, even when it is identical to a request sent moments earlier.

### 7-day identifier cooldown

The erasure engine rejects an identifier that was already submitted within the previous 7 days. This window is separate from the 24-hour idempotency above, and it applies whether or not `client_reference` is set.

Because the two windows differ in length, a resubmission can fall between them. A request repeated after three days is outside the 24-hour idempotency window, so it creates a new job — but its identifiers are still inside the 7-day cooldown, so the engine rejects them and the job ends `failed`.

A single identifier in cooldown can fail an entire job. The engine evaluates a request as a unit and does not report which identifier conflicted, so unless the earlier request provably covered everything in the new one, the whole job fails rather than risk silently skipping identifiers the earlier request did not cover.

To integrate safely against both windows:

* Set `client_reference` on every request.
* Poll job status rather than treating the `202` as confirmation that erasure finished.
* Track which identifiers have been submitted, and wait 7 days before resubmitting one.

## Rate limit

The Bulk End-User Deletion API is subject to the [global rate limits](/reference/introduction/limits).