# Users can request and receive a copy of all personal data held about them

- **Pattern:** `ab-001501` (`gdpr-readiness.user-rights.right-to-access`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001501
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001501)

## Why it matters

GDPR Art. 15 grants every EU data subject the right to receive a copy of all personal data held about them, within 30 days of requesting it. An application with no data export feature and no DSAR process is not compliant — not having a formal process is itself a violation, regardless of whether any user has actually made a request. When a subject access request does arrive (and they will, especially as GDPR awareness grows), scrambling to manually assemble a data export from multiple tables is both error-prone and legally risky. Incomplete exports — for example, returning profile fields but omitting activity history or consent records — also violate Art. 15.

## Severity rationale

High because the absence of any DSAR mechanism makes it structurally impossible to comply with Art. 15's 30-day fulfillment deadline, leaving the controller in ongoing breach for every day the gap exists.

## Remediation

Implement a self-service data export endpoint in `app/api/user/export/route.ts` that gathers all personal data held about the authenticated user.

```ts
export async function GET() {
  const session = await getServerSession()
  if (!session?.user?.id) return new Response('Unauthorized', { status: 401 })

  const userId = session.user.id
  const [profile, activity, purchases, consents] = await Promise.all([
    db.user.findUnique({ where: { id: userId }, select: { id: true, email: true, name: true, createdAt: true } }),
    db.activityLog.findMany({ where: { userId } }),
    db.order.findMany({ where: { userId }, include: { items: true } }),
    db.consentRecord.findMany({ where: { userId } }),
  ])

  return new Response(JSON.stringify({ _meta: { exportedAt: new Date().toISOString() }, profile, activity, purchases, consents }, null, 2), {
    headers: {
      'Content-Type': 'application/json',
      'Content-Disposition': `attachment; filename="my-data-${Date.now()}.json"`,
    },
  })
}
```

Add a "Download my data" button in account settings. Log each export request with a timestamp to demonstrate compliance with the 30-day fulfillment requirement per Art. 12(3).

## Detection

- **ID:** `right-to-access`
- **Severity:** `high`
- **What to look for:** Look for a user settings or profile page with a "Download My Data," "Request My Data," or "Data Subject Access Request" option. Check API routes for a data export or DSAR endpoint (common paths: `/api/user/export`, `/api/account/download`, `/api/dsar`, `/api/me/data`). Inspect what the export includes: does it cover all major data types held about the user — profile fields, activity history, uploaded content, messages, purchase history, consent records? Is the response delivered immediately, or is there an email-based fulfillment workflow? GDPR requires DSARs to be fulfilled within 30 days. If using an email-based workflow, check whether there is a documented process for handling and tracking requests within the deadline. Count every data store containing personal data (primary database, analytics, logs, third-party services) and enumerate which are included in the SAR response vs. which are omitted.
- **Pass criteria:** Authenticated users can initiate a data access request from a settings or profile page, or by contacting a documented privacy contact. The resulting export covers all personal data held about the user. If automated, it is delivered immediately or within a documented timeframe. If manual, the process is documented and tracks the 30-day deadline. Report even on pass: "SAR response covers X of Y identified data stores. Export format includes all required personal data categories." Threshold: no more than 30 days to fulfill a subject access request.
- **Fail criteria:** No data access feature or DSAR process exists. Export exists but is incomplete (e.g., only profile info, missing activity history or purchases). No documented process for handling email-based requests.
- **Skip (N/A) when:** Application collects no personal data beyond authentication credentials and has no content, history, or profile data to export.
- **Cross-reference:** The `right-to-portability` check verifies the machine-readable export format that complements this human-readable access right.
- **Detail on fail:** Example: `"No data export feature and no documented DSAR process. Users cannot request a copy of their data."` or `"Export endpoint exists but only returns profile fields; activity history and consent records are excluded."` or `"Privacy policy says to email privacy@example.com but no internal DSAR tracking process documented."`.
- **Remediation:** Implement a self-service data export endpoint:

  ```ts
  // app/api/user/export/route.ts
  import { getServerSession } from 'next-auth'
  import { db } from '@/lib/db'

  export async function GET() {
    const session = await getServerSession()
    if (!session?.user?.id) return new Response('Unauthorized', { status: 401 })

    const userId = session.user.id

    // Gather ALL personal data held about this user
    const [profile, activity, purchases, consents, preferences] = await Promise.all([
      db.user.findUnique({
        where: { id: userId },
        select: { id: true, email: true, name: true, createdAt: true, updatedAt: true }
      }),
      db.activityLog.findMany({ where: { userId }, orderBy: { createdAt: 'desc' } }),
      db.order.findMany({ where: { userId }, include: { items: true } }),
      db.consentRecord.findMany({ where: { userId }, orderBy: { recordedAt: 'desc' } }),
      db.userPreference.findUnique({ where: { userId } }),
    ])

    const exportData = {
      _meta: {
        exportedAt: new Date().toISOString(),
        formatVersion: '1.0',
        description: 'Personal data export — all data held about you.',
      },
      profile,
      activityHistory: activity,
      purchases,
      consentHistory: consents,
      preferences,
    }

    return new Response(JSON.stringify(exportData, null, 2), {
      headers: {
        'Content-Type': 'application/json',
        'Content-Disposition': `attachment; filename="my-data-${Date.now()}.json"`,
      },
    })
  }
  ```

  Add a "Download my data" button in the account settings page. Log each DSAR request with a timestamp to demonstrate compliance with the 30-day fulfillment requirement.

---

## External references

- gdpr Art. 15
- gdpr Art. 12
- nist IP-3

Taxons: privacy-consent

HTML version: https://auditbuffet.com/patterns/ab-001501
