Privacy API
The Privacy API is what the Privacy pages use. Every endpoint is for signed-in people: an API
key gets 401 sign in required. Each change is written to the audit log under
your name. For what each feature does, read the guides under Data protection first.
Permissions
Section titled “Permissions”| Permission | Allows |
|---|---|
privacy.view |
Every GET below except the request export |
dsr.handle |
Registering and working data subject requests, and reading the settings |
privacy.manage |
Every change to settings, registers, retention, holds and breaches, and the dry run |
reports.approve |
The MLRO’s review of a flagged request |
entities.view_pii |
Needed with dsr.handle to download a request’s export |
Errors
Section titled “Errors”| Status | Meaning | Examples |
|---|---|---|
401 |
Not signed in, or called with an API key | sign in required |
403 |
Missing permission | missing permission privacy.manage; not allowed; not allowed: exporting a person's data decrypts it, which needs the personal data permission (entities.view_pii) |
404 |
No such request, customer, hold, breach, notification, task, activity or transfer in your workspace | not found; not found: no such customer; not found: no such case |
409 |
The action doesn’t fit the current state | the MLRO must review this request first; run a dry run first: run a dry run (after the last policy change, within 30 days) and review it before switching the purge on; conflict: Customer C-1042 is already under a legal hold; conflict: this hold is already released; conflict: the request is completed; conflict: the clock is already stopped; conflict: the clock isn't stopped; conflict: this request isn't waiting for the MLRO |
422 |
A field fails its rules | shorter than the legal minimum: Transactions must be kept at least 1830 days (Ghana Anti-Money Laundering Act, 2020 (Act 1044) s.32(3): not less than five years); invalid: unknown category "x"; invalid: give a reason of 3 to 2000 characters; invalid: Ghana Data Protection Act, 2012 (Act 843) allows no extension (Act 843 s.35(10)); invalid: the deadline can move to 9 Jan 2027 at the latest; invalid: give the legal basis for refusing; invalid: Ghana Data Protection Act, 2012 (Act 843) doesn't let the clock stop (Act 843 s.35(10)); invalid: the discovery time can only move earlier (deadlines run from it) |
428 / 202 |
A change that needs a second person’s approval. See Approvals |
Approvals
Section titled “Approvals”These endpoints can need a second person’s approval, under the workspace’s approval policy:
| Endpoint | Action type |
|---|---|
PUT /api/v1/privacy/retention |
Retention and purging (privacy.retention) |
PUT /api/v1/privacy/retention/purge |
Retention and purging |
PUT /api/v1/privacy/settings, only when the jurisdictions change |
Retention and purging |
POST /api/v1/privacy/holds |
Legal holds (privacy.legal_holds) |
POST /api/v1/privacy/holds/{id}/release |
Legal holds |
When approval is required, a call without an X-Change-Reason header answers 428 after checking
the change, and a call with one answers 202 with the change request. See the
Change requests API.
Reference data
Section titled “Reference data”GET /api/v1/privacy/reference
Section titled “GET /api/v1/privacy/reference”Needs privacy.view, privacy.manage or dsr.handle. Returns what the Privacy pages build their
choices from:
| Field | Contents |
|---|---|
laws |
Every law in the table: code, name, authority, dsr (deadline, rule, citation, per-kind overrides each with their own rule and citation, extension, and stop_clock with stop_clock_rule where the law lets the clock stop), breach (per recipient: hours, rule, citation, when required), erasure_exemption, aml (years and citation), tipping_off, rights |
categories |
The retention categories: key, name, description, clock, actions, aml |
request_kinds |
access, rectification, erasure, restriction, objection, portability |
lawful_bases |
legal_obligation, contract, legitimate_interests, public_interest, consent, vital_interests |
data_categories |
The categories lawful bases are recorded for, with key and name |
Settings and registers
Section titled “Settings and registers”| Endpoint | Does | Permission | Success |
|---|---|---|---|
GET /api/v1/privacy/settings |
The data protection settings | privacy.view, privacy.manage or dsr.handle |
200 |
PUT /api/v1/privacy/settings |
Change them | privacy.manage |
204 |
GET /api/v1/privacy/activities |
The record of processing | privacy.view |
200 |
POST /api/v1/privacy/activities |
Add an activity | privacy.manage |
201 {"id": …} |
PUT /api/v1/privacy/activities/{id}, DELETE … |
Change or remove one | privacy.manage |
204 |
GET /api/v1/privacy/lawful-bases |
The lawful basis per data category | privacy.view |
200 |
PUT /api/v1/privacy/lawful-bases/{category} |
Set one | privacy.manage |
204 |
GET /api/v1/privacy/transfers |
The transfer register, with the hosting region and country | privacy.view |
200 |
POST /api/v1/privacy/transfers |
Add a transfer | privacy.manage |
201 {"id": …} |
PUT /api/v1/privacy/transfers/{id}, DELETE … |
Change or remove one | privacy.manage |
204 |
Settings fields
Section titled “Settings fields”| Field | Rules | Meaning |
|---|---|---|
jurisdiction |
Two letters, a known law or any country code | The main law. EU and GB are accepted |
extra_jurisdictions |
Up to 10 | Other laws that apply |
hosting_region |
Up to 64 characters | Where the data is hosted |
hosting_country |
Two letters | Transfers elsewhere are cross-border |
controller_name |
Up to 200 | The institution’s legal name; defaults to the workspace name |
dpo_user_id, dpo_name, dpo_email |
The DPO | |
registration_ref, registration_expires |
Registration with the data protection authority |
The response also has purge_enabled and laws (as in the reference data). The first read creates
the settings (jurisdiction and hosting country from the workspace’s country) and pre-fills the
registers.
Activity, lawful basis and transfer fields
Section titled “Activity, lawful basis and transfer fields”- Activity:
name(2-200),purpose,lawful_basis,data_subjects,data_categories,recipients(lists of up to 50),transfers,retention,security_measures. Pre-filled activities have akey. - Lawful basis:
basis,reference(up to 500),note(up to 2,000).{category}is one of the data category keys; an unknown one gives404. - Transfer:
recipient(2-200),recipient_role(processor,sub_processor,controller,regulator,group_company,other),destination_country,purpose,data_categories,safeguard(none_needed,adequacy,contract_clauses,binding_rules,consent,authority_approval,legal_obligation,other),reference,active. Responses addcross_borderandneeds_safeguard.
Retention and the purge
Section titled “Retention and the purge”| Endpoint | Does | Permission | Success |
|---|---|---|---|
GET /api/v1/privacy/retention |
Each category’s period, minimum and cut-off, the purge state, the last dry run and purge, and can_enable |
privacy.view |
200 |
PUT /api/v1/privacy/retention |
Change periods and actions | privacy.manage |
204 |
POST /api/v1/privacy/retention/dry-run |
Count what a purge would remove today, and record it | privacy.manage |
200 with the run |
PUT /api/v1/privacy/retention/purge |
{"enabled": true} or false |
privacy.manage |
204 |
GET /api/v1/privacy/runs?mode=dry_run|purge&limit= |
Recent dry runs and purges (default 20, at most 100) | privacy.view |
200 |
PUT /api/v1/privacy/retention takes the categories to change; others keep theirs:
{ "policies": { "transactions": { "retention_days": 2196, "action": "pseudonymise" }, "account_events": { "retention_days": 365 } }}retention_days is 1 to 36,500 and never below the minimum (422). action is delete or
pseudonymise where the category offers it; leave it out to keep the current one. Shortening a
period, or changing to delete, switches the purge off.
A run has mode, status (running, completed, failed), requested_by_name, cutoffs (per
category: retention_days, minimum_days, cutoff, action), counts (per category: eligible,
held, deleted, pseudonymised, partitions, kept, note), error, started_at and
finished_at.
Switching the purge on answers 409 unless a completed dry run is newer than the last policy or
jurisdiction change and less than 30 days old.
Legal holds
Section titled “Legal holds”| Endpoint | Does | Permission | Success |
|---|---|---|---|
GET /api/v1/privacy/holds?status=active|released |
Holds | privacy.view |
200 |
POST /api/v1/privacy/holds |
Place one | privacy.manage |
201 {"id": …} |
POST /api/v1/privacy/holds/{id}/release |
Release one: {"reason": "…"} (3-2,000 characters) |
privacy.manage |
204 |
{ "scope": "customer", "target_id": "0192f3a4-5b6c-7d8e-9f01-23456789abcd", "reason": "Litigation: Accra High Court suit 2026/114", "reference": "Court order of 2 October 2026"}scope is workspace (no target_id), case, customer or report. The response lists each hold
with its target_label (case or report number, or customer reference), who set and released it,
when, and why.
Data subject requests
Section titled “Data subject requests”| Endpoint | Does | Permission | Success |
|---|---|---|---|
GET /api/v1/privacy/requests?status= |
Requests (at most 500), open ones first by due date. status is active (not yet answered), a status, or empty for all |
dsr.handle or privacy.view |
200 |
POST /api/v1/privacy/requests |
Register one | dsr.handle |
201 with the request |
GET /api/v1/privacy/requests/{id} |
One request | dsr.handle or privacy.view |
200 |
PATCH /api/v1/privacy/requests/{id} |
Assign, verify identity, extend or withdraw | dsr.handle |
200 with the request |
POST /api/v1/privacy/requests/{id}/mlro-review |
The MLRO’s review: {"note": "…"} (3-2,000) |
reports.approve |
200 |
POST /api/v1/privacy/requests/{id}/clock/pause |
Stop the clock while waiting for identity or a clarification: {"reason": "…"} (3-1,000). Only where the request’s law allows it (UK), 422 otherwise |
dsr.handle |
200 with the request |
POST /api/v1/privacy/requests/{id}/clock/resume |
Restart the clock; the due date and any extension move later by the time it was stopped | dsr.handle |
200 with the request |
POST /api/v1/privacy/requests/{id}/complete |
Close a non-erasure request with its outcome | dsr.handle |
200 |
POST /api/v1/privacy/requests/{id}/erase |
Decide an erasure request: {"response_note": "…"} (optional) |
dsr.handle |
200 |
GET /api/v1/privacy/requests/{id}/export |
Download the zip for an access or portability request. It holds every row and is streamed, with a Content-Length |
dsr.handle and entities.view_pii |
200 application/zip |
Registering a request
Section titled “Registering a request”| Field | Required | Meaning |
|---|---|---|
customer_id |
Yes | The customer the request is about |
kind |
Yes | access, rectification, erasure, restriction, objection, portability |
law |
No | The law the deadline follows; default the workspace’s jurisdiction |
channel |
No | email, letter, branch, phone, portal, other (default) |
received_at |
No | When it reached the institution; default now. Not more than an hour in the future |
details |
No | What the person asked (up to 4,000 characters) |
identity_verified, identity_notes |
No | The identity check |
assignee_id |
No | Who handles it; they are notified |
The response has the request’s number, due_at, due_rule (the rule as the law words it, with its
citation), status (open, or mlro_review when flagged), tipping_off and overdue. A flagged
request never says why.
A request also carries:
| Field | Meaning |
|---|---|
can_pause_clock |
The request’s law lets the clock stop (today, the UK) |
clock_paused_at, clock_pause_reason |
Set while the clock is stopped. A stopped request is never overdue |
clock_paused_seconds |
How long the clock was stopped so far; already added to due_at and extended_due_at |
erasure_held |
An erasure is due (the retention period has ended) but a legal hold, an open case, an open screening match or a report still requires the records. erasure_due_at stays the day the period ended. Only the MLRO (reports.approve) is told: for everyone else it is always false, and the request looks like an ordinary scheduled erasure, because “postponed” with no legal hold in sight would reveal a case or a report |
hold_reasons |
Why the erasure is postponed, only on GET …/requests/{id} and only for the MLRO (reports.approve): legal holds on the workspace, the customer, a case or a report, open cases, and reports inside their retention period. Each has kind (workspace, customer, case, report, open_case, report_retention), label, reason and reference. Always empty for everyone else |
The MLRO check is repeated. Every GET of the list or of a request, and every action below,
checks open requests again and moves one to mlro_review (with tipping_off: true) when a case or a
report now concerns the person. An action attempted at that moment answers 409 the MLRO must review this request first.
Updating, completing and erasing
Section titled “Updating, completing and erasing”PATCHtakes any ofstatus(open,in_progress,withdrawn),assignee_id,unassign,identity_verified,identity_notes,extend_towithextension_reason(at least 3 characters). A flagged request can only be withdrawn until the MLRO has reviewed it.completetakesdecision(allowed per kind: access and portabilityexportedorrefused; rectificationrectifiedorrefused; restrictionrestrictedorrefused; objectionobjection_upheld,objection_rejectedorrefused),legal_basis(required to refuse or reject) andresponse_note.erasedecides from retention; you don’t choose the outcome. The response’sdecisioniserased,pseudonymisedorexempt_scheduled, withlegal_basis,erasure_due_atanderased_at, and, for the MLRO only,erasure_heldwhen a hold postpones an erasure that is due. See Erasure and the AML exemption.
Breaches
Section titled “Breaches”| Endpoint | Does | Permission | Success |
|---|---|---|---|
GET /api/v1/privacy/breaches |
The register (at most 500), with each breach’s next_due_at and pending_notifications |
privacy.view |
200 |
POST /api/v1/privacy/breaches |
Record a breach | privacy.manage |
201 with the breach |
GET /api/v1/privacy/breaches/{id} |
One breach with its notifications and tasks | privacy.view |
200 |
PUT /api/v1/privacy/breaches/{id} |
Change it | privacy.manage |
200 with the breach |
POST /api/v1/privacy/breaches/{id}/notifications/{nid} |
Record that a notification was sent: notified_at, reference, note |
privacy.manage |
204 |
POST /api/v1/privacy/breaches/{id}/tasks |
Add a task: title (2-300), optional assignee_id, due_at |
privacy.manage |
201 {"id": …} |
PUT /api/v1/privacy/breaches/{id}/tasks/{tid} |
{"done": true} or false |
privacy.manage |
204 |
GET /api/v1/privacy/breaches/{id}/export |
Download the report for the regulator (zip) | privacy.manage |
200 application/zip |
A breach takes title (3-200), description, data_categories, affected_count, laws (default
the workspace’s jurisdictions), risk (unlikely, risk, high_risk), occurred_at,
discovered_at (required; not in the future; not before occurred_at), contained_at, status
(open, contained, closed) and measures. Each notification has law, law_name,
recipient (authority or subjects), authority, due_at (null when the law sets no fixed
hours), rule, citation, required, notified_at, reference, note and overdue.