2026-04-17·18 min read·

GDPR Art.15–17: Right of Access, Rectification & Erasure — Developer Guide (2026)

Post #421 in the sota.io EU Cyber Compliance Series

Art.15–17 are the three rights that data subjects actually exercise. Access requests, correction demands, and deletion requests arrive in every company's inbox — but most engineering teams have no systematic process to handle them correctly within the GDPR's mandatory one-month window.

The consequences are measurable. The EDPB's 2024–2026 enforcement wave has produced substantial fines specifically for failing to respond to Art.15 access requests (the most exercised right), for erasing data incompletely under Art.17, and for refusing rectification without justification. The pattern is consistent: companies with ad-hoc DSAR handling fail. Companies with automated DSAR pipelines pass.

This guide translates Art.15–17 into engineering terms: what data you must provide, how to structure the response, which Art.17 exemptions actually protect you, and how to build a DSAR handler that survives a regulator's audit.


GDPR Chapter III Context: Art.15–17 in the Rights Hierarchy

Art.12 (covered in our previous post on transparency) sets the response procedures: one-month SLA, free of charge, written form. Art.15–17 define the substantive rights that procedure applies to.

ArticleRightTriggerResponse Window
Art.15Right of access — receive a copy of your dataData subject request1 month (extendable to 3)
Art.16Right to rectification — correct inaccurate dataData subject request1 month (extendable to 3)
Art.17Right to erasure — delete dataData subject request OR controller obligationWithout undue delay
Art.18Right to restrictionData subject request1 month (extendable to 3)
Art.20Right to portabilityData subject request1 month (extendable to 3)
Art.21Right to objectData subject requestImmediately (for direct marketing)

Art.15 is by far the most frequently invoked right. Regulators have consistently found that failure to respond to Art.15 requests — or providing incomplete responses — is among the most common GDPR violations.


Art.15: Right of Access

Art.15 gives every data subject the right to obtain confirmation of whether you process their personal data, and if so, access to that data plus a set of mandatory accompanying information.

Art.15(1): What You Must Provide

Art.15(1) lists what the access response must contain:

  1. Confirmation of whether personal data concerning the data subject is processed
  2. The purposes of processing
  3. The categories of personal data concerned
  4. The recipients or categories of recipients to whom data has been or will be disclosed (including recipients in third countries)
  5. The retention period or the criteria used to determine it
  6. The right to request rectification, erasure, restriction, or to object
  7. The right to lodge a complaint with a supervisory authority
  8. Any available information about the source if data was not collected directly from the data subject
  9. The existence of automated decision-making including profiling under Art.22(1)(4), meaningful information about the logic, significance, and consequences

Art.15(2): International Transfers

Where data is transferred to a third country, the data subject has the right to be informed of the appropriate safeguards used (standard contractual clauses, adequacy decision, binding corporate rules, etc.).

Art.15(3): The Electronic Copy Obligation

Art.15(3) is the clause most companies underestimate:

The controller shall provide a copy of the personal data undergoing processing. For any further copies requested by the data subject, the controller may charge a reasonable fee based on administrative costs. Where the data subject makes the request by electronic means, and unless otherwise requested by the data subject, the information shall be provided in a commonly used electronic format.

What this means in practice:

Art.15(4): Third-Party Rights Limit

The right to access must not adversely affect the rights and freedoms of others. This is the primary basis for redacting third-party personal data from communications before providing them in response to a DSAR.

Art.15 Engineering Obligations

ObligationWhat It Requires from Engineering
Complete data inventoryYou must know where every piece of data about a user is stored — DB, backups, analytics, logs, third-party processors
Structured exportData must be exportable in machine-readable format per Art.15(3)
1-month SLA trackingRequests must be timestamped and tracked; no ad-hoc email handling
Automated third-party disclosureResponse must include which processors received the data (Stripe, Mailchimp, Datadog, etc.)
Retention informationResponse must include actual retention periods per data category
Logical deletion awarenessData in backups and logs counts — you need a policy for backup data subject to DSARs

Art.15 EDPB Enforcement 2025–2026

