# Data export and account deletion

Customers can take everything they own out of BitCall as one zip file and
can close their account themselves. Staff can do both on a customer's
behalf. Implemented in `internal/api/ext_data.go`.

## Endpoints

| Route | Who | What |
|-------|-----|------|
| `GET /v1/account/export` | portal: account login, members with `team.write` (owner, admin) | zip download of the account's data |
| `POST /v1/account/delete` | portal: the account's own login only | deletes the account after password (and 2FA code) confirmation |
| `GET /v1/admin/accounts/{id}/export` | staff with `accounts.read` (every role) | the same zip for any account |
| `POST /v1/admin/accounts/{id}/delete` | staff with `accounts.write` **and** role `superadmin`, or the admin key | deletes any account |

Errors follow the usual shape `{"error":{"code","message"}}`; refusals by
role are `403` with `code: "forbidden"`.

## Export

The response is `application/zip` with
`Content-Disposition: attachment; filename="bitcall-export-<account id>-<YYYY-MM-DD>.zip"`.
Every file is pretty-printed JSON except `README.txt`; timestamps are RFC
3339 UTC, money is a decimal in the invoice's currency (`USD` or `INR`).

| File | Contents | Never included |
|------|----------|----------------|
| `README.txt` | export date, account, one line per file, what is left out | |
| `account.json` | id, email, company, created_at, live approval, live request, email verification, `totp_enabled`, plan | password hash, TOTP secret, recovery codes, staff pipeline notes |
| `members.json` | team members with role, status, last login | password hashes |
| `apps.json` | each application with settings, webhook URL, key prefix, `usage_by_month` (`seconds` and `minutes` per `YYYY-MM`) | API key hashes, webhook signing secrets |
| `invoices.json` | every invoice of the account with lines and payment status | |
| `plan.json` | the commercial plan, `null` on sandbox / legacy pricing | |
| `consultants.json` | consultants of all apps, each with `app_id` | |
| `consultations.json` | consultation sessions of all apps, newest first, up to 100,000 per app | |
| `calls.json` | call records of all apps, newest first, up to 5,000 per app | |
| `webhook_deliveries.json` | the delivery log (last 1,000 per app) with attempts and payloads | |

If the webhook delivery log is not reachable on the server the file is
omitted and `README.txt` says so.

Not in the export: media recordings (fetch them per call through the
recordings API while the account exists), chat transcripts, bookings and the
audit log (the portal shows the last entries under Security).

Every export is written to the audit log (`exported account data for
<id> (<email>)`) with the member or staff member who asked for it.

## Deletion

### By the customer

```http
POST /v1/account/delete
{"password": "…", "confirm": "DELETE", "code": "123456"}
```

* Only the account's own login may call it. Team members, including members
  with the `owner` role, get `403 forbidden`.
* `confirm` must be exactly `DELETE` (`400` otherwise).
* `password` is checked against the account password (`401` on mismatch).
* When two-factor login is enabled, `code` (a TOTP or recovery code) is
  required (`400` when missing, `401` when wrong).
* Wrong passwords and codes count against the same lockout as the 2FA
  endpoints: five failures block the endpoint for fifteen minutes (`429`).

### By staff

```http
POST /v1/admin/accounts/{id}/delete
{"confirm": "DELETE"}
```

Requires `accounts.write` and the `superadmin` role (the admin key counts as
superadmin). Support and finance staff, who hold `accounts.write` for other
reasons, get `403 forbidden`.

### What happens

In this order:

1. The account owner receives the email "Your BitCall account has been
   deleted" (kind `account.deleted` in the outbox), listing what was removed
   and what is kept.
2. An audit entry `deleted account <id> (<email>)` is recorded with the actor
   (owner, staff id or `admin`) and `target: account:<id>`. It survives the
   deletion.
3. `store.DeleteAccount` removes the account, its team members, applications
   (with API keys and webhook settings), rooms, call records, consultants
   and consultation history. Sessions stop working immediately because the
   account no longer resolves; the email addresses become free again.
4. Recording files under `DATA_DIR/recordings/<app id>/` are removed for each
   of the account's applications.

Response: `200 {"deleted": true, "account_id": "…", "email": "…"}`.

### What deletion keeps

| Data | Why |
|------|-----|
| Invoices | accounting and tax records, retained for 7 years (`GET /v1/admin/billing/invoices` still lists them) |
| Audit log | security record, retained for 7 years; the deletion entry itself is part of it |
| Usage already counted on invoices | same as invoices |

### What deletion does not reach (yet)

These feature stores have no per-app delete API, so their data stays until
their own pruning removes it:

* **Chat history** (`internal/chat`): kept per room; `Service.DeleteRoom`
  exists but the service instance is private to `ext_chat.go`. The room
  records are gone, so the history is unreachable through the API and is
  dropped by the chat sweeper once the room is no longer alive.
* **Bookings and availability** (`internal/booking`): no delete; bookings
  expire or are cancelled by the manager's own loop.
* **Webhook delivery log** (`internal/events`): no delete; the per-app ring
  (1,000 entries) is unreachable once the app is gone and is overwritten
  only by new deliveries, so it is effectively frozen on disk at
  `DATA_DIR/webhooks/<app id>.json`.
* **Lifecycle, usage-alert and pipeline state** kept by other extensions in
  their own files.

Operators who need a hard purge today can remove the files named above from
`DATA_DIR` after the API call.

## Retention summary

* Invoices and audit entries: 7 years, staff access only, never reused for
  marketing.
* Everything else the account owned: deleted at the time of the request.
* Exports are generated on demand and are not stored on the server.
