Close accounts and erase eligible data
On this page
The supplied operator commands separate closing access from erasing local data. They run against the selected database; they do not verify a requester's identity, decide retention obligations or delete records at external providers. These are operator decisions made before execution.
There is no supplied self-service account-deletion screen, account-export command or complete personal-data export workflow. For an export request, verify the requester and define the data to return, including any custom tables and provider-held records. Build and review a scoped extraction and a secure delivery method for your application. Account closure is not a substitute for an export.
Prepare the request
Use the supported Node.js/npm environment and the intended installation's runtime configuration. Run from the application directory:
npm run data -- --help
npm run with-env -- /ABS/runtime.env data -- target
The help command prints syntax without connecting. target reports the mode, database and destination; compare these with the installation you intend to modify. The commands below use the same private runtime file. Do not source it in a shell.
Resolve the requester to the exact users.id UUID using a parameterized, read-only query against that database. For example, bind a verified email address as $1 in your database client:
SELECT id, email, closed_at
FROM users
WHERE lower(email) = lower($1);
Inspect the match privately. An email match alone is not proof that the requester controls the account. Keep personal data out of command arguments, logs and reports; the mutation commands use the UUID only.
If you added application fields or tables, review their relationships and erasure behavior before using these commands. The supplied SQL handles the supplied schema; it cannot infer the retention policy or cleanup for your extensions.
Before proceeding, decide whether you are closing access, erasing an already closed account, and whether eligible sole-member teams should also be erased. The CLI has no dry-run option: a confirmed mutation executes immediately after its transactional checks.
Close access
Replace both instances of USER_UUID with the same verified UUID:
npm run with-env -- /ABS/runtime.env data -- close-user --subject USER_UUID --confirm USER_UUID
The command prints an operation UUID with state: "selected" before connecting. Save that UUID; this first line does not establish that the change committed.
A committed closure marks the account closed, removes its sessions and verification records, removes affected invitations and local mail, and abandons the affected queued email payloads. Subsequent sign-in and use of old account links are refused. The profile, memberships, teams and projects remain for a separate erasure decision.
Check the recorded result:
npm run with-env -- /ABS/runtime.env data -- status OPERATION_UUID
Verify that the account can no longer use its former session. Closure does not cancel a Stripe subscription; nonterminal sole-member billing blocks the operation.
Erase an already closed account
Erasing a sole-member team removes its local link to the Stripe customer. Before
running the erasure command, save the references below in the private request
record so you can find that customer when handling the remaining Stripe data.
Bind the verified subject UUID as $1 in your database client:
SELECT b.team_id, b.account_id, b.livemode,
b.customer_id, b.checkout_id, b.subscription_id
FROM stripe_billing b
JOIN memberships m ON m.team_id = b.team_id
WHERE m.user_id = $1::uuid AND m.role = 'owner'
AND NOT EXISTS (
SELECT 1 FROM memberships other
WHERE other.team_id = m.team_id AND other.user_id <> m.user_id
);
Record the installation alongside these references. Use account_id and
livemode (true for live, false for test) to select the matching Stripe
account and environment.
Also find customers from earlier Checkout attempts using the saved team UUID in
their team metadata. Check the Stripe accounts and test/live environments used
for that team, and record those customer IDs for the provider follow-up.
For an account with no eligible sole-member teams:
npm run with-env -- /ABS/runtime.env data -- erase-user --subject USER_UUID --confirm USER_UUID
If the request also covers deleting teams where this user is the owner and the only member, explicitly include:
npm run with-env -- /ABS/runtime.env data -- erase-user --subject USER_UUID --confirm USER_UUID --erase-solo-teams
Erasure removes the local user and eligible sole-member teams, including their projects. It preserves shared teams and other members' data. Without --erase-solo-teams, the command refuses erasure when such teams exist. The flag is accepted only for erase-user, not closure.
Save this operation's UUID and inspect its status as above. Erasure does not prevent someone from registering a new account with the same email address; the new account does not recover the erased identity, teams or old links.
Resolve a refusal
Resolve the reported blocker below, then rerun the command so it can check the current data inside the transaction.
| Refusal | What to resolve |
|---|---|
| Shared owner | A user owns a team with other members. Arrange a reviewed ownership change before closure; the command does not choose a successor. The supplied UI has no ownership-transfer workflow. |
| Subscription or Checkout not terminal | Resolve or cancel the original Stripe work as appropriate, synchronize billing and process outstanding webhook jobs. Sole-member teams must have a confirmed terminal state without outstanding billing leases or reconciliation failures. |
| Email may be in flight or accepted | Inspect email operations, allow current dispatch to finish and resolve uncertain delivery. Retain attempt and provider records to determine whether mail was already sent. |
| Close the account first | Complete close-user before erasure. |
| Sole-member team confirmation required | Decide whether deleting those teams and projects is within the request; only then add --erase-solo-teams. |
| More than 1,000 rows in a selected category | Review and implement an extension suited to that account's data volume. The command refuses rather than partially processing it. |
Resume after an interruption
An unconfirmed result or lost terminal output can occur around commit. Inspect the saved operation UUID first:
npm run with-env -- /ABS/runtime.env data -- status OPERATION_UUID
If needed, retry the original action with the same UUID and exactly the same subject and sole-member-team choice:
npm run with-env -- /ABS/runtime.env data -- close-user --subject USER_UUID --confirm USER_UUID --operation OPERATION_UUID
For an erasure retry, use erase-user and preserve the original presence or absence of --erase-solo-teams. A matching committed request returns its recorded result with replay: true. Reusing an operation UUID for different parameters is refused.
Interpret the result and finish the request
| Status | Meaning |
|---|---|
retained |
Access is closed; the local profile and teams remain for a separate erasure decision. |
local-complete |
Local erasure committed. Shared data, operation history and abuse/suppression records remain. |
provider-pending |
The local step committed, but provider records require separate operator handling. |
not-recorded |
No committed operation with this UUID exists in the selected database. Recheck the target and original operation before retrying. |
provider-pending does not trigger automatic deletion at Stripe or Resend, and the CLI has no provider-completion action. Track that remaining work in your own request record. Decide what must be retained or removed at each provider and in backups under the applicable policy; neither local-complete nor a successful command establishes provider or backup deletion.
Record the installation, subject UUID, operation UUID, local outcome, retained categories and remaining provider or backup work. Verify the actual access or erasure effect before reporting completion to the requester.