Confirm data deletion when a consent is revoked
When a consent is revoked, expires, or is consumed, the TPP must review and delete the data it no longer has a lawful basis to hold, but the API Hub, the consent source of truth, holds no confirmation that it happened. Add an append-only attestations sub-resource so a TPP can attest — per data category — what it deleted or lawfully retained, across every consent type and within 45 days, with no new state model.
Cast your vote
Sign in with the Trust Framework to vote — For, Against, or Abstain — recorded in the open with your reasoning. Your organisation and name come from your directory profile, and each person may vote once.
Revocation ends access, but leaves the data
A consent is the customer's permission for a TPP to hold and use their data. When that consent reaches a terminal status — revoked (by the TPP, the LFI, or the customer), expired, or consumed (a payment consent, once fully used) — the permission is gone, and the TPP must review and delete what it no longer has a lawful basis to hold. That is not always everything: a TPP may be required to retain certain records to meet other legal obligations, such as anti-money-laundering rules. Ending the consent stops future access at the gateway; it does nothing about the data the TPP has already pulled and stored.
The API Hub is the source of truth for consent: it creates, stores, and manages every consent, and validates it on every request. That makes it the natural place to hold the confirmation of what happened to the data afterwards — yet today it has none. When a consent ends the Hub records the ending itself — a revocation, or a move to Expired or Consumed — and nothing more:
# A consent reaches a terminal status — Revoked (by the TPP, the LFI, or the
# customer), Expired, or Consumed (a payment consent, once fully used).
# The TPP must then review and delete the data it no longer has a lawful basis
# to hold. Today the Hub can record only the ending itself — e.g. a revocation:
PATCH /account-access-consents/{ConsentId}
{ "Data": { "Status": "Revoked", "RevokedBy": "TPP.InitiatedByUser" } }
# ...and on expiry or consumption the Hub moves the consent to its terminal
# status on its own. Either way, there is nowhere to confirm the TPP has acted
# on the data it holds.The requirement here is deliberately contained: give the TPP a way to attest that it has completed its data review and deletion — or lawful retention — in line with its statutory obligations, and let the Hub hold that attestation against the consent. It is not a request for a deletion workflow or a status model — just a durable, timestamped attestation, allied to the consent it concerns, submitted within a defined window.
An append-only attestations sub-resource on the consent
Add an attestations sub-resource to each consent. A TPP POSTs an Attestation Event to confirm it has completed its data review and deletion once the consent ended, whether revoked, expired, or consumed. A TPP GETs the sub-resource to read the events it has posted. The event hangs off the consent it concerns, so the confirmation is allied to the consent with nothing to reconcile.
# PROPOSED — post an immutable Attestation Event to the consent
POST /account-access-consents/{ConsentId}/attestations # Bank Data Sharing
POST /payment-consents/{ConsentId}/attestations # Bank Service Initiation
POST /insurance-consents/{ConsentId}/attestations # Insurance (all types)
{
"Data": {
"AttestationType": "DataRetentionDeletion",
"AttestationStatusAppliedDateTime": "2026-07-08T17:30:00Z",
"DataAccessCeasedDateTime": "2026-07-01T09:15:30Z",
"ConsentRevocationDateTime": "2026-07-01T09:15:00Z", # only if revoked at the TPP
"DataActions": [
{ "AttestationType": "Deleted",
"DataCategory": "AllConsentedData",
"AttestationDate": "2026-07-08T17:00:00Z" }
]
}
}
# 201 Created — a receipt, not a status transition. Authorised with the same
# client credentials token used to create/get the consent; no new auth, no state.A TPP's answer is not a single flag: the same data was not necessarily all handled the same way. So the event carries a DataActions array with one entry per category of data — and an event must list every category the consent covers. Each entry states its AttestationType: Deleted (the data is gone), or Retained / Anonymised / ArchivedRestricted (the data is kept in some form — a RetentionReason, a RetainedUntilDate, and an AccessRestriction are then required). The category is AllConsentedData where one action covers everything, or a specific Permission Code, AnalyticsData, or AuditRecords where it does not:
# One event lists every data category the consent covers, and states what
# happened to each — deletion is not always the whole story.
"DataActions": [
{ "AttestationType": "Deleted",
"DataCategory": "AllConsentedData",
"AttestationDate": "2026-07-08T17:00:00Z" },
# Retained under a lawful basis — reason, retained-until, and restriction required:
{ "AttestationType": "Retained",
"DataCategory": "AuditRecords",
"AttestationDate": "2026-07-08T17:01:00Z",
"RetentionReason": "RegulatoryOrLegalObligation",
"RetainedUntilDate": "2031-07-01",
"AccessRestriction": "RestrictedUseOnly" },
# Anonymised for analytics:
{ "AttestationType": "Anonymised",
"DataCategory": "AnalyticsData",
"AttestationDate": "2026-07-08T17:02:00Z",
"RetentionReason": "AnonymisedOrAggregatedData",
"RetainedUntilDate": "2027-07-01",
"AccessRestriction": "NotRestricted" }
]Each POST records a new, immutable event, treated as a single atomic event uncorrelated with any other. There is no state model and no correction endpoint: an AttestationType is data on the event, not a status the consent moves through, the Hub applies no de-duplication, and all successfully recorded events are stored. If a TPP posts more than once, it has still met its obligation; which event is surfaced in reporting — the earliest, the latest, or all — is a reporting-layer matter, not consent state. The event's top-level AttestationType is the extension point — DataRetentionDeletion is the only value defined today, and a future obligation to attest to something else against a consent becomes a new value, not a new API. The 201 response is a receipt, and a GET returns the list:
# The 201 response is a receipt — including whether the 45-day deadline was met:
{ "Data": {
"AttestationId": "3f1c8b1e-9a2d-4c7e-bf10-1d2e3f4a5b6c",
"AttestationReceivedDateTime": "2026-07-08T17:30:04Z",
"RegulatoryDeadlineMetIndicator": true
} }
# GET the Attestation Events a TPP has posted against this consent:
GET /account-access-consents/{ConsentId}/attestations
{
"Data": [
{
"Attestation": { "AttestationType": "DataRetentionDeletion", "...": "..." },
"Receipt": {
"AttestationId": "3f1c8b1e-9a2d-4c7e-bf10-1d2e3f4a5b6c",
"AttestationReceivedDateTime": "2026-07-08T17:30:04Z",
"RegulatoryDeadlineMetIndicator": true
}
}
]
}The sub-resource is a child of the consent (nothing to reconcile) and append-only (no state model) — two properties the other shapes we looked at could not both offer, as Alternatives considered below sets out. Authorisation is the same as getting a consent: the sub-resource is called with the very client credentials access token a TPP already uses to create and retrieve the consent itself (for example GET /account-access-consents). So there is no new auth flow, and no dependence on a token that dies with the consent. The attestation obligation and its 45-day window are documented on the consent requirements page; whether every required attestation was posted is a reporting-layer question, kept out of the consent itself.
Attest within 45 days of the consent ending
The obligation attaches only when a consent reaches one of three terminal statuses, and it carries a defined deadline. Within 45 days of the consent reaching that status, the TPP MUST complete its data review and deletion (or record a lawful retention) and POST the Attestation Event. The receipt's RegulatoryDeadlineMetIndicator tells the TPP, there and then, whether it attested inside the window.
Revoked— the consent was withdrawn, by the TPP, the LFI, or the customer.Expired— the consent'sExpirationDateTimepassed and the Hub moved it toExpired.Consumed— a payment consent that has been fully used. This is why Service Initiation is in scope (see below).
No other terminal status is in scope for v2.2 — Rejected, for example, is not, because no data was shared under it. The requirement applies to consents created under Standards v2.2 or later, and to earlier consents that are still Authorized on the CBUAE-mandated v2.2 implementation date and subsequently become Revoked, Expired, or Consumed. Consents that were already terminal before that date are out of scope.
The clock and the deadline live in the reporting layer, not on the consent: the consent holds no deletion state, and the 45-day rule is enforced by monitoring which consents have a recorded attestation, not by a new consent status. This proposal deliberately leaves the monitoring, reminder, and escalation processes — and any correction process for a mistaken attestation — out of scope.
Why a sub-resource, and not the two obvious alternatives
Two other shapes were worked through first. Both can be made to work; each carries a cost the sub-resource avoids.
A field on the consent revoke (PATCH). The first instinct was to add a DataDeletion object to the existing consent PATCH body — a small Status (say Completed, NotRequired, RetainedUnderLegalObligation) and a timestamp — so the TPP confirms deletion in the same call it uses to revoke. It is appealingly small and needs no new operation. But it couples an attestation to a status transition, and the two do not share a lifetime. Once the consent has already ended — revoked at the LFI, or expired — which is exactly when the confirmation is needed, there is no transition left to make: the PATCH would have to be relaxed to accept a body with no status change, on a consent already in a terminal state. That is a carve-out in the revoke contract — and because the field would live on the consent itself, the consent now carries deletion state, the very thing the requirement does not ask it to hold. (An AttestationType on a separate, immutable Attestation Event, as recommended above, is different: it describes a single confirmation, and the consent holds no deletion state of its own.)
A standalone events API. The other option was a new top-level resource — a /revocation-events endpoint, say — that the TPP posts to. This cleanly separates the report from the consent, but it buys a new problem: every event must then be reconciled back to the consent it concerns, with its own rules for matching, ordering, and orphaned records. It is not the endpoint that is expensive so much as the reconciliation around it.
The sub-resource takes the good part of each. Like the events API it is a distinct, append-only Attestation Event rather than a field bolted onto a status change — so it behaves identically whether the consent is active, revoked, expired, or consumed. Like the PATCH field it is allied to the consent — the event is a child of the consent, so there is nothing to reconcile. And because each post is an immutable event, there is no state model to build or enforce in the consent layer. Any enforcement that emerges later — checking a required attestation was posted by its deadline — sits in the reporting layer over these events, leaving the events themselves immutable and the consent stateless.
One event shape, however the consent ends
A consent reaches the end of its life in more than one way, and an append-only POST serves them all without special cases — because it never depends on the consent's status.
Path A — the TPP revokes. The TPP revokes the consent (PATCH to Status: Revoked) as it does today, deletes the data, and POSTs an Attestation Event to the consent's attestations sub-resource.
Path B — the customer revokes at the LFI. The customer withdraws consent in their bank's or insurer's app. The LFI tells the Hub, the Hub sets the consent to Revoked, and the Hub notifies the TPP through the existing Consent Status Event. The TPP deletes its data and POSTs the same event.
Path C — the consent expires. No one revokes it: its ExpirationDateTime passes and the Hub moves it to Expired on its own, firing the same Consent Status Event. The TPP's basis to hold the data ends just as it does on revocation, so it deletes the data and POSTs the same event. Expiry is a quiet case — there is no deliberate act by anyone to prompt the clean-up — which is exactly why it must be in scope.
Path D — a payment consent is consumed. A Service Initiation consent that has been fully used moves to Consumed. Like expiry, it is a status the Hub sets on its own, firing the same Consent Status Event — and the payment consent still holds data (see the next section). The TPP deletes it and POSTs the same event.
The one event shape carries all four because it is appended, not a status change: it does not matter that the consent is already Revoked, Expired, or Consumed, since there is no transition to fight — as there would be if the confirmation were a PATCH of the consent's status. And because the sub-resource is authorised with the same client credentials token used to create and get the consent — not a consent-bound token — it works the same way whether the consent is still active or already terminal: there is no token that expires with the consent, and no new authorisation flow to add.
The TPP learns the consent has ended either from the Consent Status Event webhook (Paths B, C, and D) or, where it has not subscribed, by checking the consent status on its own schedule. Either way, the absence of a webhook subscription does not remove the obligation to notice the terminal status and attest.
This applies to all consent types — including Service Initiation
Every consent type carries data the TPP may have retrieved and stored, so the sub-resource applies uniformly to all three:
- Bank Data Sharing —
POST /account-access-consents/{ConsentId}/attestations. The obvious case: account, balance, transaction, and party data pulled under the consent. - Bank Service Initiation —
POST /payment-consents/{ConsentId}/attestations. See the call-out below — a payment consent is not empty. - Insurance —
POST /insurance-consents/{ConsentId}/attestations, across every insurance type (Motor, Health, Home, Life, Travel, and the rest), each carrying policy and claims data.
Service Initiation is the one to call out, because it is the easiest to wave through. It is filed under "payments", not "data sharing", so it is tempting to assume a payment consent holds nothing to delete once the money has moved. That is wrong. A Service Initiation consent carries data: the debtor and creditor account identifiers and names, the amounts, references, and the charge and exchange-rate information attached to the consent (Charges, ExchangeRate) — and any account data the TPP retrieved to set the payment up. That is personal and financial data held under a consent, and when the consent ends — revoked, expired, or consumed — it falls under the same deletion obligation as any Data Sharing consent. Excluding Service Initiation would leave exactly the data people assume is not there.
What changes in the spec
A new sub-resource on each consent, a shared Attestation Event schema, and storage against the consent. No new top-level API, no change to the consent status model.
attestations sub-resourceAdd POST and GET on /{consent}/attestations to the three TPP-facing consent specs: Bank Data Sharing (/account-access-consents), Bank Service Initiation (/payment-consents), and Insurance (/insurance-consents). The POST records an Attestation Event; the GET lists them. No PATCH or DELETE — events are immutable. Authorisation is the same as the consent endpoints: the client credentials token used to create and get the consent (the client_credentials grant), with the same scope as the parent consent — not the consent-bound (authorization_code) token.
A shared event: a top-level AttestationType (DataRetentionDeletion today, extensible), the envelope date-times (AttestationStatusAppliedDateTime, DataAccessCeasedDateTime, optional ConsentRevocationDateTime), and a DataActions array of one entry per DataCategory. Each action's AttestationType (Deleted / Retained / Anonymised / ArchivedRestricted) selects whether RetentionReason, RetainedUntilDate, and AccessRestriction are required. The Hub returns a receipt: AttestationId, AttestationReceivedDateTime, and RegulatoryDeadlineMetIndicator.
Mandatory properties, enum values, and date-time formats are enforced by schema validation — not restated as technical rules. The rule layer covers only what schema cannot: the temporal ordering of the date-times and the conditional requirements. A rule failure returns 400 with Attestation.ValidationError.
The Consent Manager stores each Attestation Event against its ConsentId. All successfully recorded events are kept; the Hub applies no de-duplication and offers no correction endpoint. Whether a required attestation was posted, and within the 45 days, is derived in the reporting layer — not held as state on the consent — so the consent layer needs no new state machine.
Document the obligation and its 45-day window on the consent requirements page, and add the attestation step to the consent API guide and the Consent Status Event guide (on receiving a revocation, expiry, or consumption event, delete and post an Attestation Event).
The sub-resource, written out
A working draft attached to this proposal — the attestations sub-resource on all three consent types, with its shared Attestation Event schema. It opens in the same rendered view as the published API specs.
| Field | Required | Type | Description |
|---|---|---|---|
| $.Data | Yes | object | The Attestation being submitted. The only permitted type today is DataRetentionDeletion. |
| $.Data.AttestationType | Yes | string | The attestation type. Value: DataRetentionDeletion. |
| $.Data.AttestationStatusAppliedDateTime | Yes | date-time | Date and time from which the attestation is valid. Must be in the past. |
| $.Data.DataAccessCeasedDateTime | Yes | date-time | Date and time at which the TPP’s data access ceased. Must be in the past. |
| $.Data.ConsentRevocationDateTime | No | date-time | Date and time the customer revoked consent at the TPP. Provided only where the consent was revoked at the TPP — not where it expired or was consumed. |
| $.Data.DataActions | Yes | array (≥1) | The action taken for each category of data held. Must list every category that applies to the consent. |
| $.Data.DataActions[*].AttestationType | Yes | string | Deleted, Retained, Anonymised, or ArchivedRestricted. Deleted attests the data is gone; the others attest it is retained in some form. |
| $.Data.DataActions[*].DataCategory | Yes | string | The category the action applies to: AllConsentedData, a Permission Code, AnalyticsData, or AuditRecords. |
| $.Data.DataActions[*].AttestationDate | Yes | date-time | Date and time the attestation for this category is made. |
| $.Data.DataActions[*].RetentionReason | Conditional | string | Why the data is retained. Required where the action is Retained, Anonymised, or ArchivedRestricted. |
| $.Data.DataActions[*].RetentionReasonDescription | Conditional | string (1–500) | Free-text reason. Required where RetentionReason is Other. |
| $.Data.DataActions[*].RetainedUntilDate | Conditional | date | The date until which the data is retained. Required where the action is Retained, Anonymised, or ArchivedRestricted; must be in the future. |
| $.Data.DataActions[*].AccessRestriction | Conditional | string | How access to retained data is restricted: NotRestricted, RestrictedUseOnly, or Other. Required where the action is Retained, Anonymised, or ArchivedRestricted. |
| $.Data.DataActions[*].AccessRestrictionDescription | Conditional | string (1–500) | Free-text description. Required where the action is ArchivedRestricted and AccessRestriction is Other. |
| Field | Required | Type | Description |
|---|---|---|---|
| $.Data | Yes | object | Wrapper for the receipt recorded by the API Hub. |
| $.Data.AttestationId | Yes | uuid | Unique identifier the API Hub assigns to the Attestation Event. |
| $.Data.AttestationReceivedDateTime | Yes | date-time | Date and time at which the API Hub received the Attestation Event. |
| $.Data.RegulatoryDeadlineMetIndicator | Yes | boolean | Whether the API Hub received the event before the regulatory deadline — 45 days after the consent reached its terminal status. |
What an attestations sub-resource buys
- ✓Closes the compliance-evidence gap — the API Hub, the consent source of truth, gains a durable, timestamped attestation that the TPP met its data-deletion obligation. Today it holds a terminal consent but no such confirmation.
- ✓Minimal build — an append-only sub-resource on the consent: no state model to maintain and no reconciliation between a separate event and the consent, because the attestation is the consent’s own child.
- ✓Reuses the existing authorisation — the sub-resource is called with the same client credentials token a TPP already uses to create and get the consent (not the consent-bound token). There is no new auth flow to add, and the call works unchanged after the consent has ended.
- ✓Captures the real picture, per category — one Attestation Event lists each data category and whether it was Deleted, Retained, Anonymised, or ArchivedRestricted, with the reason and retention period where kept — not a bare “done”.
- ✓One shape for every ending — the same event serves Revoked, Expired, and Consumed, because an append-only POST never depends on the consent’s status.
- ✓Uniform across every consent type — Bank Data Sharing, Bank Service Initiation, and every Insurance type get the same sub-resource and the same event shape.
- ✓The deadline is visible in the response — the Hub returns RegulatoryDeadlineMetIndicator, so the TPP gets immediate confirmation it attested within the 45-day window.
- ✓An extensible pattern — a future obligation to attest to something else against a consent is a new AttestationType, not a new API: the standard grows and the Consent Manager absorbs it.
- ✓Non-breaking and stateless — it adds a sub-resource; the existing consent revoke (PATCH) flow is untouched, and there is no de-duplication or correction endpoint to build.
What it costs
- ×It is a new operation — a sub-resource, not merely a field on an existing call — so there is some new surface to specify, build, and secure, even if far less than a standalone events API with reconciliation.
- ×A self-declaration, not proof — the attestation captures the TPP’s confirmation that it deleted (or lawfully retained) the data, not independently verified erasure.
- ×No state model means no enforcement in the consent layer — whether a required attestation was posted, and by when, is a reporting-layer concern the Hub must build separately (a deliberate trade to keep state out of the consent).
- ×The per-category model asks more of the TPP — listing every applicable DataCategory with its reason, retention date, and access restriction is richer than a single flag, and TPPs must map their data holdings onto the categories.
- ×A new obligation for TPPs — completing the review and attesting within 45 days is extra work and tooling, and non-attestation needs a defined consequence to be meaningful rather than cosmetic.
