# End-user deletions (/docs/api/reference/telemetry/end-user-deletions)



<Endpoint method="POST" path="/v1/projects/:projectId/end-user-deletions" auth="Bearer" scope="events:write" phase="1" />

<Endpoint method="GET" path="/v1/projects/:projectId/end-user-deletions/:deletionId" auth="Bearer" scope="events:read" phase="1" />

Erases one end user's measurement data for one project. Use it to fulfil a data-subject deletion request, and for the deletion obligations in measurement partner terms (Snap attribution terms, Google Ads App Attribution Partner requirements). Studio members can file the same request in MMA under **Settings, Studio**.

The erasure is irreversible. `POST` answers `202` with the request; the erasure runs in the background, and `GET` reports its status. A request stays `running` until every store that holds user-level rows is erased, including the measurement pipeline.

## What is erased [#what-is-erased]

Layers resolves the identifier to the installs, app user ids and SDK device ids linked to it in this project, then:

* **Deletes** the user's SDK events, sessions, app-user profile, device links, install attribution, reattributions, attributed clicks, revenue identity links and SDK health beacons.
* **Irreversibly anonymizes** rows that feed reported totals: install channel assignments, event and session rollup rows, cohort membership and revenue events. The identifier is replaced by a one-way token whose key is discarded when the erasure completes. Revenue events keep their amount, currency and dates; the customer id, email, transaction ids and raw store payload are removed.
* **Keeps** aggregates. Daily totals, cohort curves, ROAS and spend already computed keep their counts and are not restated. A day that is still inside a refresh window can drop by this user's contribution when that window next refreshes.

An identifier that links to more than 100 installs or app users is refused with `status: "failed"`. That fan-out means a shared or placeholder id, and erasing it would erase other people.

If the app keeps running on the user's device, the SDK sends new events after the erasure. Stop collection on the device (the SDK's consent controls or `reset()`) before you file the request.

## Create a deletion [#create-a-deletion]

<Parameters
  title="Path"
  rows="[
  { name: 'projectId', type: 'string', required: true, description: 'Project ID.' },
]"
/>

<Parameters
  title="Body"
  rows="[
  { name: 'identifierKind', type: 'string', required: true, description: 'One of `install_id`, `app_user_id`, `idfa`, `gaid`, `idfv`, `device_id` (the SDK device id). `idfa`, `gaid` and `idfv` match case-insensitively.' },
  { name: 'identifierValue', type: 'string', required: true, description: 'The identifier exactly as your app sends it. 1 to 512 characters.' },
]"
/>

Send an `Idempotency-Key` header. A repeat request for the same identifier while one is open returns the open request.

```bash
curl -X POST "https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/end-user-deletions" \
  -H "Authorization: Bearer lp_..." \
  -H "Idempotency-Key: 6f1c2b7e-dsr-42" \
  -H "Content-Type: application/json" \
  -d '{ "identifierKind": "app_user_id", "identifierValue": "user_a7f3c1d9" }'
```

<Response status="202" description="Accepted">
  ```json
  {
    "deletion": {
      "id": "eud_9b1f6c0e-2a47-4c1b-8f0e-5d2c7a31b8e4",
      "projectId": "prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39",
      "identifierKind": "app_user_id",
      "status": "pending",
      "requestedAt": "2026-09-25T18:02:11Z",
      "completedAt": null,
      "error": null
    }
  }
  ```
</Response>

## Get a deletion [#get-a-deletion]

```bash
curl "https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/end-user-deletions/eud_9b1f6c0e-2a47-4c1b-8f0e-5d2c7a31b8e4" \
  -H "Authorization: Bearer lp_..."
```

| `status`    | Meaning                                      | Your next action                                                                                                                                                                                 |
| ----------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pending`   | Accepted, not started.                       | Poll again.                                                                                                                                                                                      |
| `running`   | Erasure in progress.                         | Poll again; a minute apart is enough.                                                                                                                                                            |
| `completed` | Every store is erased. `completedAt` is set. | Record completion. Stop polling.                                                                                                                                                                 |
| `failed`    | Stopped. `error` says why.                   | Fix the cause and submit again. When the Postgres store completed and only the ClickHouse store failed, the same request (same `id`) is reopened for ClickHouse; otherwise a new request starts. |

The response never echoes the identifier value. Layers keeps a SHA-256 hash of it on the request record.

## Errors [#errors]

| Status | Code         | When                                                                               |
| ------ | ------------ | ---------------------------------------------------------------------------------- |
| `404`  | `NOT_FOUND`  | The project or deletion is not in your organization.                               |
| `422`  | `VALIDATION` | Unknown `identifierKind`, empty value, an extra field, or a malformed deletion id. |
| `503`  | `INTERNAL`   | The request is recorded but could not start. Submit it again to retry.             |
