Data Privacy
Fulfill GDPR and CCPA data-subject access requests within the 30-day window, erase personal data across modules, and track processing activities with ROPA.
The Data Privacy module handles your organization’s statutory obligations under GDPR, CCPA, and adjacent privacy regimes. Three primary surfaces:
- DSAR management — receive, verify, fulfill, and deliver data-subject access requests within the 30-day GDPR window
- Records of Processing Activities (ROPA) — document processing purposes, legal bases, data categories, and retention with annual review reminders
- Consent — configure and deploy the public-facing consent banner tied to the tenant’s GDPR/CCPA posture
All three are at /compliance/privacy. DSAR-related endpoints are rate-limited both per-user and per-tenant to prevent abuse.
The 30-Day DSAR Lifecycle
Under GDPR Article 12(3), you have one month from receipt to respond to a data subject’s access, erasure, or portability request. Muin computes this deadline as received_date + relativedelta(months=1) — a calendar month, so a Feb 1 request is due Mar 1 (not 30 days later). This matches the statutory clock, not a fixed-day count.
The Status Machine (R9-DSAR-TRANSITIONS)
DSARs move through a strict state machine. Attempting an invalid transition returns 409 InvalidStateError with the set of valid next states in the error body.
received
├→ identity_verification
│ ├→ in_progress
│ └→ rejected
├→ in_progress
│ ├→ pending_approval ──→ completed
│ ├→ extended ──→ in_progress (one 60-day extension allowed)
│ ├→ completed
│ └→ rejected
└→ rejected (terminal)
Key rules enforced by the state machine:
- You can’t skip from
received→completedwithout either verifying identity or acknowledging explicit risk - Once in
pending_approval, the only way forward iscompleted(after compliance owner approval) or back toin_progress extendedis entered with an explicit reason + new due date (GDPR allows one two-month extension for complex requests)rejectedis terminal — rejection requires a documented justification that the subject sees
The full transition matrix is in muin_core/models/compliance/privacy.py (DataSubjectRequest.VALID_TRANSITIONS).
Filing a DSAR
On /compliance/privacy, switch to the DSARs tab and click New Request. The form asks for:
| Field | Purpose |
|---|---|
| Request type | access, erasure, rectification, portability, objection, restriction |
| Subject name | Subject’s full name |
| Subject email | Subject’s email (used for all communications) |
| Request details | Free text — what the subject is asking for |
| Proof of identity | Optional file upload at intake |
Subject Picker vs Free-Text Email
For subjects who are known entities in your tenant (employees, customers with accounts, CRM contacts), use the Subject Picker at the top of the form to resolve the request to an existing person record (User, Employee, or Contact). The form pre-fills name and email from that record.
For subjects who are not entities in your tenant (anonymous site visitors, platform data subjects from third-party data flows), skip the picker and enter name + email directly.
The picker matters because picking a person lets the cross-module erasure (below) find all their records automatically — it knows the person_id to search for. For free-text submissions, erasure searches by email address, which is correct for most flows but misses records that don’t store email.
The Received Date
The received_date is set to the form’s timestamp by default. For requests that came in via email before you filed them in Muin, override to the actual receipt date — the 30-day clock should start when you got the request, not when you logged it.
Fulfilling a DSAR
Identity Verification
Click Start Verification on a received DSAR. The verification screen lets you:
- Upload proof-of-identity documents
- Record verification method (government ID, tenant-known-account, phone callback, etc.)
- Add a verification note for audit
On save, the DSAR transitions to in_progress. If the subject can’t verify, transition to rejected with the rejection reason — the subject sees the rejection via the tenant’s configured response channel.
Data Discovery (Cross-Module Erasure)
For erasure requests, Muin’s GDPRDeletionService.erase_for_subject searches across every module for records referencing the subject:
- HR (employee records, payroll, benefits)
- CRM (contacts, deals, activities)
- Finance (customer records, invoices, payments)
- Documents (uploaded files tagged with the subject)
- Support (tickets, conversations)
- Marketing (email subscribers, campaign engagement)
- Compliance itself (prior DSARs, consent records, incidents involving the subject)
Each module’s records are either deleted outright or pseudonymized (references replaced with hash-stable opaque IDs) depending on what’s legally required to retain for other purposes (tax, dispute windows, ongoing litigation holds).
The service returns a structured summary of what was erased, what was retained (with legal justification), and any records that couldn’t be processed automatically (flagged for manual review). That summary becomes part of the DSAR’s delivery artifact.
Approval and Completion
Once the data package is assembled (for access/portability) or the erasure is executed (for erasure), transition the DSAR to pending_approval. The compliance owner reviews the response before it goes out. On approval, transition to completed and use the Complete dialog to record:
- Response method — email, secure portal link, physical mail
- Response document — the data package (optional UUID picker)
- Response details — free-text summary sent to the subject
The completion is logged with user, timestamp, and response method for audit.
Extension
For complex requests, GDPR allows extending the deadline by two additional months. On an in_progress DSAR, click Extend. The form requires:
- New due date — must be at most
received_date + 3 months - Extension reason — free text, sent to the subject
The subject is notified of the extension within the original 30-day window (also GDPR-required). The DSAR transitions to extended, and the dashboard countdown resets to the new deadline.
Rate Limits (D10)
DSAR endpoints are rate-limited using enforce_multi_rate_limit with both per-user and per-tenant buckets on the same request:
| Endpoint | Rate-limit key (endpoint name in config) |
|---|---|
| List DSARs | compliance.dsar_list |
| Lookup a single DSAR | compliance.dsar_lookup |
| Create DSAR | compliance.dsar_create |
| Update DSAR | compliance.dsar_update |
| Complete DSAR | compliance.dsar_complete |
The per-user bucket prevents a single employee from hammering the API. The per-tenant bucket prevents a script-driven spike across many user tokens from degrading the endpoint for everyone. Exceeding either returns 429 Too Many Requests with a structured dsar_rate_limit_exceeded log entry and a Retry-After header.
The actual limit values are tenant-configurable via the PlatformConfigCache — so a tenant with genuinely high DSAR volume (large consumer brand with many access requests per day) can raise their limit without a code deploy. Defaults are tuned for a typical B2B SaaS tenant.
Records of Processing Activities (ROPA)
GDPR Article 30 requires documenting each processing activity. The Processing Activities tab on /compliance/privacy is the tenant’s ROPA register.
Creating an Activity
Click New Processing Activity. Fill in:
| Field | Purpose |
|---|---|
| Name | e.g. “Customer billing” |
| Purpose | Why you process this data |
| Legal basis | consent, contract, legal_obligation, vital_interests, public_task, legitimate_interests |
| Data categories | What kinds of data (names, emails, payment info, health data, …) |
| Data subjects | Whose data (customers, employees, vendors, minors, …) |
| Recipients | Who you share the data with |
| Retention period | How long you keep it |
| Transfers outside EEA | Countries + transfer mechanism (SCCs, adequacy, BCRs) |
| Security measures | Controls protecting this data |
| Next review date | Auto-set to 1 year out |
The Staleness Notifier (R9-ROPA-WORKER)
A daily worker (compliance_ropa_staleness_task) scans all active processing activities. For any activity where next_review_date is within 14 days (or past), it:
- Fires a notification to the compliance owner
- Creates a
HumanTask(category=ROPA_REVIEW)pointing back to the activity
This keeps the ROPA from decaying — a yearly review on an active register means you never show the auditor a 3-year-old processing description.
Updating a Review
Clicking Mark Reviewed on an activity resets next_review_date = today + 1 year (calendar year via relativedelta). Any fields changed during the review are versioned for audit.
Consent Banner (D5)
The tenant’s public-facing consent banner is configured via the Consent tab. Configure:
- Which jurisdictions the banner applies to (GDPR, CCPA, UK-GDPR, …)
- The categories of cookies / trackers the tenant uses (analytics, advertising, functional, strictly-necessary)
- The cookie policy URL
- Banner copy in the tenant’s supported languages
The ConsentBanner component (rendered at the root of user-facing apps) reads this configuration and shows the banner only to visitors whose inferred jurisdiction requires consent. Consent decisions are recorded in consent_records with subject identifier, scope, granted categories, timestamp, IP, and user agent.
The banner itself is driven by the ConsentBanner React component in muin.ui/src/components/features/compliance/. Each tenant can enable or disable it per environment.
FAQs
What’s the DSAR deadline?
GDPR Article 12(3) requires response within one month of receipt — a calendar month, not 30 days. Muin computes the deadline as received_date + relativedelta(months=1), so a Feb 1 request is due Mar 1 (not Mar 3). For complex requests, GDPR allows one two-month extension; Muin enforces new_due_date <= received + 3 months when extending, and requires notifying the subject of the extension within the original month.
How does erasure work across modules?
The GDPRDeletionService.erase_for_subject searches every module for references to the data subject — HR, CRM, Finance, Documents, Support, Marketing, and Compliance itself. Records are either deleted or pseudonymized (opaque hash-stable IDs replace references), depending on legal retention requirements. The returned summary documents what was erased, what was retained with legal justification, and what needs manual review.
Why was I rate-limited?
DSAR endpoints enforce two rate-limit buckets at once: per-user and per-tenant. Either bucket can trigger a 429. The per-user bucket prevents a single employee from script-hammering the API; the per-tenant bucket protects the endpoint for everyone when a token leak or misbehaving integration spikes traffic. The Retry-After header in the 429 tells you how long to wait; the log emits dsar_rate_limit_exceeded with your user ID, tenant ID, endpoint, and the bucket that denied the request so admins can debug.
Can I delete a DSAR record?
You can soft-delete a DSAR (archives it, removes it from active views), but the audit record — including all status transitions, response method, and completion timestamp — is retained for the legally required period. The archive is for removing administrative clutter from the dashboard, not for erasing the audit trail.
What if the subject can’t be found in any module?
That’s a normal outcome — the data subject may simply not be represented anywhere (e.g. they contacted a marketing email that wasn’t stored, or their account was already purged for unrelated reasons). The response should document “no personal data found for this subject” and provide that statement to the subject. This is a valid GDPR response and Muin records it as a completed DSAR with “no-data” response_method.
Related
- Compliance Health — DSAR SLA compliance contributes to the Readiness posture
- Incidents — a personal-data breach may also create a DSAR
- Policy hub — your Privacy Policy lives here
- Compliance Frameworks — GDPR controls live under the Privacy framework