# Deleting accounts

Let people delete their own account after a grace period you set, and see what a deletion removes and what it keeps.

People can delete their own account from the Security screen of your app's settings. They get 7 days to change their mind unless you set another grace period, and then their personal data is scrubbed. Not soft-deleted with every field still sitting in the row: the name, address, phone, sign-in methods and devices go. The user record itself stays, renamed "Deleted user", so the activity and audit rows that point at it still resolve to something.

Step by step, with screenshots: [The account security screens](/sign-in-setup/account-security-screens).

```tsx
import { useSaaSAuth } from '@buildbase/sdk/react';

function DeletionNotice() {
  const { user, openWorkspaceSettings } = useSaaSAuth();
  if (!user?.deleteAfter) return null;

  const date = new Date(user.deleteAfter).toLocaleDateString();
  return (
    <p>
      Your account will be deleted on {date}.{' '}
      <button onClick={() => openWorkspaceSettings('security')}>Cancel</button>
    </p>
  );
}
```

## What people see

The Security screen ends with a **Delete account** section.

- **A typed confirmation.** The person types the word the section asks for: `DELETE`, or its translation in their language.
- **The warning names your grace period** above it ("You have 7 days to change your mind"), or, at 0, says the account goes straight away and they will be signed out.
- **A recent sign-in.** A session older than 10 minutes is asked to [confirm it's you](/authentication/reverification) first. A guest is not asked.
- **Every other session ends** the moment they ask. This one stays signed in, so they can come back and cancel.
- **Until the date**, the section shows when the deletion runs, with **Cancel deletion**. Signing in during the grace period is allowed, for exactly that reason.
- **With a grace period of 0**, the account is deleted at once and this session ends with it.

Hide the section with `ui.settings.security.deleteAccount: false` if your app handles deletion its own way.

## The grace period

Set it under **User Management → Access → Authentication → Security**, in the **Account deletion** card: from 0 to 30 days, and 7 when you never set it. 0 deletes the moment the person asks. It is stored as `security.accountDeletionGraceDays` on the auth config.

A sweep runs every 10 minutes and deletes each account whose date has passed.

## Workspaces they created

A workspace with other people in it cannot vanish because one of them left. Deleting is refused with `409 owns_workspaces` while the person created a workspace that someone else is a member of, or that has a subscription. The refusal names each one (`workspaces: [{ id, name }]`) and the Security screen lists them.

Nothing hands a workspace to someone else, so the person clears the way themselves: they remove the other members and cancel the subscription, or delete the workspace, then ask again. The refusal's `message` says so, naming the workspace when there is one:

> The workspace "Acme" has other members or a subscription, so it cannot be deleted with this account. Remove the other members and cancel the subscription, or delete the workspace, then try again.

With several, it counts them instead: "3 workspaces have other members or a subscription, so they cannot be deleted with this account."

A workspace they created and use alone is deleted with the account, and their memberships in other people's workspaces end.

The rule is checked again when the grace period runs out. If someone joined one of their workspaces in the meantime, the deletion is held, logged and tried again at the next sweep. It stays scheduled until it goes through or the person cancels.

## What goes and what stays

Deleted outright:

- passwords and every other sign-in record, connected accounts and wallets, passkeys and authenticator apps
- devices and sessions, so the person is signed out everywhere
- agent consents and OAuth refresh tokens
- push subscriptions and preferences
- their audience contact and its list memberships

Cleared from the user record: the phone, username, image, tags, attributes, custom claims, external id, Stripe customer id, ad attribution, language, country, currency and timezone. The name becomes "Deleted user" and the address `<userId>@deleted.invalid`, which is unique and can never receive mail.

Kept: activity, email history and audit records. They are keyed by the user ID, and the person behind it is gone.

## Deleting a user from the console

An admin can delete a user from their page in the console with **Delete**, then **Delete user now**. That one is immediate, with no grace period: the grace period is for a person who changed their mind, and this person never asked. It runs the same scrub under the same workspace rule, emits `user.deleted` with `by: 'admin'`, and writes a `user.deleted` row to the security audit log.

Over the API it is `DELETE /api/users/:id`, with an org API token whose role can delete users. It answers `{ success: true }`, or `409 owns_workspaces` with the workspaces.

## Events

| Event                     | When                                  | `data`                             |
| ------------------------- | ------------------------------------- | ---------------------------------- |
| `user.deletion_scheduled` | The person asked, with a grace period | `actorId`, `userId`, `deleteAfter` |
| `user.deletion_cancelled` | They cancelled before the date        | `actorId`, `userId`                |
| `user.deleted`            | The account was scrubbed              | `actorId`, `userId`, `by`          |

`by` is `self` (the person asked, at once or after the grace period), `admin` (deleted from the console) or `guest_expired` (a [guest](/authentication/guests) left idle too long). With a grace period of 0 there is no `user.deletion_scheduled`, only `user.deleted`.

None of them carries the old address, because by then there is none to send. Match on `userId`. The SDK types the payload as `UserDeletionWebhookData`. Subscribe to any of them as a [webhook event](/webhooks/overview).

## The API

For a screen of your own, the same calls are on `WorkspaceApi`:

```ts
const { deleteAfter, deleted } = await api.requestAccountDeletion();
// deleted is true when your app has no grace period: the account is gone.
await api.cancelAccountDeletion(); // { cancelled }
```

| Route                                       | Answers                                                                                  |
| ------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `POST /api/v1/public/account/delete`        | `{ deleteAfter, deleted }`. `deleteAfter` is `null` when the account was deleted at once |
| `POST /api/v1/public/account/delete/cancel` | `{ cancelled }`, `false` when nothing was scheduled                                      |

| Refusal                   | When                                                                                    |
| ------------------------- | --------------------------------------------------------------------------------------- |
| `403` `reauth_required`   | The session is not recent. See [Confirm it's you](/authentication/reverification)       |
| `409` `owns_workspaces`   | They created a workspace that has other members or a subscription. Carries `workspaces` |
| `403` `read_only_session` | An app owner is [signed in as the person](/authentication/sign-in-as), read-only        |

While a deletion is scheduled, the profile and `useSaaSAuth().user` carry `deleteAfter`, ISO 8601.

## Next steps

- [Confirm it's you](/authentication/reverification) - the recent sign-in a deletion asks for.
- [Guest users](/authentication/guests) - guests left idle are removed by the same scrub.
- [Webhooks](/webhooks/overview) - subscribe to the three deletion events.
