BIO.RE
User

Request Deletion (Legacy)

Older /users/delete endpoint — same effect as POST /gdpr/delete but additionally requires the current account password and returns only the scheduled date.

POST /api/v1/users/delete — 🔑 Bearer · Rate limit: 3 req / hour

Legacy alias of POST /gdpr/delete. Same effect (scheduled deletion + immediate deactivation + session revoke), but it additionally requires the current account password in the body and returns the leaner { scheduledAt } shape.

New clients should use POST /gdpr/delete. This legacy endpoint differs from the modern equivalent in three ways:

  1. Body is required{ password } must be sent and is bcrypt-checked against User.passwordHash.
  2. Response is leaner{ scheduledAt } only, no request id or status field.
  3. Throttle is looser — 3/hour vs 1/day on the modern endpoint.

To cancel a deletion requested through this endpoint, use DELETE /gdpr/delete — both endpoints write to the same GDPRRequest table, so the cancel endpoint sees both.

Request

Body — RequestDeletionDto

FieldTypeRequiredValidationNotes
passwordstringMinLength(8)Current account password — bcrypt-compared against User.passwordHash
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKApiResponseOf<DeletionScheduledDto>

{
  "success": true,
  "data": {
    "scheduledAt": "2026-05-29T20:00:00.000Z"
  }
}
FieldTypeNotes
scheduledAtstring (ISO 8601)When the worker will run the actual purge — now + gdpr.delete_grace_days (admin-managed, default 30 days)

Errors

HTTPcode / i18nKeyReason
400(DTO validation)password shorter than 8 chars
400error.user.password_requiredAccount has no passwordHash (OAuth-only — set a password first or use the modern endpoint which doesn't require one)
400error.user.password_incorrectBcrypt password check failed
401(guard)Missing / invalid bearer token
404error.user.not_foundToken decoded but user row missing
409error.user.deletion_scheduledA previous DELETION request is already PENDING
429(throttle)Rate limit exceeded (3 req/hour)

Side effects

  1. Lookup User; reject if missing or no passwordHash.
  2. Bcrypt-compare password against User.passwordHash.
  3. Look for an existing GDPRRequest { type: DELETION, status: PENDING }. If found → deletion_scheduled.
  4. Compute scheduledAt = now + gdpr.delete_grace_days * 86400 * 1000.
  5. Inside one transaction (same as /gdpr/delete):
    • Insert GDPRRequest { id, userId, type: DELETION, status: PENDING, scheduledAt }.
    • User.status = DEACTIVATED.
    • Revoke every active Session.
  6. Audit log: [gdpr] Deletion scheduled for user {userId} at <iso>.

Migration to the modern endpoint

- await fetch('/api/v1/users/delete', {
-   method: 'POST',
-   body: JSON.stringify({ password }),
- });
+ // The modern endpoint does not require the password — re-confirm via UI flow if you want
+ await fetch('/api/v1/gdpr/delete', { method: 'POST' });

If you need the password-confirmation UX (recommended for destructive operations), keep using this legacy endpoint or add an interstitial password re-prompt in your app before calling /gdpr/delete.

Code samples

curl -X POST https://api.bio.re/api/v1/users/delete \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"password": "current-account-password"}'
async function requestDeletionLegacy(accessToken: string, password: string): Promise<string> {
  const res = await fetch('https://api.bio.re/api/v1/users/delete', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ password }),
  });
  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.scheduledAt as string;
}
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useRequestDeletionLegacy() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (password: string) => {
      const res = await fetch('/api/v1/users/delete', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ password }),
      });
      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.scheduledAt as string;
    },
    onSuccess: () => {
      // Sessions revoked server-side — drop all caches, force re-auth flow
      qc.clear();
    },
  });
}

Try it

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

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/users/delete" \  -H "Content-Type: application/json" \  -d '{    "password": "stringst"  }'
{
  "success": true,
  "data": {
    "scheduledAt": "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/user.controller.ts223–233 (requestDeletion)
DTO (request)apps/api-core/src/modules/user/dto/index.ts16–18 (RequestDeletionDto)
DTO (response)apps/api-core/src/modules/user/dto/user-client-response.dto.ts168–171 (DeletionScheduledDto)
Serviceapps/api-core/src/modules/user/user.service.ts483–526 (requestDeletion)
Modern equivalentapps/api-core/src/modules/user/gdpr.controller.ts72–82 (requestDeletion)
Prisma modelspackages/prisma/prisma/schema.prismaGDPRRequest, User.status, Session.revoked

On this page