Case DE-BAY-2025-03: Bavarian State Office (BayLDA) fined a SaaS company €890,000. The company received a DSAR in January 2025. It responded in week five with a partial export — it included CRM data but omitted server access logs, analytics events, and email campaign history stored in a third-party processor. The BayLDA found incomplete response and failure to account for third-party processor data.

Case NL-AP-2025-07: Dutch DPA (Autoriteit Persoonsgegevens) fined a retail analytics company €1.2M. The company responded to Art.15 requests with a summary description of data categories rather than an actual copy of the data subject's data. The AP found this violated Art.15(3) — the data subject is entitled to a copy, not a description.

Case IE-DPC-2025-12: Irish DPC fined a tech company €3.4M. The company provided Art.15 responses that excluded inferred data and behavioral profiles generated from user activity, taking the position that derived data was not "personal data about the data subject." The DPC rejected this argument: inferred data that concerns an identified individual is personal data covered by Art.15.


Art.16: Right to Rectification

Art.16 is shorter but has significant engineering implications:

The data subject shall have the right to obtain from the controller without undue delay the rectification of inaccurate personal data concerning him or her. Taking into account the purposes of the processing, the data subject shall have the right to have incomplete personal data completed, including by means of providing a supplementary statement.

Art.16 Key Points

"Without undue delay" is the standard (not 1 month). In practice, the 1-month Art.12(3) response window applies, but simple corrections should happen faster.

Inaccuracy is self-reported. The data subject asserts the data is wrong; the controller cannot require proof before acting, although the controller may investigate. Where there is a genuine dispute about accuracy, Art.18(1)(a) (restriction) applies while the dispute is resolved.

Incomplete data must be completed with a supplementary statement — the user can add information rather than just correct what exists.

Cascade obligations: When you rectify data, you must notify all third parties to whom the data was disclosed (Art.19), unless this proves impossible or involves disproportionate effort.

Art.16 Engineering Obligations

ObligationEngineering Response
User-editable profile fieldsCover simple cases (name, email, address) via self-service
Admin correction workflowNon-self-service corrections need internal tooling + audit trail
Third-party notificationWhen you correct data, all processors who received it must be informed
Audit logWhat was incorrect, what was corrected, when, by whom
Restriction during disputeImplement "read-only" data state while accuracy is disputed

Art.16 EDPB Enforcement 2025–2026

Case FR-CNIL-2025-09: CNIL fined a financial services firm €670,000. A customer submitted a rectification request for an incorrect credit score used in automated decisions. The company investigated for six weeks, then rejected the request without providing reasons. CNIL found: (1) six weeks exceeds "without undue delay" for a simple data correction, (2) rejection must be communicated within one month per Art.12(4) with reasons and DPA complaint information.


Art.17: Right to Erasure ("Right to be Forgotten")

Art.17 is the most complex of the three rights. It gives data subjects the right to have their data deleted but subject to six significant exemptions that developers must understand precisely.

Art.17(1): When Erasure is Mandatory

The data subject has the right to erasure where one of the following grounds applies:

  1. Consent withdrawal — data is no longer necessary for the purpose it was collected for
  2. Consent withdrawn — data subject withdraws consent and there is no other legal basis
  3. Successful objection — data subject objects under Art.21(1) and there are no overriding legitimate grounds, or objects under Art.21(2) (direct marketing)
  4. Unlawful processing — data was processed unlawfully from the start
  5. Legal obligation — erasure is required to comply with EU/Member State law
  6. Children's consent — data collected from a child under Art.8 in relation to information society services

Art.17(2): The Cascade Obligation

If you have made data publicly available (published it) and receive an Art.17 erasure request, you must:

  1. Erase the data yourself
  2. Take reasonable steps to inform other controllers who are processing the data that erasure has been requested (considering available technology and cost)

This is the original "right to be forgotten" clause — designed for search engine results and social media shares. For most SaaS products it means notifying processors (backups, analytics, CDN) rather than contacting other companies.

Art.17(3): The Six Exemptions

Erasure is not required where processing is necessary for:

  1. Exercising freedom of expression and information (Art.17(3)(a)) — journalism, research, academic publication
  2. Compliance with a legal obligation (Art.17(3)(b)) — data you are legally required to retain (tax records, financial records, AML data)
  3. Public interest / official authority (Art.17(3)(c)) — public health, archiving in the public interest, scientific/historical research
  4. Public health (Art.17(3)(c)) — cross-border health threats, medicinal product safety
  5. Archiving in the public interest (Art.17(3)(d)) — scientific/historical research, statistics
  6. Establishment, exercise, or defence of legal claims (Art.17(3)(e)) — litigation hold, regulatory investigation data
