# GDPR Requests

https://dev.wisecp.com/es/gdpr-requests

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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -G 'https://panel.example.com/api/v1/admin/clients/gdpr-requests' \
  -H "Authorization: Bearer $API_KEY" \
  -d limit=50
```

```javascript
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();
```

```php
$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);
```

```php
$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

requestobject 7 fieldsThe 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-requests/9' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-requests/9', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$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);
```

```php
$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_invalid422`status` 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 JavaScript PHP (HTTP) PHP (Internal)

```bash
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}'
```

```javascript
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();
```

```php
$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);
```

```php
$response = Api::Clients()->ProcessGdprRequest([
    'rid'         => 9,
    'status'      => 'anonymize',
    'remove_type' => 'identifying_data',
    'status_note' => 'Request verified',
]);

$applied = $response['data']['remove_action'] ?? 'none';
```

Response 200 422

```json
{
  "data": {
    "processed": true,
    "status": "anonymize",
    "remove_action": "identifying_data",
    "blacklist": 0
  }
}
```

```json
{
  "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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/gdpr-requests/9' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
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();
```

```php
$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);
```

```php
$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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-settings' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-settings', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$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);
```

```php
$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 JavaScript PHP (HTTP) PHP (Internal)

```bash
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}'
```

```javascript
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();
```

```php
$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);
```

```php
$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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-contracts' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-contracts', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$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);
```

```php
$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 JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/gdpr-denial-reasons', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$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);
```

```php
$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_required422`value` was empty.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
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"}'
```

```javascript
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();
```

```php
$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);
```

```php
$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_required422`value` was empty.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
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"}'
```

```javascript
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();
```

```php
$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);
```

```php
$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.

## Related Articles

- [Client Endpoints](https://dev.wisecp.com/en/client-endpoints)
- [Blacklist and Duplicates](https://dev.wisecp.com/en/blacklist-and-duplicates)
