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

# End-user data requests: erase or export one end user

> Erase or export the trace data of one of your application's end users, by user id or session id, in one project — for GDPR and similar data subject requests.

When someone who uses **your** application asks you to delete or hand over their data, you can erase or export everything Zespan holds for them in a project. The person is identified by the `userId` or `sessionId` your SDK or OpenTelemetry spans carry — see [Context enrichment](https://docs.zespan.com/sdk/typescript.md#context-enrichment) for how those ids are set.

This is different from your own Zespan account. To export or delete **your** account, see [Privacy](https://docs.zespan.com/account/privacy.md).

## Endpoints

These endpoints authenticate with your dashboard session, not a project API key, and are refused while a Zespan staff member is impersonating you.

| Method | Path | Required permission | Response |
| - | - | - | - |
| `POST` | `/v1/projects/{projectId}/end-users/erase` | `projects:manage` (Owner, Admin) | `202 { "requestId" }` |
| `POST` | `/v1/projects/{projectId}/end-users/export` | `traces:export` (Owner, Admin, Editor) | `202 { "requestId" }` |
| `GET` | `/v1/projects/{projectId}/end-users/requests/{requestId}` | The permission for that request's kind | Request status, below |

Both `POST` bodies take exactly one of `userId` or `sessionId` — a non-blank string of at most 256 characters, matched exactly as your application sent it:

```json theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
{ "userId": "user_8f2c1a" }
```

Sending both, neither, or any other field returns `400`. You can make up to **10 requests per hour per project**, across both kinds.

### Request status

`GET` returns:

| Field | Description |
| - | - |
| `requestId` | The request's id. |
| `kind` | `erase` or `export`. |
| `subjectType` | `user` or `session`. |
| `subjectHash` | A one-way hash of the id. The raw id is never returned. |
| `status` | `pending` → `processing` → `completed` or `failed`. |
| `counts` | What was found and removed, including `notErased` and `skipped` for an erase (see [What is not erased](#what-is-not-erased)). |
| `failureReason` | Why a request failed, if it did. |
| `createdAt`, `completedAt`, `expiresAt` | Timestamps. |
| `url`, `expiresInSeconds` | For a completed, unexpired export only: a fresh download link valid for 5 minutes. Each call creates a new link; it is never emailed or stored. |
| `expired` | `true` when the export's download window has passed; no `url` is returned. |

### Errors

| Status | Code | When |
| - | - | - |
| 400 | — | Not exactly one of `userId` or `sessionId`, blank, or longer than 256 characters. |
| 403 | `IMPERSONATION_NOT_ALLOWED` | The session is a staff impersonation session. |
| 403 | `FORBIDDEN` | Your role lacks `projects:manage` (erase) or `traces:export` (export). |
| 404 | `NOT_FOUND` | The project isn't in your organization, or the request id belongs to another project. |
| 409 | `REQUEST_IN_PROGRESS` | The same kind of request for the same end user is already pending or processing. The existing `requestId` is returned. |
| 409 | `ERASE_IN_PROGRESS` | You asked for an export while an erase of the same end user is pending or processing. |
| 429 | `RATE_LIMIT` | More than 10 requests per hour for this project. Honour `Retry-After`. |
| 503 | `QUEUE_UNAVAILABLE` | The request could not be queued. Retry. |
| 503 | `STORAGE_UNAVAILABLE` | Exports are unavailable, or the download link could not be created. |

## What an erase removes

Requests run in the background; an erase on a large project can take a few minutes. In that project only, Zespan deletes:

* The end user's spans and events — by `userId` or `sessionId`, and every trace they appear in
* Prompt usage records and, for a user erase, per-user cost rollups
* The evaluation scores and errors, outcomes, MCP tool spans, guardrail events, LLM errors, and policy signals of those traces and sessions
* The analysis results recorded for those traces, such as findings and attributions. An export includes the same results
* Older copies of the trace tables, where a deployment still keeps them. These copies are not included in an export
* Earlier exports of the same end user

For **30 days** afterwards, newly ingested events carrying that `userId` (or `sessionId`) in that project are dropped, so late SDK flushes and replays don't bring the data back. Suppression follows the kind of id: erasing user `x` drops later events whose user id is `x`, not events whose session id happens to be `x`.

Outcomes are matched by session id only, because an outcome carries no user id: after you erase a **session**, an outcome reported with that `sessionId` is dropped for those 30 days, and the outcome response reports how many were dropped (`dropped`). After you erase a **user**, later outcomes are not dropped, even ones that name one of that user's sessions, and an outcome that carries only a trace id can't be matched either way; both are stored. To keep a user's late outcomes out, also erase their sessions, or stop reporting outcomes for them.

## What an export contains

An export is one JSON file with a section per kind of record. If a section couldn't be read, the file says so (`partial: true` and the list of `failedSections`). It stays available for **7 days** through the `GET` endpoint. An export of an end user erased in the last 30 days fails with `failureReason: "erased"`.

## What is not erased

* **Records you curated that copy trace content**: dataset items, dataset run items, regression replay results, evaluation runs and their results, annotation queue items, issues (their affected-trace lists), guardrail executions, and agent-to-agent tasks. A completed erase reports how many of each reference the end user in `counts.notErased`, so you can review and remove them in the dashboard.
* **Incident timelines**, which aren't stored per user (reported in `counts.skipped`).
* **Per-user cost rollups on a session erase**, because they have no session key.
* **Backups**, which are not edited. They expire on their own schedule (database backups after 30 days, trace-store backups after 42 days), and completed erasures are re-applied if a backup is ever restored.

> [!WARNING]
>
> **Session ids must be unique per conversation.** Session-based deletion removes every row of every session the end user appeared in — for a user erase, every session id seen with that `userId`. If your application reuses session ids across users (a constant, empty-like, or shared value), erasing one user also removes other users' data in those sessions.

> [!TIP]
>
> Guardrail records are written a few seconds after each check. If an erase runs immediately after live traffic for the same user, run it again a minute later to catch anything written in between.

## Next steps

* [Privacy](https://docs.zespan.com/account/privacy.md) — export or delete your own Zespan account
* [Data retention and deletion](https://docs.zespan.com/platform/data-retention.md) — how long trace data is kept
* [PII redaction](https://docs.zespan.com/sdk/pii-redaction.md) — keep personal data out of traces in the first place


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.