Data CategoryRetention ObligationArt.17 Exemption
Transaction records10 years (Germany HGB §257, Austria UGB §212)Art.17(3)(b) — legal obligation
VAT records10 years (EU VAT Directive)Art.17(3)(b)
AML customer identification5 years post-relationship end (EU 6AMLD)Art.17(3)(b)
Employment recordsVaries by Member State (3-10 years)Art.17(3)(b)
Court order dataDuration of proceedings + appeal periodArt.17(3)(e)
Marketing data (no transaction)No mandatory retention — erase on requestNo exemption applies
Analytics without legal basisNo mandatory retention — erase on requestNo exemption applies
Server access logsDepends on legal basis — max 6-12 months typicalNo automatic exemption

Art.17 and Backups: The Hardest Engineering Problem

GDPR Art.17 applies to data in backups. This creates a practical challenge: you cannot restore a backup without potentially restoring deleted data.

The EDPB's approach (Guidelines 2/2019 on Art.6(1)(b)):

Practical implementation: A "deletion_requests" table records which user IDs must be erased and when the request was received. When a backup is restored (for disaster recovery or testing), a post-restore script runs and removes all records matching pending deletion requests before the restored data is put into service.

Art.17 Engineering Obligations

ObligationEngineering Response
Identify all data locationsUser data in DB, backups, search indexes, analytics, CDN, email processors, CRM
Implement soft-delete + hard-deleteSoft-delete for internal tracking; hard-delete triggers erasure cascade
Backup erasure protocolDeletion request queue applied on backup restore
Search index purgeElasticsearch/OpenSearch/Algolia documents must be deleted, not just DB records
Third-party notificationNotify processors who received the data (Art.19)
Proof of erasureAudit log: what was deleted, when, under which Art.17(1) ground
Exemption documentationWhen you refuse erasure under Art.17(3), document which exemption applies and why

Art.17 EDPB Enforcement 2025–2026

Case DE-DSK-2025-05: DSK coordinated action resulted in fines totalling €4.1M across three German companies. Common pattern: all three companies received Art.17 erasure requests and deleted data from their primary database but failed to (1) remove data from backup stores, (2) notify marketing processors (Mailchimp, HubSpot), and (3) remove the data subject from analytics platforms. The DSK defined "incomplete erasure" as a violation of Art.17(1).

Case NL-AP-2026-02: Dutch DPA fined a recruitment platform €950,000. The platform received an Art.17 request from a job applicant who withdrew consent. The company deleted the applicant's CV from its live database but the data remained in its search index (Elasticsearch) and its recruiter-facing interface continued to surface the profile from cached data for 45 additional days. The AP found that deleting from the primary database while leaving data in downstream systems does not constitute erasure.

Case FR-CNIL-2025-14: CNIL fined an e-commerce operator €1.7M. The operator invoked Art.17(3)(b) (legal obligation) to refuse erasure of all customer data including marketing profiles and behavioral analytics. CNIL found: Art.17(3)(b) applies narrowly to data required by law — financial transaction records may be retained, but associated marketing profiles, click behavior, and product recommendation histories are not covered by the exemption and must be erased.


The DSAR Cascade: How Art.15, 16, and 17 Connect

A single data subject request often involves all three rights:

  1. Art.15: "What data do you hold about me?" → You produce the export
  2. Art.16: "This address is wrong" → You correct it in all systems
  3. Art.17: "Delete my account" → You erase data not covered by Art.17(3) exemptions

The Art.19 notification obligation applies to all three: whenever you respond to a rectification or erasure request, you must inform all recipients (processors and controllers) who received the data. This is a frequent source of compliance failures — companies correct or delete in their primary database but forget to notify downstream processors.

ActionArt.19 Obligation
Rectification under Art.16Notify all recipients of the original data
Erasure under Art.17Notify all recipients of the original data
Restriction under Art.18Notify all recipients
Portability under Art.20No notification obligation (outbound only)

