CCPA data export in machine-readable format
Why it matters
CCPA §1798.100 and §1798.110 grant California residents the right to know what personal information a business has collected about them and to receive it in a portable format. GDPR Article 15 (right of access) and Article 20 (right to data portability) impose equivalent obligations for EU residents. Without a data export path, every access request requires a manual database query — slow, expensive, and inconsistent across requests. An export that only returns the contact profile while omitting consent history, engagement events, and form submissions is an incomplete response to a legal right, leaving the business exposed to regulatory action.
Severity rationale
Low because the operational gap (no export tooling) only becomes a compliance violation when a data subject actually exercises their access or portability right — but that trigger is entirely outside your control.
Remediation
Implement a GET /api/admin/contacts/:id/export endpoint backed by a service that aggregates all PII categories in one call:
// src/lib/compliance/export.ts
export async function exportContactData(contactId: string): Promise<ContactDataExport> {
const [contact, consentHistory, emailEvents, formSubmissions] = await Promise.all([
db.contact.findUnique({ where: { id: contactId } }),
db.consentRecord.findMany({ where: { contactId }, orderBy: { createdAt: 'asc' } }),
db.emailEvent.findMany({ where: { contactId }, orderBy: { occurredAt: 'asc' } }),
db.formSubmission.findMany({ where: { contactId }, orderBy: { submittedAt: 'asc' } }),
])
return {
exportedAt: new Date().toISOString(),
contactId,
profile: contact,
consentHistory,
emailEngagement: emailEvents,
formSubmissions,
}
}
Verify the export is scoped to one contact — run a query in staging that confirms no other contact's records appear in the response. Protect the endpoint with admin-only authentication.
Detection
-
ID:
ccpa-export -
Severity:
low -
What to look for: CCPA grants California residents the right to know what personal data is collected about them (access right) and to receive it in a portable format. Look for a data export path — an endpoint or admin action that collects all personal data associated with a contact and returns it as JSON, CSV, or another machine-readable format. Check that it covers all data categories (contact info, consent history, email event history, form submissions).
-
Pass criteria: A data export path exists that produces a structured, machine-readable output containing all personal data categories held about a contact. The export is scoped to a single contact and does not leak other contacts' data. Count all data categories included in the export — at least 3 must be present (profile, consent history, engagement events).
-
Fail criteria: No data export capability exists. Or export exists but only returns partial data (e.g., contact record only, missing event history and consent log).
-
Skip (N/A) when: Application provably serves no California residents and is not subject to CCPA.
-
Detail on fail:
"No data export endpoint or admin action found — access right requests would require manual database queries"or"exportContactData() returns contacts and consent_records but omits email_events and form_submissions" -
Remediation: Implement a comprehensive export:
export async function exportContactData(contactId: string): Promise<ContactDataExport> { const [contact, consentHistory, emailEvents, formSubmissions] = await Promise.all([ db.contact.findUnique({ where: { id: contactId } }), db.consentRecord.findMany({ where: { contactId }, orderBy: { createdAt: 'asc' } }), db.emailEvent.findMany({ where: { contactId }, orderBy: { occurredAt: 'asc' } }), db.formSubmission.findMany({ where: { contactId }, orderBy: { submittedAt: 'asc' } }), ]) return { exportedAt: new Date().toISOString(), contactId, profile: contact, consentHistory, emailEngagement: emailEvents, formSubmissions, } }
External references
- ccpa · §1798.100 — Right to know — categories and specific pieces of personal information
- ccpa · §1798.110 — Right to know specific personal information collected
- gdpr · Art. 15 — Right of access by the data subject
- gdpr · Art. 20 — Right to data portability — machine-readable format
Taxons
History
- 2026-04-18·v1.0.0·Initial import from compliance-consent-engine·automated