Skip to main content
Version: 6.3 (Upcoming)

Privacy API

Inspect information requests and consents, create requests and export data through the com_privacy workflow.

See the introduction for authentication, request formats and error handling.

Routes​

Paths are relative to /api/index.php. Enable the corresponding Web Services plugin.

MethodsPathController tasks
GET, POST/v1/privacy/requestsrequests.displayList, requests.add
GET/v1/privacy/requests/{id}requests.displayItem
GET/v1/privacy/requests/export/{id}requests.export
GET/v1/privacy/consentsconsents.displayList
GET/v1/privacy/consents/{id}consents.displayItem

Forms and data​

Relevant forms and extension parameter manifests are:

Joomla selects additional parameter fields according to the extension, client or field type. The OpenAPI specification includes these core parameter variants.

FieldJSON type or roleMeaning
emailstringRequired address for an information request.
request_typestringRequest action, such as export or remove, as defined by the form.
requested_at, statusresponse fieldsRequest time and workflow state.
GET /privacy/requests/export/{id}request IDExport action for an existing request.
GET /privacy/consentscollectionConsents, separate from information requests.

List and item endpoints for requests and consents, together with the export endpoint, require core.admin on com_privacy. There is no consent DELETE route.

Creation, confirmation, export and removal are separate stages. Creating a request does not complete export or delete a user. The export action requires request_type: "export" and confirmed status 1; otherwise the model supplies no export data and the view raises a route-not-found exception.

Privacy plugins supply the export domains, but the API serialises their array as one resource. Its attributes can therefore be an array of domain names and descriptions, with an empty resource ID. Protected domain items are not included by that serializer. A missing ID can also raise a PHP warning and fail under stricter error handling. Do not treat this response as a complete personal-data export or as the export file produced in the administrator. Use the administrator export workflow when you need that complete result. There is no generic PATCH or DELETE workflow for request records.

Export response​

The export representation is distinct from the information request itself. For example, GET /privacy/requests/export/42 can return the following structure:

{
"data": {
"type": "export",
"id": "",
"attributes": [
{
"name": "example",
"description": "Example privacy domain"
}
]
}
}

This domain metadata is not a complete export of the person's records. Inspect the operation's schema and use the supported privacy workflow rather than assuming that an HTTP 200 response contains all personal data.

Backend server-side requests​

Enable Web Services - Privacy and use an account with core.admin on com_privacy. This reads requests without creating one or exporting data.

Joomla HTTPFactory​

Use this request in a Joomla component, plugin or console command. Set the API URL, token and record values for the target site. Keep the token in your integration's secret store. HTTP and JSON errors throw exceptions for the calling extension to handle.

<?php
declare(strict_types=1);

use Joomla\Http\HttpFactory;
use Joomla\Uri\Uri;

$siteUrl = 'https://example.org';
$token = 'REPLACE_WITH_YOUR_API_TOKEN';
$headers = [
'Accept' => 'application/vnd.api+json',
'Content-Type' => 'application/json',
'X-Joomla-Token' => $token,
];

$uri = (new Uri($siteUrl))
->setPath('/api/index.php/v1/privacy/requests');
$uri->setQuery([
'page' => [
'offset' => 0,
'limit' => 20,
],
]);
$http = (new HttpFactory())->getHttp(['follow_location' => false]);
$response = $http->get(
$uri,
$headers,
30
);

$status = $response->getStatusCode();
$body = (string) $response->getBody();

if ($status < 200 || $status >= 300) {
throw new \RuntimeException('HTTP ' . $status . ': ' . $body);
}

$document = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

if (!empty($document['errors'])) {
throw new \RuntimeException(json_encode($document['errors'], JSON_THROW_ON_ERROR));
}

if (isset($document['success']) && $document['success'] === false) {
throw new \RuntimeException('The API action reported failure.');
}

echo json_encode($document, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR), PHP_EOL;

curl​

This complete POSIX shell example requires curl 7.76 or later. On Windows, use a compatible shell such as Git Bash or WSL.

API_BASE='https://example.org/api/index.php/v1'
TOKEN='REPLACE_WITH_YOUR_API_TOKEN'

curl --silent --show-error --fail-with-body --include --globoff \
--request GET \
--url "$API_BASE/privacy/requests" \
--header 'Accept: application/vnd.api+json' \
--header 'X-Joomla-Token: '"$TOKEN" \
--get \
--data-urlencode 'page[offset]=0' \
--data-urlencode 'page[limit]=20'