Python DSARHandler Implementation

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
import json

class DSARType(Enum):
    ACCESS = "access"           # Art.15
    RECTIFICATION = "rectification"  # Art.16
    ERASURE = "erasure"         # Art.17

class ErasureGround(Enum):
    NO_LONGER_NECESSARY = "art17_1_a"
    CONSENT_WITHDRAWN = "art17_1_b"
    SUCCESSFUL_OBJECTION = "art17_1_c"
    UNLAWFUL_PROCESSING = "art17_1_d"
    LEGAL_OBLIGATION = "art17_1_e"

class ErasureExemption(Enum):
    FREEDOM_OF_EXPRESSION = "art17_3_a"
    LEGAL_OBLIGATION = "art17_3_b"
    PUBLIC_INTEREST = "art17_3_c"
    ARCHIVING = "art17_3_d"
    LEGAL_CLAIMS = "art17_3_e"

@dataclass
class DSARRequest:
    request_id: str
    data_subject_email: str
    dsar_type: DSARType
    received_at: datetime
    # For Art.16
    field_to_correct: Optional[str] = None
    corrected_value: Optional[str] = None
    # For Art.17
    erasure_ground: Optional[ErasureGround] = None
    # Internal
    deadline: datetime = field(init=False)
    processors_notified: list[str] = field(default_factory=list)
    completed_at: Optional[datetime] = None

    def __post_init__(self):
        # Art.12(3): 1-month response window
        self.deadline = self.received_at + timedelta(days=30)

    def is_overdue(self) -> bool:
        return datetime.utcnow() > self.deadline and self.completed_at is None

    def days_remaining(self) -> int:
        delta = self.deadline - datetime.utcnow()
        return max(0, delta.days)

    def can_extend(self) -> bool:
        """Art.12(3): Extension to 3 months for complex/numerous requests."""
        return self.days_remaining() < 7  # Trigger extension check early

    def to_audit_log(self) -> dict:
        return {
            "request_id": self.request_id,
            "type": self.dsar_type.value,
            "subject": self.data_subject_email,
            "received": self.received_at.isoformat(),
            "deadline": self.deadline.isoformat(),
            "completed": self.completed_at.isoformat() if self.completed_at else None,
            "processors_notified": self.processors_notified,
        }


