BIO.RE
User

Request Export (Legacy)

Older /users/export alias. Same intent as POST /gdpr/export but with a simpler response shape and a slightly looser duplicate-check. Prefer the GDPR endpoint for new clients.

POST /api/v1/users/export โ€” ๐Ÿ”‘ Bearer ยท Rate limit: 3 req / hour

Legacy alias of POST /gdpr/export. Same purpose, slightly different behavior โ€” kept for backward compatibility.

New clients should use POST /gdpr/export. This legacy endpoint differs from it in two ways:

  1. Duplicate-check is looser โ€” it only blocks when an existing request is PENDING. The modern endpoint blocks on PENDING or PROCESSING.
  2. Response is leaner โ€” only { requestId } is returned, no status or createdAt.

The throttle ceiling is also higher here (3 / hour vs 3 / day on the modern endpoint), which exists for historical reasons.

Request

No body, no params.

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

Response

200 OK โ€” ApiResponseOf<ExportRequestedDto>

{
  "success": true,
  "data": {
    "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
FieldTypeNotes
requestIdstring (UUID)GDPRRequest.id โ€” pass to GET /gdpr/export/:id/status and GET /gdpr/export/:id/download (the modern status / download endpoints accept ids created by either path)

Errors

HTTPcode / i18nKeyReason
401(guard)Missing / invalid bearer token
409error.user.export_in_progressA previous EXPORT request is PENDING (does not trigger on PROCESSING โ€” that's the modern-endpoint behavior)
429(throttle)Rate limit exceeded (3 req/hour)

Side effects

  1. Look for an existing GDPRRequest { type: EXPORT, status: PENDING } for this user. If found โ†’ export_in_progress.
  2. Insert GDPRRequest { id: randomUUID(), userId, type: EXPORT, status: PENDING }.
  3. Audit log: [gdpr] Export requested for user {userId}: {requestId}.
  4. Worker pickup happens the same way as the modern endpoint โ€” there is one shared worker that processes all PENDING GDPRRequest EXPORT rows regardless of which controller created them.

Migration to the modern endpoint

- const res = await fetch('/api/v1/users/export', { method: 'POST' });
- const { requestId } = (await res.json()).data;
+ const res = await fetch('/api/v1/gdpr/export', { method: 'POST' });
+ const { id, status, createdAt } = (await res.json()).data;

The status / download endpoints (GET /gdpr/export/:id/status and GET /gdpr/export/:id/download) work identically against either id โ€” there is no need to migrate already-issued ids.

Code samples

curl -X POST https://api.bio.re/api/v1/users/export \
  -H "Authorization: Bearer $ACCESS_TOKEN"
async function requestExportLegacy(accessToken: string): Promise<string> {
  const res = await fetch('https://api.bio.re/api/v1/users/export', {
    method: 'POST',
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Export request failed'), {
      code: json?.error?.code,
    });
  }
  return json.data.requestId;
}
import { useMutation } from '@tanstack/react-query';

export function useRequestExportLegacy() {
  return useMutation({
    mutationFn: async () => {
      const res = await fetch('/api/v1/users/export', { method: 'POST' });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Export request failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data.requestId as string;
    },
  });
}

Try it

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

In: header

Response Body

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/users/export"
{
  "success": true,
  "data": {
    "requestId": "d385ab22-0f51-4b97-9ecd-b8ff3fd4fcb6"
  }
}
{
  "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.ts235โ€“244 (requestExport)
DTO (response)apps/api-core/src/modules/user/dto/user-client-response.dto.ts175โ€“178 (ExportRequestedDto)
Serviceapps/api-core/src/modules/user/user.service.ts531โ€“550 (requestDataExport)
Modern equivalentapps/api-core/src/modules/user/gdpr.controller.ts32โ€“42 (requestExport)
Prisma modelpackages/prisma/prisma/schema.prismaGDPRRequest, GDPRRequestType.EXPORT

On this page