Audit Log
The audit log records who did what, and when, across the org - offboarding actions, integration changes, exports, and administrative changes. Entries are hash-chained (each entry's hash covers the previous entry's hash), so tampering with or deleting a row breaks the chain and can be detected.
These endpoints sit under /api/audit-log and use your regular dashboard session, not an API key. Access is role-based: viewing and exporting require superadmin, admin, or hr.
List audit log entries
GET /api/audit-log
Query parameters
| Param | Type | Description |
|---|---|---|
limit | integer | Results per page (default 50, max 100) |
offset | integer | Row offset for pagination (default 0) |
action | string | Filter by exact action name |
actorId | UUID | Filter by the user who performed the action |
from | date | Only entries created on or after this date |
to | date | Only entries created on or before this date |
search | string | Matches against target, details, or actor name |
Response 200 OK
{
"rows": [
{
"id": "9e1c2f3a-...",
"action": "offboarding_stage_changed",
"target": "Jordan Lee",
"details": "Stage changed from notice_period to it_revocation",
"created_at": "2024-06-02T14:30:00.000Z",
"is_signed": true,
"actor": { "id": "usr_abc123", "full_name": "Jane Smith" }
}
],
"total": 1480,
"limit": 50,
"offset": 0
}is_signed just reflects whether the row has a hash recorded - it doesn't tell you whether the chain around it is intact. Use /verify for that.
Export the audit log
GET /api/audit-log/export
Exports up to 5,000 matching rows. Rate-limited to 10 exports per 15 minutes per org.
Query parameters - same filters as the list endpoint, plus:
| Param | Type | Description |
|---|---|---|
format | string | csv (default) or json |
Response - a file download. CSV columns are ID, Timestamp, Actor, Action, Target, Details, Entry Hash, Prev Hash. The JSON format returns { "exported_at": "...", "rows": [...] } with each row's hash chain fields included.
Both formats include an X-Content-Digest: hmac-sha256=<digest> response header - an HMAC of the exported body, computed server-side, so you can confirm the file wasn't altered after it left PurpletGo.
The export itself is logged as an audit_log_exported entry.
Verify chain integrity
GET /api/audit-log/verify
Requires the tamper-evident audit log feature (Enterprise). Rate-limited to 2 requests per minute per org.
Query parameters
| Param | Type | Description |
|---|---|---|
limit | integer | Max entries to check (default 10000, max 50000) |
Response 200 OK
{
"ok": true,
"checked": 1480,
"broken": []
}If any link in the chain has been tampered with, ok is false and broken lists the affected entries. Every verification run is itself written back to the audit log as audit_chain_verified.
Retention settings
GET /api/audit-log/retention · PUT /api/audit-log/retention
Superadmin only. Controls how many months of audit history the org retains.
{ "audit_retention_months": 24 }PUT accepts a value between 3 and 120 and requires the tamper-evident audit log feature.
Archives
GET /api/audit-log/archives · POST /api/audit-log/archive
Enterprise orgs can archive their full audit log to Google Cloud Storage - either on a schedule (handled internally) or on demand via POST /archive (superadmin only, rate-limited to 4 per hour, requires AUDIT_ARCHIVE_BUCKET to be configured for the deployment).
GET /archives lists past archive runs:
{
"archives": [
{
"id": "b2c3d4e5-...",
"gcs_path": "gs://purpletgo-audit-archives/audit-logs/org_id/2024-06-01T00-00-00-000Z.json",
"file_digest": "hmac-sha256=...",
"row_count": 48213,
"from_ts": "2023-01-01T00:00:00.000Z",
"to_ts": "2024-06-01T00:00:00.000Z",
"is_auto": true,
"created_at": "2024-06-01T00:05:00.000Z",
"exported_by_name": null
}
]
}POST /archive returns the newly created archive record in the same shape.