GDPR Requests

9 Aufrufe Markdown

The ten endpoints that read a client's data removal requests, settle them, and manage the feature's settings.

Overview

A client can ask for their data to be removed. These endpoints read those requests, settle them, and manage the feature's own settings.

Removal has three scopes and no way back: cutting access, anonymising the identifying data, deleting the user outright. Which one ran is in remove_action on the response.

Reference

Listing Requests

get/api/v1/admin/clients/gdpr-requests
Clients/GetGdprRequests admin

Returns the list of data requests coming from clients.

Query parameters 4
statusstringFilters by status.
typestringFilters by request type.
pageintDefaults to 1.
limitintDefaults to 25, maximum 100.
Response fields data[] — 5
idintRequest id.
user_idintThe client who asked.
typestringWhat was asked for, for example remove.
statusstringWhere the request stands, for example pending.
created_atstringWhen it arrived.
Meta 4
totalintTotal matching requests.
pageintThe page you are on.
limitintPage size.
next_pageintThe next page, or 0 on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -G 'https://panel.example.com/api/v1/admin/clients/gdpr-requests' \
  -H "Authorization: Bearer $API_KEY" \
  -d limit=50
const url = new URL('https://panel.example.com/api/v1/admin/clients/gdpr-requests');
url.searchParams.set('limit', '50');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
$url = 'https://panel.example.com/api/v1/admin/clients/gdpr-requests?' . http_build_query(['limit' => 50]);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->GetGdprRequests([], ['limit' => 50]);

Request Detail

get/api/v1/admin/clients/gdpr-requests/{rid}
Clients/GetGdprRequest admin

Returns one request. This is what you read before deciding.

Response fields data — 1
requestobjectThe request record.
idintRequest id.
statusstringStatus of the request.
remove_typestringThe scope asked for: block_access, identifying_data or all.
invoice_countintHow many invoices the client has. Worth reading before deciding: invoices fall under legal retention.
processedboolWhether it has been settled.
processed_byintId of the admin who settled it.
created_atstringWhen the request arrived.
Errors 2
not_found404No such request.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-requests/9' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-requests/9', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-requests/9');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->GetGdprRequest(['rid' => 9]);

$request = $response['data']['request'];

// Asking for 'all' on a client with invoices can collide with legal retention.
$hasInvoices = $request['invoice_count'] > 0;

Settling a Request

post/api/v1/admin/clients/gdpr-requests/{rid}/process
Clients/ProcessGdprRequest admin cannot be undone

Approves or refuses the request. On approval the data removal runs in the scope you choose.

Body 5
statusstringrequiredThe decision: remove, anonymize, destroy, or cancelled to refuse.
remove_typestringThe removal scope. Only meaningful when approving. block_access cuts access, identifying_data anonymises the identifying data, all deletes the user.
status_notestringA note on the decision. Put the reason here when refusing.
notificationboolSends the client a notification.
blacklistboolBlacklists or unblacklists the client in the same operation.
Response fields data — 4
processedboolWhether it was settled.
statusstringThe status that was applied.
remove_actionstringThe removal that actually ran: none, block_access, identifying_data or all.
blacklistintBlacklist state afterwards: 0 or 1.
Errors 4
not_found404No such request.
status_invalid422status is not one of the allowed values.
blocked_by_gate422A hook vetoed the operation. One of your own addons may be blocking the removal.
gdpr_delete_failed500The user data could not be removed.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/gdpr-requests/9/process' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"anonymize","remove_type":"identifying_data","status_note":"Request verified","notification":true}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-requests/9/process', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    status: 'anonymize',
    remove_type: 'identifying_data',
    status_note: 'Request verified',
    notification: true,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-requests/9/process');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'status'      => 'anonymize',
        'remove_type' => 'identifying_data',
        'status_note' => 'Request verified',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->ProcessGdprRequest([
    'rid'         => 9,
    'status'      => 'anonymize',
    'remove_type' => 'identifying_data',
    'status_note' => 'Request verified',
]);

$applied = $response['data']['remove_action'] ?? 'none';
Response
{
  "data": {
    "processed": true,
    "status": "anonymize",
    "remove_action": "identifying_data",
    "blacklist": 0
  }
}
{
  "error": {
    "code": "blocked_by_gate",
    "message": "The removal was blocked by a hook."
  }
}