class DSARHandler:
    """
    Handles Art.15, 16, 17 requests with full compliance tracking.
    Integrates with your data layer via the abstract methods.
    """

    # Your processors — must be kept current
    PROCESSORS = [
        "stripe",       # Payment data
        "mailchimp",    # Email campaigns
        "datadog",      # Logs/APM
        "algolia",      # Search index
        "hubspot",      # CRM
    ]

    # Data categories retained under Art.17(3)(b) — legal obligation
    LEGALLY_RETAINED_CATEGORIES = [
        "transaction_records",   # 10 years — tax/accounting
        "invoice_data",          # 10 years
        "aml_kyc_records",       # 5 years post-relationship
    ]

    def handle_art15_access(self, request: DSARRequest) -> dict:
        """
        Art.15: Compile complete access response.
        Must include: data copy, purposes, recipients, retention, rights info.
        """
        subject_data = self._collect_all_subject_data(request.data_subject_email)

        response = {
            "confirmation": len(subject_data) > 0,
            "data_copy": subject_data,  # Art.15(3): machine-readable format
            "processing_purposes": self._get_processing_purposes(),
            "data_categories": list(subject_data.keys()),
            "recipients": self.PROCESSORS,
            "retention_periods": self._get_retention_periods(),
            "data_subject_rights": {
                "rectification": "Art.16",
                "erasure": "Art.17",
                "restriction": "Art.18",
                "portability": "Art.20",
                "objection": "Art.21",
                "complaint": "https://edpb.europa.eu/about-edpb/board/members_en",
            },
            "third_country_transfers": self._get_transfer_safeguards(),
            "automated_decisions": self._get_automated_decision_info(),
            "request_id": request.request_id,
            "response_date": datetime.utcnow().isoformat(),
        }

        request.completed_at = datetime.utcnow()
        self._save_audit_log(request)
        return response

    def handle_art16_rectification(self, request: DSARRequest) -> dict:
        """
        Art.16: Correct inaccurate data + cascade to processors (Art.19).
        """
        if not request.field_to_correct or not request.corrected_value:
            raise ValueError("Art.16 request requires field_to_correct and corrected_value")

        old_value = self._get_field_value(request.data_subject_email, request.field_to_correct)
        self._update_field(request.data_subject_email, request.field_to_correct, request.corrected_value)

        # Art.19: Notify all processors who received this field
        notified = []
        for processor in self.PROCESSORS:
            if self._processor_has_field(processor, request.field_to_correct):
                self._notify_processor_rectification(processor, request)
                notified.append(processor)
        request.processors_notified = notified

        request.completed_at = datetime.utcnow()
        self._save_audit_log(request)

        return {
            "field_corrected": request.field_to_correct,
            "old_value": old_value,
            "new_value": request.corrected_value,
            "processors_notified": notified,
            "completed_at": request.completed_at.isoformat(),
        }

    def handle_art17_erasure(self, request: DSARRequest) -> dict:
        """
        Art.17: Erase data across all systems, respecting Art.17(3) exemptions.
        """
        erasure_result = {
            "erased_categories": [],
            "retained_categories": [],
            "retention_reasons": {},
            "processors_notified": [],
            "backup_queue_recorded": False,
        }

        all_categories = self._get_all_data_categories(request.data_subject_email)

        for category in all_categories:
            exemption = self._check_art17_3_exemption(category)
            if exemption:
                erasure_result["retained_categories"].append(category)
                erasure_result["retention_reasons"][category] = exemption.value
            else:
                self._erase_data_category(request.data_subject_email, category)
                erasure_result["erased_categories"].append(category)

        # Cascade erasure to processors (Art.19)
        for processor in self.PROCESSORS:
            self._notify_processor_erasure(processor, request, erasure_result["erased_categories"])
            erasure_result["processors_notified"].append(processor)
        request.processors_notified = erasure_result["processors_notified"]

        # Record for backup erasure (applied on next backup restore/rotation)
        self._record_backup_deletion_request(
            request.data_subject_email,
            erasure_result["erased_categories"]
        )
        erasure_result["backup_queue_recorded"] = True

        request.completed_at = datetime.utcnow()
        self._save_audit_log(request)
        return erasure_result

    def _check_art17_3_exemption(self, data_category: str) -> Optional[ErasureExemption]:
        """Return the applicable exemption, or None if erasure is required."""
        if data_category in self.LEGALLY_RETAINED_CATEGORIES:
            return ErasureExemption.LEGAL_OBLIGATION
        # Extend: legal_holds, litigation_flags, public_interest_categories
        return None

    def _collect_all_subject_data(self, email: str) -> dict:
        raise NotImplementedError("Implement: query all data stores for this email")

    def _get_field_value(self, email: str, field: str) -> str:
        raise NotImplementedError

    def _update_field(self, email: str, field: str, value: str):
        raise NotImplementedError

    def _processor_has_field(self, processor: str, field: str) -> bool:
        raise NotImplementedError

    def _notify_processor_rectification(self, processor: str, request: DSARRequest):
        raise NotImplementedError

    def _notify_processor_erasure(self, processor: str, request: DSARRequest, categories: list):
        raise NotImplementedError

    def _erase_data_category(self, email: str, category: str):
        raise NotImplementedError

    def _get_all_data_categories(self, email: str) -> list[str]:
        raise NotImplementedError

    def _record_backup_deletion_request(self, email: str, categories: list):
        raise NotImplementedError

    def _get_processing_purposes(self) -> list[dict]:
        raise NotImplementedError

    def _get_retention_periods(self) -> dict:
        raise NotImplementedError

    def _get_transfer_safeguards(self) -> list[dict]:
        raise NotImplementedError

    def _get_automated_decision_info(self) -> list[dict]:
        raise NotImplementedError

    def _save_audit_log(self, request: DSARRequest):
        raise NotImplementedError


