Audit log
The endpoint that pulls the project's audit log with a cursor, and every field of an audit entry, for SIEM collectors.
This endpoint hands the project's audit log to a collector, oldest entry first, one page at a time. Paths are relative to https://console.dpconsent.com/consent/sdk/v1. How to set up a collector is in Send the audit log to your SIEM.
Endpoints#
GET /audit-log#
Returns the next page of audit entries after a cursor.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret, live | audit:read |
Ignored | None |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
after |
string, up to 200 | No | The nextCursor of the previous page. |
since |
string | No | Where to start when there is no cursor: a UTC day, 2026-10-02, or a time with Z or an offset, 2026-10-02T05:00:00Z. Ignored when after is sent. |
limit |
integer, 1 to 1,000 | No | Entries in the page. Default 500. |
Without after or since, the feed starts from the project's first entry.
Request
curl "https://console.dpconsent.com/consent/sdk/v1/audit-log?since=2026-10-01&limit=500" \
-H "Authorization: Bearer $PIEDAP_AUDIT_KEY"const query = new URLSearchParams({ after: cursor, limit: '1000' });
const response = await fetch(
`https://console.dpconsent.com/consent/sdk/v1/audit-log?${query}`,
{ headers: { Authorization: `Bearer ${process.env.PIEDAP_AUDIT_KEY}` } },
);
const page = await response.json();Response
200, with Cache-Control: no-store:
{
"items": [
{
"id": "3f6c1d2e-8b0a-4c5e-9d71-2a4b6c8e0f13",
"createdOn": "2026-10-03T05:08:09.189304Z",
"action": "lookup",
"resource": "consent-record",
"resourceId": null,
"userId": "6f0c2a54-…",
"userEmail": "ananya.iyer@example.in",
"organizationId": "…",
"accountId": "…",
"projectId": "…",
"ipAddress": "203.0.113.24",
"userAgent": "Mozilla/5.0 …",
"details": {
"method": "POST",
"path": "/api/v1/api/consent-record/customer-user/lookup-subject",
"statusCode": 200,
"subjects": ["9c41e07a2b5d"],
"subjectType": "EMAIL",
"environment": "all",
"records": 1
}
}
],
"nextCursor": "MjAyNi0xMC0wM1QwNTowODowOS4xODkzMDRafDNmNmMxZDJl…",
"hasMore": false,
"lagSeconds": 60
}| Field | Type | Description |
|---|---|---|
items |
array | Entries, oldest first. See Fields of an entry. |
nextCursor |
string or null | Send it as after for the next page. On an empty page it repeats your cursor. |
hasMore |
boolean | true when another page is ready now. |
lagSeconds |
integer | Entries younger than this are held back. |
Errors
| Status | Code | When |
|---|---|---|
400 |
INVALID_CURSOR |
after is not a cursor this endpoint returned. |
400 |
None. The message is a list | since or limit is not valid. |
403 |
SCOPE_REQUIRED |
A publishable key, or a secret key without audit:read. |
403 |
LIVE_KEY_REQUIRED |
A test key. Audit entries belong to the project, not to an environment. |
429 |
None | The key read the feed 60 times this minute. Wait for Retry-After. |
Also the errors every call can return.
Cursor rules#
- Entries come oldest first, ordered by
createdOnto the microsecond, thenid. - Entries younger than
lagSeconds, 60 unless the operator changesAUDIT_FEED_LAG_SECONDS, are held back. An entry whose write commits within that time is not skipped. - An entry whose write commits later than that reaches the feed late or not at all. The download and standard output still have it.
- An empty page returns your cursor unchanged. Call again later with it.
- Delivery is at least once. Remove duplicates by
id. - A key may read the feed 60 times a minute, set by
AUDIT_FEED_CALLS_PER_MINUTE, on top of its own budget.
Fields of an entry#
| Field | Type | Description |
|---|---|---|
id |
string | The entry's id. |
createdOn |
string | When it was written, in UTC to the microsecond. |
action |
string | What was done. See Actions. |
resource |
string | The area: consent-record, subject-request, audit-log, consent-import, notice-campaign, processor, processor-task, sdk_key, subject and others. |
resourceId |
string or null | The target, such as a record or key id. |
userId, userEmail |
string or null | Who did it. null for a secret key or a job. |
organizationId, accountId, projectId |
string | The tenant. |
ipAddress |
string or null | The address the request came from, in clear. These are your team's and your servers' addresses. |
userAgent |
string or null | The browser or client. |
details |
object or null | See Keys inside details. |
Actions#
| Action | Written when |
|---|---|
create, update, delete, publish, archive, rotate, import, resolve |
A console change |
withdraw, erase |
A withdrawal or an erasure from the console |
export |
A property export, a receipt, an audit log download, or a notice-campaign export |
view |
Someone opened a record or a rights request |
lookup |
A person search in Records, or Find a person in a notice campaign |
close, issue |
A notice campaign was closed, or links were issued |
replay |
Failed webhook events were replayed |
acknowledge |
A processor report was recorded in the console |
revoke, reveal, request_live, cancel_live_request, approve, decline |
Key actions |
purge |
Records were deleted under Keep evidence for |
FIND, EXPORT, WITHDRAW, ERASE, LINK_IDENTIFIERS, LINK_REQUEST |
A secret key acted on a person, resource subject |
CHECK_BUDGET_EXCEEDED |
A key ran out of its consent check budget, once a minute |
CAMPAIGN_LINKS_READ |
A key read a page of notice links |
ISSUE |
The job finished minting a batch of notice links |
ACKNOWLEDGE_PROCESSOR_TASKS |
A key recorded processor reports |
Keys inside details#
| Key | Present on |
|---|---|
method, path, statusCode, error |
Console entries. path never holds a query string. |
subjects, subjectType, environment, records |
lookup. subjects holds the first 12 characters of each hash, never the identifier. |
via, keyId, keyName |
Every entry a secret key wrote. via is secret key, or job for the link minting job. |
subject, environment, records |
Secret-key calls on a person. subject is the first 12 characters of the hash. |
query, searched, rows, complete |
Downloads. A search is recorded as searched: true, never its text. |
batchId, count |
CAMPAIGN_LINKS_READ |
batchId, sequence, linkCount |
ISSUE |
limit, environment |
CHECK_BUDGET_EXCEEDED |
environment, recorded, unchanged, refused |
ACKNOWLEDGE_PROCESSOR_TASKS |
purgedRecords, retentionDays, cutoff, consentIds |
purge |
Standard output#
Each entry is also written as one JSON line on the API's standard output, unless AUDIT_LOG_STDOUT=false. The line holds the fields above, flat, plus:
| Field | Value |
|---|---|
context |
AUDIT |
level |
log |
message |
audit <action> <resource> |
timestamp |
When the line was written |
stored |
false when the database did not store the entry |
{"level":"log","message":"audit view consent-record",
"timestamp":"2026-10-03T05:08:09.402Z","context":"AUDIT","stored":true,
"id":"…","createdOn":"2026-10-03T05:08:09.189304Z","action":"view",
"resource":"consent-record","resourceId":"cns_…","userId":"…",
"userEmail":"ananya.iyer@example.in","organizationId":"…",
"accountId":"…","projectId":"…","ipAddress":"203.0.113.24",
"userAgent":"Mozilla/5.0 …","details":{"method":"GET",
"path":"/api/v1/api/consent-record/customer-user/fetch-one/cns_…",
"statusCode":200}}