Server-side successful response​

HTTP 200 OK, with Content-Type: application/vnd.api+json. The response excerpt below shows the resource structure. Responses can also contain relationships, links and metadata. IDs and values depend on the site.

{
"data": [
{
"type": "requests",
"id": "42",
"attributes": {
"email": "[email protected]",
"requested_at": "2026-01-15 12:00:00",
"status": 0
}
}
]
}

After creation, read /privacy/requests/{id} and check its workflow state. Export and removal need separate completion checks.

Frontend browser requests​

These examples read privacy requests with GET /privacy/requests. Calling the API from the browser still uses administrator models and ACL. Follow the browser setup and authentication guidance, including loading Joomla's core script for Joomla.request. Replace the site origin and API path, then enter the signed-in operator's personal API token when prompted. Keep shared integration tokens on the server.

Joomla.request​

(async () => {
const token = window.prompt('Enter your personal Joomla API token');

if (!token) {
throw new Error('An API token is required.');
}

const url = new URL('https://example.org');
url.pathname = '/api/index.php/v1/privacy/requests';
url.search = new URLSearchParams({
"page[offset]": "0",
"page[limit]": "20"
}).toString();

let xhr;

try {
xhr = await Joomla.request({
url: url.toString(),
method: 'GET',
headers: {
Accept: 'application/vnd.api+json',
'X-Joomla-Token': token,
},
promise: true,
onBefore(request) {
request.timeout = 30000;
},
});
} catch (error) {
// Joomla.request rejects statuses other than 200, including 204.
if (!(error instanceof XMLHttpRequest)) {
throw error;
}

xhr = error;
}

if (xhr.status < 200 || xhr.status >= 300) {
throw new Error(`HTTP ${xhr.status}: ${xhr.responseText || xhr.statusText}`);
}

const text = xhr.responseText;

const document = text.trim() ? JSON.parse(text) : null;

if (document?.errors && Object.keys(document.errors).length > 0) {
throw new Error(JSON.stringify(document.errors));
}

if (document?.success === false) {
throw new Error(document.message || 'The API action reported failure.');
}

console.log(document);
return document;
})();

Native fetch()​

(async () => {
const token = window.prompt('Enter your personal Joomla API token');

if (!token) {
throw new Error('An API token is required.');
}

const url = new URL('https://example.org');
url.pathname = '/api/index.php/v1/privacy/requests';
url.search = new URLSearchParams({
"page[offset]": "0",
"page[limit]": "20"
}).toString();

const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), 30000);
let response;
let text;

try {
response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/vnd.api+json',
'X-Joomla-Token': token,
},
credentials: 'omit',
redirect: 'error',
signal: controller.signal,
});
text = await response.text();
} finally {
window.clearTimeout(timeout);
}

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
}

const document = text.trim() ? JSON.parse(text) : null;

if (document?.errors && Object.keys(document.errors).length > 0) {
throw new Error(JSON.stringify(document.errors));
}

if (document?.success === false) {
throw new Error(document.message || 'The API action reported failure.');
}

console.log(document);
return document;
})();

Both examples check the HTTP status and API error fields. An empty successful body produces null; a JSON collection has an array in data.

Browser successful response​

HTTP 200 OK returns a collection such as:

{
"data": [
{
"type": "requests",
"id": "42",
"attributes": {
"email": "[email protected]",
"requested_at": "2026-01-15 12:00:00",
"status": 0
}
}
]
}

This collection excerpt omits other attributes, relationships, links and metadata. An empty result has "data": [].

Errors​

These responses apply to server-side and browser clients. Check both HTTP status and response body. Wording depends on the site language, plugins and server configuration. The following examples show possible error responses.

HTTP statusPossible cause
401Authentication is absent or invalid, or API login is not allowed.
400Email/type validation fails or a workflow action is invalid.
403The user cannot access privacy management or export data.
404The request or consent cannot be found.

Form validation can produce HTTP 400 Bad Request:

{
"errors": [
{
"title": "Invalid field: Email"
}
]
}

Authentication failure can produce HTTP 401 Unauthorized:

{
"errors": [
{
"title": "Forbidden"
}
]
}

After an interrupted or failed write, read the resource before retrying. Saving may have occurred before a later error.

References​