# Handle data subject erasure (DSAR)

Erase one user's data across a workspace and confirm the request finished.

A request erases one workspace. To erase a person from several workspaces, compute their pseudonym in each workspace with that workspace's key, and submit one request to each workspace's privacy-api.

The request lifecycle and every field are in the [Privacy API reference](/reference/platform/privacy-api/#dsar-routes).

## Prerequisites

- Enterprise installed (`enterprise.enabled: true`). The DSAR routes are served in dev mode, so a license is not required. See [Licensing & Features](/explanation/platform/licensing/).
- `spec.privacy` set on the Workspace, so the operator deploys privacy-api.
- The user's pseudonymized id (`virtualUserId`) in this workspace. Sessions, memories and requests are keyed by it, so a raw email or user id matches nothing. Each workspace has its own pseudonym key, so the same person has a different id in each workspace. The id is the first 16 hex characters of a hash of the identifier the facade hashed.

  With no pseudonym key, the hash is SHA-256, the same in every workspace:

  ```bash
  printf %s '<raw-user-id>' | shasum -a 256 | cut -c1-16
  ```

  With a key, the hash is HMAC-SHA256 keyed with the workspace's key. The operator keeps it in the Secret `omnia-workspace-pseudonym-key` in the workspace namespace:

  ```bash
  export OMNIA_PSEUDONYM_HMAC_KEY="$(kubectl --context <ctx> -n <workspace-namespace> \
    get secret omnia-workspace-pseudonym-key -o jsonpath='{.data.key}' | base64 -d)"
  printf %s '<raw-user-id>' | openssl dgst -sha256 -hmac "$OMNIA_PSEUDONYM_HMAC_KEY" -r | cut -c1-16
  ```

  If you cannot read Secrets in the workspace namespace, derive the key from the install key and the scope pinned on the workspace namespace instead. See [Compute a workspace key](/reference/platform/privacy-api/#compute-a-workspace-key). An install with no key has no `omnia-workspace-pseudonym-key` Secret.

  Identifiers that start with `system:` are always hashed with plain SHA-256.

  session-api, memory-api and privacy-api each bind their database to their workspace key's fingerprint and refuse to start under another. privacy-api sends its fingerprint with every erasure, and a session-api or memory-api bound to another key refuses it with `409 pseudonym_key_mismatch`, so the request fails instead of reporting success with nothing deleted. See [Pseudonym key](/reference/platform/privacy-api/#pseudonym-key).

- A way to reach privacy-api. Its Service is ClusterIP only: `privacy-<workspace>` on port 8080, in the namespace named by the Workspace's `spec.namespace.name`. Forward it locally:

  ```bash
  kubectl port-forward -n <workspace-namespace> svc/privacy-<workspace> 8087:8080
  ```

- A bearer token, when `internalServiceAuth.enabled` is true (the chart default). privacy-api then requires a ServiceAccount token with audience `omnia-session-api` from a ServiceAccount in the workspace namespace:

  ```bash
  export SA_TOKEN=$(kubectl create token default -n <workspace-namespace> --audience omnia-session-api)
  ```

  With `internalServiceAuth.enabled` set to false, drop the `Authorization` header from the commands below.

## Submit the request

1. Send `POST /api/v1/privacy/deletion-request`. The call returns `202 Accepted` and processing continues in the background.

   ```bash
   curl -sS -X POST http://localhost:8087/api/v1/privacy/deletion-request \
     -H "Authorization: Bearer $SA_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
           "virtualUserId": "<pseudonymized-user-id>",
           "reason": "gdpr_erasure",
           "scope": "all"
         }'
   ```

   The request erases all of the user's sessions, media and memories in the workspace. privacy-api cannot erase by date, so a `date_range` scope or a `dateFrom` or `dateTo` field returns `400`. See the [request fields](/reference/platform/privacy-api/#dsar-routes).

2. Copy the `id` from the response. Its `status` is `pending`.

## Track the request

Send `GET /api/v1/privacy/deletion-request/{id}`:

```bash
curl -sS http://localhost:8087/api/v1/privacy/deletion-request/<id> \
  -H "Authorization: Bearer $SA_TOKEN"
```

`status` moves from `pending` to `in_progress` and ends at `completed` or `failed`. The response includes `sessionsDeleted` and an `errors` array.

To list every request for one user, newest first, send `GET /api/v1/privacy/deletion-requests?virtualUserId=<id>`. The alias `virtual_user_id` also works.

## Verify

The request is done when `status` is `completed` and `errors` is empty. Illustrative response:

```json
{
  "id": "<request-id>",
  "virtualUserId": "<pseudonymized-user-id>",
  "status": "completed",
  "sessionsDeleted": 12,
  "errors": []
}
```

privacy-api writes `deletion_requested` when it accepts the request and `deletion_completed` or `deletion_failed` when it ends. The rows are in the `audit_log` table of the privacy-api database, with `source_service` `privacy-api-dsar`. See the [Privacy API reference](/reference/platform/privacy-api/) for the rest of the privacy surface.

## Troubleshooting

### The request is `failed`

Read `errors`. Each entry names the service group and the tier that failed, for example `group <name> sessions: ...` or `group <name> memory: ...`. Data erased before the failure stays erased. Fix the cause, then submit a new request for the same user; a finished request is not retried.

If `errors` reads `no service group to erase from`, privacy-api found no session-api or memory-api URL in the Workspace status when it started. Check `status.services` on the Workspace, then restart privacy-api so it reads the status again.

### The request is `completed` with `sessionsDeleted` 0

Check that `virtualUserId` is the pseudonym and not the raw identifier.

### The API returns `401`

`internalServiceAuth.enabled` is true and the token is missing, expired, has the wrong audience, or belongs to a ServiceAccount outside the workspace namespace.

### The API returns `404`

privacy-api serves the DSAR routes only when Enterprise is installed. Check that the `privacy-<workspace>` Service exists, which needs `spec.privacy` on the Workspace.

## Related pages

- [Privacy API reference](/reference/platform/privacy-api/)
- [Manage user consent](/how-to/privacy/manage-user-consent/)
- [Licensing & Features](/explanation/platform/licensing/)