BIO.RE
User

Request Account Deletion (GDPR)

Right-to-Erasure (Art. 17) — schedule the account for deletion after a grace period. Account is deactivated immediately and all sessions revoked.

POST /api/v1/gdpr/delete — 🔑 Bearer · Rate limit: 1 req / day

Schedules the calling account for permanent deletion. Creates a GDPRRequest DELETION PENDING row, deactivates the account immediately (User.status = DEACTIVATED), and revokes every active session in the same transaction. The actual purge runs after a grace period (gdpr.delete_grace_days, admin-managed, default 30) — until then, the user can cancel via DELETE /gdpr/delete (or by clicking the reactivation link from the email flow).

Sessions are revoked immediately, including the one calling this endpoint. The user must re-authenticate to interact with the account again. Plan a redirect to a logged-out landing page on success.

For the legacy alias POST /users/delete, see Request Deletion (Legacy). The legacy endpoint additionally requires the current account password in the body — kept for backward compatibility but the modern endpoint here is preferred.

Request

No body, no params.

HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKApiResponseOf<GdprDeletionRequestDto>

{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "PENDING",
    "gracePeriodEnds": "2026-05-29T20:00:00.000Z"
  }
}
FieldTypeNotes
idstring (UUID)GDPRRequest.id — pass to DELETE /gdpr/delete to cancel
statusenumAlways PENDING immediately after creation. Will progress to PROCESSINGCOMPLETED (worker-driven) or CANCELLED (user-driven via cancel endpoint).
gracePeriodEndsstring (ISO 8601)When the actual purge will run — now + gdpr.delete_grace_days (default 30 days)

Errors

HTTPcode / i18nKeyReason
401(guard)Missing / invalid bearer token
409error.gdpr.deletion_already_pendingA previous DELETION request is already PENDING
429(throttle)Rate limit exceeded (1 req/day)

Side effects

  1. Look for an existing GDPRRequest { type: DELETION, status: PENDING } for this user. If found → deletion_already_pending.
  2. Compute gracePeriodEnds = now + gdpr.delete_grace_days * 86400 * 1000.
  3. Inside one transaction:
    • Insert GDPRRequest { id, userId, type: DELETION, status: PENDING, scheduledAt: gracePeriodEnds }.
    • User.status = DEACTIVATED — account is hidden during the grace period.
    • UPDATE Session SET revoked = true, revokedAt = now() WHERE userId = :userId AND revoked = false.
  4. Audit log: [gdpr] Self-service deletion requested by user {userId}, grace ends <iso>.
  5. Worker pickup — after gracePeriodEnds, the worker-service cron flips status to PROCESSING, performs the purge (Art. 17 erasure across all owned data), and sets status = COMPLETED. The cancel endpoint becomes a no-op once status leaves PENDING.

Cancel flow

To stop a scheduled deletion before the grace period ends:

# Cancel + reactivate the account in one call
curl -X DELETE https://api.bio.re/api/v1/gdpr/delete \
  -H "Authorization: Bearer $ACCESS_TOKEN"

The cancel endpoint flips GDPRRequest.status = CANCELLED and User.status = ACTIVE atomically. Sessions stay revoked — the user must log in fresh after cancelling.

Code samples

curl -X POST https://api.bio.re/api/v1/gdpr/delete \
  -H "Authorization: Bearer $ACCESS_TOKEN"
type GdprDeletionRequest = {
  id: string;
  status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
  gracePeriodEnds: string;
};

async function requestGdprDeletion(accessToken: string): Promise<GdprDeletionRequest> {
  const res = await fetch('https://api.bio.re/api/v1/gdpr/delete', {
    method: 'POST',
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Deletion request failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useRequestGdprDeletion() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async () => {
      const res = await fetch('/api/v1/gdpr/delete', { method: 'POST' });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Deletion request failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as GdprDeletionRequest;
    },
    onSuccess: () => {
      // Sessions revoked server-side — drop all caches, force re-auth flow
      qc.clear();
    },
  });
}

Try it

POST
/api/v1/gdpr/delete
AuthorizationBearer <token>

In: header

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/gdpr/delete"
{
  "success": true,
  "data": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "status": "PENDING",
    "gracePeriodEnds": "2019-08-24T14:15:22Z"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/user/gdpr.controller.ts72–82 (requestDeletion)
DTO (response)apps/api-core/src/modules/user/dto/gdpr-response.dto.ts51–63 (GdprDeletionRequestDto)
Serviceapps/api-core/src/modules/user/user.service.ts638–678 (gdprRequestDeletion)
Worker pickupapps/worker-service/cron scans GDPRRequest with type=DELETION, status=PENDING, scheduledAt <= now()
Prisma modelspackages/prisma/prisma/schema.prismaGDPRRequest, User.status, Session.revoked

On this page