---
title: "Request account deletion (asynchronous finalize)"
method: DELETE
path: "/account"
tags: ["Account"]
---

# Request account deletion (asynchronous finalize)

`DELETE /account`

## Request

- Base URL: `https://api.percher.run`
- URL: `https://api.percher.run/account`
- Auth: HTTP bearer

Fail-closed account-delete request. Flow:

1. Confirmation gate (`?confirm=<your-email>`).
2. Stale-pending outgoing transfers are swept (flipped to `expired`) so they don't show up as live blockers.
3. The server re-runs `/account/deletion-preview` inside a `BEGIN IMMEDIATE` transaction. If any blocker remains → 409 with `error.blockers`/`error.ownedAppCount` and an `ACCOUNT_HAS_*` code (see below). The caller resolves blockers via the existing routes (`DELETE /apps/:ref`, `share/revoke`, `transfer/cancel`, etc.) and retries.
4. On a clean account: `users.status` flips to `deleting`, `deletion_requested_at` + `deletion_blocker_snapshot` get stamped, **API tokens are revoked server-side in the same transaction** (so the caller's next request returns 401), the `account.delete_requested` audit row is written, and an `account_delete` job is enqueued. Response: **202** `{ status: 'deleting', finalizeJobId }`.

The finalizer (`account_delete` job handler) runs `AccountDeletionService.runFinalize()` off the request path: re-validates blockers; cleans up `github_installations`, incoming `shared_access` rows keyed on the user's email, and pending incoming `app_transfers` to that email; throwing-inserts the `account.deleted` audit row; then DELETE FROMs the user row gated on `status='deleting'`. If a blocker reappears between the route's accept and the finalizer running, the finalizer flips status back to `active` and emits `account.delete_aborted` — the only path where `users.status` moves backward.

**Error codes (409):** `ACCOUNT_HAS_APPS`, `ACCOUNT_HAS_COLLABORATORS`, `ACCOUNT_HAS_PENDING_INVITES`, `ACCOUNT_HAS_ACTIVE_PR_PREVIEWS`, `ACCOUNT_HAS_PENDING_TRANSFERS`, `ACCOUNT_HAS_DEPLOYS_IN_PROGRESS`, `ACCOUNT_HAS_ACTIVE_RESTORE`. Plus `ACCOUNT_DELETION_IN_PROGRESS` (concurrent delete or status already flipped). Auth-middleware also raises `ACCOUNT_DELETION_IN_PROGRESS` (403) for any further request from a `deleting` user.

## Query parameters

- `confirm` string, required
- `acknowledge` 'forfeit-export-window'

## Response `202`

Deletion accepted; finalization runs asynchronously. Body: `{ status: 'deleting', finalizeJobId: string }`. API tokens are revoked server-side in the same transaction — the caller's next request will 401.

## Other responses

- `400`: Missing or mismatched confirmation
- `401`: Unauthorized
- `403`: `ACCOUNT_DELETION_IN_PROGRESS` from auth-middleware (the user is already in delete pipeline) — bearer token rejected before reaching the route handler.
- `409`: Blockers prevent deletion. Body: `{ error: { code, message, blockers, ownedAppCount } }`. `code` is one of the `ACCOUNT_HAS_*` codes listed above (the first blocker kind); `blockers` is the full list so the caller can render every category in one round.
- `503`: Platform is in sunset read-only mode (at or past T-0). Mutating routes are frozen (`platform_read_only`); app/account delete is gated (`platform_delete_gated`) unless the dashboard password-reauth override is supplied.

---

[API](https://skmtc.dev/percher/apis/percher-api.md) · [All operations](https://skmtc.dev/percher/apis/percher-api/llms.txt) · [OpenAPI document](https://skmtc.dev/percher/apis/percher-api/revisions/d9e7089c21e5?raw)
