Validate Reactivation Token
Pre-flight check for an account reactivation token from an email link. Returns account status without performing reactivation.
GET /api/v1/auth/reactivate/validate?token=... — 🌐 Public · Rate limit: 30 req / hour
Pre-flight check: validates a reactivation token without committing reactivation. Returns the account status (paused / pending-deletion / expired / deleted) so the landing page can render appropriate UI before the user clicks the "Reactivate" button.
The actual reactivation happens via POST /users/reactivate (separate endpoint, in the user module). This endpoint is read-only — it lets the UI show "Welcome back, your account is paused — click below to reactivate" vs "This account was deleted and cannot be recovered".
Request
Query parameters
| Param | Type | Required | Notes |
|---|---|---|---|
token | string | ✓ | Opaque reactivation token from the email link |
No body, no path params, no headers.
Response
200 OK — ApiResponseOf<ReactivateValidateResponseDto>
{
"success": true,
"data": {
"valid": true,
"status": "paused",
"userMaskEmail": "u***@b***.re",
"deletionDate": null
}
}| Field | Type | Notes |
|---|---|---|
valid | boolean | false if token is malformed, unknown, used, or expired |
status | enum | One of: paused / pending-deletion / expired / deleted |
userMaskEmail | string | null | Masked email (single-letter prefix per maskEmail helper) of the account associated with the token |
deletionDate | string (ISO 8601) | null | Hard-deletion schedule — present only when status = pending-deletion |
Status semantics
status | Meaning | UI guidance |
|---|---|---|
paused | Account is DEACTIVATED, no pending deletion → reactivate eligible | "Reactivate your account" CTA |
pending-deletion | Account is DEACTIVATED + GDPRRequest DELETION pending in grace window → reactivate eligible | "Cancel deletion + reactivate (deadline {deletionDate})" CTA |
expired | Token signature valid but expiresAt past → user must re-request link | "This link expired — request a new one" |
deleted | Account purged (UserStatus.DELETED) → cannot reactivate | "This account has been permanently deleted" — terminal |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
429 | (throttle) | Rate limit exceeded (30 req/hour) |
The endpoint always returns 200 — invalid / unknown / expired tokens come back with valid: false (not 4xx) so the landing page can render a coherent expired-link UI.
Side effects
- Decode + verify token signature.
- Look up associated
User+GDPRRequest(if any). - Compute status from
User.status+GDPRRequest.status+expiresAt. - Mask email for display.
- No mutations — purely a read endpoint. The reactivation itself is a separate POST.
Code samples
curl 'https://api.bio.re/api/v1/auth/reactivate/validate?token=abc123-from-email-link'type ReactivateStatus = 'paused' | 'pending-deletion' | 'expired' | 'deleted';
type ReactivateValidate = {
valid: boolean;
status: ReactivateStatus;
userMaskEmail: string | null;
deletionDate: string | null;
};
async function validateReactivate(token: string): Promise<ReactivateValidate> {
const url = new URL('https://api.bio.re/api/v1/auth/reactivate/validate');
url.searchParams.set('token', token);
const res = await fetch(url);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Failed'), {
code: json?.error?.code,
});
}
return json.data;
}import { useQuery } from '@tanstack/react-query';
export const reactivateKeys = {
validate: (token: string) => ['auth', 'reactivate', 'validate', token] as const,
};
export function useReactivateValidate(token: string) {
return useQuery({
queryKey: reactivateKeys.validate(token),
queryFn: async () => {
const url = new URL('/api/v1/auth/reactivate/validate', window.location.origin);
url.searchParams.set('token', token);
const res = await fetch(url);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Failed'), {
code: json?.error?.code,
});
}
return json.data as ReactivateValidate;
},
enabled: Boolean(token),
retry: false, // Token state is deterministic — no point retrying
});
}Try it
curl -X GET "https://loading/api/v1/auth/reactivate/validate?token=string"{
"success": true,
"data": {
"valid": true,
"status": "paused",
"userMaskEmail": "u***@b***.re",
"deletionDate": "2019-08-24T14:15:22Z"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/auth/auth.controller.ts | 407–423 (reactivateValidate) |
| DTO (response) | apps/api-core/src/modules/auth/dto/reactivate-validate.dto.ts | 34–45 (ReactivateValidateResponseDto), 21–24 (REACTIVATE_STATUSES) |
| Service | apps/api-core/src/modules/auth/challenge.service.ts | validateReactivationToken() |
| Prisma models | packages/prisma/prisma/schema.prisma | User.status, GDPRRequest |
Get Challenge Metadata
Fetch metadata for an active OTP Challenge (masked phone, expiry, remaining attempts). Does NOT return the code.
Generate 2FA Setup
Start TOTP enrollment. Generates a server-side TOTP secret, encrypts it on the User row, and returns the secret + QR code so the user can pair an authenticator app.