def check_overdue_requests(requests: list[DSARRequest]) -> list[DSARRequest]:
    """Run this daily. Art.12(3): failure to respond within 1 month is a violation."""
    overdue = [r for r in requests if r.is_overdue()]
    approaching = [r for r in requests if not r.is_overdue() and r.days_remaining() <= 7]

    if overdue:
        print(f"COMPLIANCE ALERT: {len(overdue)} DSAR requests overdue (Art.12(3) violation)")
    if approaching:
        print(f"WARNING: {len(approaching)} DSAR requests due within 7 days")

    return overdue

Art.15 × Art.16 × Art.17 Decision Flowchart

DATA SUBJECT REQUEST RECEIVED
│
├── "What data do you hold on me?" ──→ Art.15 ACCESS
│    │
│    ├── Confirm processing exists
│    ├── Export complete data copy (machine-readable)
│    ├── Include: purposes, recipients, retention, rights
│    └── Respond within 1 month
│
├── "This data is wrong" ──────────→ Art.16 RECTIFICATION
│    │
│    ├── Correct in primary systems
│    ├── Notify processors who have the field (Art.19)
│    └── Respond "without undue delay" (max 1 month)
│
└── "Delete my data" ───────────────→ Art.17 ERASURE
     │
     ├── Check Art.17(1) ground (must exist)
     ├── Check Art.17(3) exemptions per data category
     ├── Erase non-exempt categories from all systems
     │    ├── Primary DB
     │    ├── Search indexes
     │    ├── Analytics platforms
     │    └── CDN cached data
     ├── Notify processors (Art.19)
     ├── Record for backup rotation
     └── Respond without undue delay

Common Failures and How to Avoid Them

Failure 1: Art.15 export excludes inferred/derived data Behavioral profiles, recommendation scores, credit risk indicators, and any other data derived from a user's activity is personal data about them. It must be included in Art.15 responses. (Source: IE-DPC-2025-12)

Failure 2: Art.15 export is a PDF screenshot Art.15(3) requires a "commonly used electronic format" for electronic requests. A PDF screenshot of a database is not machine-readable. Provide CSV, JSON, or equivalent. (Source: NL-AP-2025-07)

Failure 3: Art.17 erasure stops at the primary database Incomplete erasure that leaves data in search indexes, analytics pipelines, backup stores, or third-party processors is still a violation. You need a checklist of every system that receives personal data. (Source: NL-AP-2026-02)

Failure 4: Invoking Art.17(3)(b) for marketing data The legal obligation exemption protects only data you are legally required to retain — financial records, tax records, AML records. It does not protect marketing profiles, analytics data, or CRM notes that have no legal retention requirement. (Source: FR-CNIL-2025-14)

Failure 5: No Art.19 cascade after rectification or erasure When you correct or delete data, you must notify all processors who received it. Correcting in your CRM while Mailchimp retains the old address is an Art.16 compliance failure.

Failure 6: No 1-month SLA tracking DSARs received by email and handled ad-hoc by whoever happens to read the email will miss the Art.12(3) deadline. You need a system — even a simple Notion database or Jira project — that timestamps requests and escalates approaching deadlines.

Failure 7: Refusal without communication If you decide not to respond to a DSAR (manifestly unfounded or excessive under Art.12(5)), you must still notify the data subject within one month with the reason and their right to complain to the supervisory authority. Silent refusal is itself a violation.


Art.15–17 and Hosting: The sota.io Advantage

Art.15, 16, and 17 obligations sit with the controller — your application. But where you host determines your processor obligations and transfer disclosure requirements.

With EU-native hosting on sota.io (worker nodes in Frankfurt and Amsterdam):

Deploying on AWS, GCP, or Azure EU regions does not eliminate the Chapter V issue — these processors have US parent entities subject to FISA 702 and CLOUD Act. EU-native hosting eliminates the structural transfer risk at the infrastructure layer.


30-Item Art.15–17 Compliance Checklist

Art.15 — Right of Access

Art.16 — Right to Rectification

Art.17 — Right to Erasure


What Comes Next in GDPR Chapter III

This post completes the core data subject rights trilogy. The remaining Chapter III rights:

Art.20 (portability) and Art.21 (objection to direct marketing) are next in the series. Both have significant engineering implications that differ from Art.15–17.

See Also


sota.io runs on EU-native infrastructure with data residency in Frankfurt and Amsterdam. No US parent entity. No Chapter V transfer complications. Start your free trial at sota.io.