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-checked — request.userId must match the JWT subject; otherwise 403.
This is the canonical poll endpoint. The status progresses PENDING → PROCESSING → COMPLETED (or FAILED). Once COMPLETED, fetch the signed download URL from GET /gdpr/export/:id/download.
Request
Path parameters
| Param | Type | Validation | Notes |
|---|---|---|---|
id | string (UUID) | ParseUUIDPipe | The id returned by POST /gdpr/export |
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<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"
}
}| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Echoes the request id |
status | enum | One of PENDING / PROCESSING / COMPLETED / FAILED / CANCELLED |
createdAt | string (ISO 8601) | Request creation timestamp |
completedAt | string (ISO 8601) | null | Set when worker flips status to COMPLETED or FAILED. null while PENDING / PROCESSING. |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | (guard) | Missing / invalid bearer token |
403 | error.gdpr.not_owner | Request exists but userId does not match the bearer's subject |
404 | error.gdpr.request_not_found | No GDPRRequest row with this id |
Side effects
prisma.gdprRequest.findUnique({ where: { id } })selectingid, userId, status, createdAt, completedAt.- If row missing → throw
request_not_found. - If
userIdmismatch → thrownot_owner. - 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
Authorization
bearer In: header
Path Parameters
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
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/user/gdpr.controller.ts | 44–55 (getExportStatus) |
| DTO (response) | apps/api-core/src/modules/user/dto/gdpr-response.dto.ts | 22–37 (GdprExportStatusDto) |
| Service | apps/api-core/src/modules/user/user.service.ts | 586–597 (gdprGetExportStatus) |
| Prisma model | packages/prisma/prisma/schema.prisma | GDPRRequest |
Request Data Export (GDPR)
Right-of-Access (Art. 15) — queue an export of the user's data. Returns a request id that the client polls until COMPLETED, then downloads via the signed-URL endpoint.
Download Export
Once status is COMPLETED, fetch a time-limited signed URL for the export archive. The URL is short-lived — render it into an `<a download>` and trigger immediately.