BIO.RE
User

Get Export Status

Poll the status of a previously created GDPR export request. Returns the current state plus completedAt when finished.

GET /api/v1/gdpr/export/:id/status — 🔑 Bearer

Returns the current state of a GDPRRequest EXPORT row owned by the calling user. Ownership-checkedrequest.userId must match the JWT subject; otherwise 403.

This is the canonical poll endpoint. The status progresses PENDINGPROCESSINGCOMPLETED (or FAILED). Once COMPLETED, fetch the signed download URL from GET /gdpr/export/:id/download.

Request

Path parameters

ParamTypeValidationNotes
idstring (UUID)ParseUUIDPipeThe id returned by POST /gdpr/export
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKApiResponseOf<GdprExportStatusDto>

{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "COMPLETED",
    "createdAt": "2026-04-29T20:00:00.000Z",
    "completedAt": "2026-04-29T20:04:32.000Z"
  }
}
FieldTypeNotes
idstring (UUID)Echoes the request id
statusenumOne of PENDING / PROCESSING / COMPLETED / FAILED / CANCELLED
createdAtstring (ISO 8601)Request creation timestamp
completedAtstring (ISO 8601) | nullSet when worker flips status to COMPLETED or FAILED. null while PENDING / PROCESSING.

Errors

HTTPcode / i18nKeyReason
401(guard)Missing / invalid bearer token
403error.gdpr.not_ownerRequest exists but userId does not match the bearer's subject
404error.gdpr.request_not_foundNo GDPRRequest row with this id

Side effects

  1. prisma.gdprRequest.findUnique({ where: { id } }) selecting id, userId, status, createdAt, completedAt.
  2. If row missing → throw request_not_found.
  3. If userId mismatch → throw not_owner.
  4. Return the slice. No mutations.

Polling guidance

  • The worker pickup is on the order of seconds; total processing depends on dataset size (typically minutes).
  • Recommended poll cadence: every 3–5 seconds while the dialog is open. Stop polling once status === 'COMPLETED' or 'FAILED'.
  • For large datasets, fall back to background polling (e.g., 30s) and surface a notification when done.

Code samples

curl https://api.bio.re/api/v1/gdpr/export/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status \
  -H "Authorization: Bearer $ACCESS_TOKEN"
type GdprExportStatus = {
  id: string;
  status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
  createdAt: string;
  completedAt: string | null;
};

async function getExportStatus(accessToken: string, id: string): Promise<GdprExportStatus> {
  const res = await fetch(`https://api.bio.re/api/v1/gdpr/export/${id}/status`, {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Status check failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useQuery } from '@tanstack/react-query';

export const gdprKeys = {
  exportStatus: (id: string) => ['gdpr', 'export', id, 'status'] as const,
};

export function useExportStatus(id: string) {
  return useQuery({
    queryKey: gdprKeys.exportStatus(id),
    queryFn: async () => {
      const res = await fetch(`/api/v1/gdpr/export/${id}/status`);
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Status check failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as GdprExportStatus;
    },
    enabled: Boolean(id),
    // Poll until terminal state, then stop
    refetchInterval: (query) => {
      const data = query.state.data;
      if (!data) return 5000;
      return data.status === 'COMPLETED' || data.status === 'FAILED' || data.status === 'CANCELLED'
        ? false
        : 5000;
    },
  });
}

Try it

GET
/api/v1/gdpr/export/{id}/status
AuthorizationBearer <token>

In: header

Path Parameters

id*string

Response Body

application/json

application/json

application/json

application/json

curl -X GET "https://loading/api/v1/gdpr/export/string/status"
{
  "success": true,
  "data": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "status": "PROCESSING",
    "createdAt": "2019-08-24T14:15:22Z",
    "completedAt": "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.ts44–55 (getExportStatus)
DTO (response)apps/api-core/src/modules/user/dto/gdpr-response.dto.ts22–37 (GdprExportStatusDto)
Serviceapps/api-core/src/modules/user/user.service.ts586–597 (gdprGetExportStatus)
Prisma modelpackages/prisma/prisma/schema.prismaGDPRRequest

On this page