Deleting a Request

delete/api/v1/admin/clients/gdpr-requests/{rid}
Clients/DeleteGdprRequest admin

Deletes the request record. It does not touch the client's data; only the record goes.

Response fields data — 2
deletedboolWhether the delete succeeded.
idintId of the deleted request.
Errors 1
not_found404No such request.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/gdpr-requests/9' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-requests/9', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-requests/9');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->DeleteGdprRequest(['rid' => 9]);

Reading the Settings

get/api/v1/admin/clients/gdpr-settings
Clients/GetGdprSettings admin

Returns whether the feature is on, which contract page is attached, and the canned denial reasons.

Response fields data — 4
enabledboolWhether the feature is on.
requiredboolWhether consent is mandatory.
contract_page_idintId of the attached contract page.
denial_reasonsarrayThe canned denial reasons. An empty array when there are none.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-settings' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-settings', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->GetGdprSettings();

Saving the Settings

put/api/v1/admin/clients/gdpr-settings
Clients/SaveGdprSettings admin

Turns the feature on or off and attaches the contract page.

Body 3
enabledboolTurns the feature on or off.
requiredboolMakes consent mandatory.
contract_page_idintId of the contract page to attach. List: gdpr-contracts.
Response fields data — 3
enabledboolWhether the feature is on after the write.
requiredboolWhether consent is mandatory.
contract_page_idintThe attached contract page.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/clients/gdpr-settings' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"enabled":true,"required":true,"contract_page_id":12}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-settings', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ enabled: true, required: true, contract_page_id: 12 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'enabled'          => true,
        'required'         => true,
        'contract_page_id' => 12,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->SaveGdprSettings([
    'enabled'          => true,
    'required'         => true,
    'contract_page_id' => 12,
]);

Searching Contract Pages

get/api/v1/admin/clients/gdpr-contracts
Clients/GetGdprContracts admin

Returns the pages that can be attached as the contract. This is where contract_page_id comes from.

Response fields data[] — 2
idintPage id — this is what you send as contract_page_id.
titlestringPage title.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-contracts' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-contracts', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-contracts');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->GetGdprContracts();

Listing Denial Reasons

get/api/v1/admin/clients/gdpr-denial-reasons
Clients/GetGdprDenialReasons admin

Returns the canned texts used when refusing a request.

Response fields data[]
datastring[]A plain list of reason texts — there are no ids here, the text itself is the identity.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->GetGdprDenialReasons();

Adding a Denial Reason

post/api/v1/admin/clients/gdpr-denial-reasons
Clients/AddGdprDenialReason admin

Adds a new canned reason to the list.

Body 1
valuestringrequiredThe reason text.
Response fields data[]
datastring[]The whole list after the add, not the added text alone.
Errors 1
value_required422value was empty.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"value":"The legal retention period has not ended"}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ value: 'The legal retention period has not ended' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['value' => 'The legal retention period has not ended']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->AddGdprDenialReason(['value' => 'The legal retention period has not ended']);

Deleting a Denial Reason

delete/api/v1/admin/clients/gdpr-denial-reasons
Clients/DeleteGdprDenialReason admin deleted by text

Removes a reason from the list. You send the text itself, not an id.

Body 1
valuestringrequiredThe text to remove. It has to match the stored one exactly.
Response fields data[]
datastring[]The reasons that remain. A text that did not match leaves the list unchanged — compare it to know whether anything went.
Errors 1
value_required422value was empty.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"value":"The legal retention period has not ended"}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ value: 'The legal retention period has not ended' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['value' => 'The legal retention period has not ended']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->DeleteGdprDenialReason(['value' => 'The legal retention period has not ended']);

Pitfalls

Read the invoice count before deciding

The request detail carries invoice_count. In most countries invoices fall under a legal retention period, so asking for all on a client with invoices can collide with that duty. That is why the field is in the response.

A hook can veto the operation

A blocked_by_gate error does not mean your request was wrong; it means an addon in the installation blocked the removal. If you wrote your own hook, look there first.

Deleting the request does not delete the data

The delete endpoint only removes the request record. The client's data stays exactly as it was; removal runs only through the process endpoint.

War das hilfreich?

Vielen Dank für Ihre Rückmeldung!

Brauchen Sie weitere Hilfe?

Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.