Data export & erasure
What Mesa provides when a data-subject request arrives — "give me my data" or "delete my data" — and precisely where its coverage ends. The two halves are not symmetrical: erasure is a built, auditable workflow; export is not built, and an access request is assembled by hand. Both are stated plainly below, because a procedure that overclaims is worse than none.
Where personal data lives
Erasure touches four stores; knowing them is what makes "done" checkable.
| Store | What it holds |
|---|---|
message_history | The hot tier — recent messages, as ordinary database rows |
message_archive_chunks + object storage | The cold tier — archived messages in chunks encrypted under the tenant's key; the catalog row holds ranges, counts and checksums, the object holds content |
message_tombstones | Markers recording that content was deleted, without retaining it |
tenant_keys | The tenant's data-encryption key, wrapped by the deployment's key-management provider |
A fifth table, gdpr_deletion_requests, is the audit trail: one row per erasure request, which
survives execution by design — there is always a record of what was removed, when, and by whom.
Messages also exist wherever the platform keeps them — Telegram's servers and clients, email recipients' mailboxes. Those copies are outside Mesa entirely; no Mesa operation reaches them.
Who can run this
The erasure API lives under /api/admin/gdpr and is tenant-admin scoped: the caller must hold an
admin or owner role in the identity-provider organization mapped to the tenant. Every route
resolves the tenant from the authenticated caller and fails closed — a request cannot erase, or
even list, another tenant's data by naming it. Deployments without the identity provider
configured have no erasure API at all.
Step 1 — record the request
POST /api/admin/gdpr/request records the request without deleting anything. Intake and
execution are deliberately separate calls, so the destructive step is always explicit.
scopeType | Erases | Required scopePayload |
|---|---|---|
tenant | Everything the tenant owns in message storage, plus its encryption key | — |
user | One platform user's messages across the tenant | subjectUserId |
chat | One channel's messages | channelId (optional timestampStart/timestampEnd window) |
message_range | A time window within one channel | channelId, timestampStart, timestampEnd |
message_ids | Specific messages | channelId, messageIds |
The request lands in gdpr_deletion_requests as pending, with the requesting user and the scope
on the row. Inspect with GET /api/admin/gdpr/request/:id, list with
GET /api/admin/gdpr/requests, and get per-status counts with GET /api/admin/gdpr/stats.
Step 2 — export, before you erase
There is no export endpoint. If the data subject has also asked for a copy of their data (access or portability), assemble it before executing erasure — erasure is irreversible, and for tenant scope it is terminal.
Assembly today is manual: read the relevant conversation history through the standard message-history API — archived content is fetched back transparently, so hot and cold are both served — and account for platform-side copies separately. There is no per-subject bundle, no generated report, and no machine-readable package. If your compliance process requires one, that is work your team does by hand today.
Step 3 — execute
POST /api/admin/gdpr/request/:id/execute runs the erasure synchronously. In order:
- Tombstones — rows written to
message_tombstonesmatching the scope. From this moment the read path filters tombstoned messages out of every reply and retrieval, hot and cold — and if the tombstone lookup itself fails, reads exclude rather than include. - Hot delete — matching rows removed from
message_history. - Crypto-shred (tenant scope only) — the tenant's wrapped key in
tenant_keysis overwritten with random data and marked inactive. Every archived chunk the tenant owns remains in object storage as ciphertext that can never again be decrypted.
Only a pending request can be executed. On error the request moves to failed with the error
recorded on the row; a failed request is not retried — create a new one.
What "done" means
| Final status | Meaning |
|---|---|
logically_deleted | Hot rows are gone and tombstones hide every archived copy from all reads. The encrypted bytes of archived copies still exist in the cold tier. This is the terminal state for every scope except tenant. |
physically_purged | No recoverable copy remains inside Mesa: hot rows deleted and the archives cryptographically unrecoverable. Today only the tenant scope reaches this state. |
failed | Nothing further will happen; the error is on the row. Create a new request. |
The row also carries timestamps for each stage — hotDeletedAt, coldLogicalDeletedAt, and
coldPurgedAt — so an auditor can reconstruct what happened when.
Crypto-shred vs compaction
There are two ways to make an archived message actually unrecoverable, and Mesa has one of them.
- Destroy the key (crypto-shred). Immediate and total, but per tenant — encryption is per tenant, so the key cannot be destroyed for one user's messages while keeping a neighbour's readable. Built, and runs as part of tenant-scope execution.
- Rewrite the chunks (compaction). Re-write each affected archive chunk without the tombstoned messages, then delete the old object. Selective — and not built. No job today rewrites archived chunks to drop tombstoned content.
This is why sub-tenant scopes stop at logically_deleted: nothing will ever serve the content,
but the encrypted bytes persist in object storage until the tenant is eventually erased whole. If
your obligations require physical destruction of specific messages in the cold tier, Mesa does not
do that today — record it as such in your compliance process rather than assuming otherwise.
Tenant-scope erasure is terminal
After a crypto-shred the tenant's key is inactive, and every decryption and every new encryption for that tenant refuses. Archived content is gone for good, and archival of new content fails too, because there is no active key. Treat tenant-scope erasure as offboarding, not cleanup. There is no undo.
What erasure does not touch
The honest answer to a data subject names what remains, not only what was removed:
- Platform copies — the platform's own servers and every participant's device or mailbox.
- Model providers — content already sent to a model provider was subject to that provider's retention terms at the time.
- Catalog, tombstone, and request metadata —
message_archive_chunksrows (ranges, counts, checksums), the tombstones, and the audit row survive. Tombstones necessarily record identifiers — channel, message ids, subject user id — of what was removed. - Logs and analytics — application logs redact message bodies, but log and analytics retention is the deployment's own policy.
- Database backups — backups of the cold tier are covered by a crypto-shred (ciphertext without a key), but database backups are not: hot rows in a pre-erasure backup, and the wrapped key in a pre-shred backup, persist until backup rotation ages them out. Completing an erasure includes your backup window.
The procedure, end to end
- Intake. Record who asked, how their identity was verified, and which scope covers the request. Verifying that the requester is the data subject is your process — Mesa does not do it.
- Export first if access or portability was also requested (see step 2). After execution there is nothing left to export.
- Create the deletion request; note the returned id.
- Execute it.
- Verify. Fetch the request: the status should be
physically_purgedfor tenant scope andlogically_deletedfor any other, with the stage timestamps set. Spot-check that a read no longer returns the content. - Record completion — and record what remains: platform copies always, cold-tier ciphertext for sub-tenant scopes, and your backup window.
A note on the word "GDPR"
The API path says gdpr, and erasure maps naturally onto Article 17. But Mesa provides
mechanics, not compliance: there is no export endpoint (Articles 15 and 20 are handled manually),
no deadline tracking, no data-subject identity verification, and no propagation to platforms or
model providers. Those remain the controller's own process — this page tells you exactly which
parts Mesa carries and which parts it does not.