The audit log records every authentication event, finding-status change, secret operation, webhook delivery, admin action, and authenticated API call within a workspace. It is append-only and mirrored to a tamper-evident audit channel at the database layer.
GET /api/v2/audit
curl -sS "https://penaxtra.com/api/v2/audit?action=finding.status_changed&from=2026-04-01T00:00:00Z" \
-H "Authorization: Bearer $TOKEN"
Required scope: audit:read. Result set is tenant-scoped; newest first; capped per the limit parameter (default 50, max 200).
Query parameters
| Param | Type | Notes |
|---|---|---|
action | string | Action slug. Must match ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*){1,2}$ (e.g. auth.login_success, finding.status_changed, api.call). |
target_kind | string | Short slug, e.g. finding, endpoint, scan, api_token. |
result | enum | success or failure. |
from | ISO 8601 | Inclusive lower bound on ts. Must include zone (Z or +HH:MM). |
to | ISO 8601 | Exclusive upper bound on ts. |
limit | integer | 1 to 200; default 50. |
Filtering by actor_user_id, target_id, and cursor pagination beyond limit are planned.
Response
{
"data": [
{
"id": 42891,
"ts": "2026-05-22T10:24:33Z",
"actor_user_id": "<user uuid>",
"actor_ip": "203.0.113.42",
"action": "finding.status_changed",
"target_kind": "finding",
"target_id": "<uuid>",
"before_jsonb": { "status": "open" },
"after_jsonb": { "status": "triaged" },
"result": "success",
"request_id": "<uuid>"
}
]
}
tenant_id is not echoed back in the row payload since every result is already scoped to the calling token's tenant by RLS; surfacing it again would be redundant noise.
Errors
| Code | Body | Reason |
|---|---|---|
400 | {"error":"invalid_action_filter"} | action does not match the slug regex or exceeds 64 chars. |
400 | {"error":"invalid_target_kind"} | target_kind is not a short lowercase slug. |
400 | {"error":"invalid_result"} | result is not success or failure. |
400 | {"error":"invalid_from"} / {"error":"invalid_to"} | Timestamp is not ISO 8601 with explicit zone. |
403 | {"error":"scope_missing"} | Token lacks the audit:read scope. |
Stream events (planned)
A server-sent-events endpoint for tail-following the audit log is planned. Until then, poll with from=<last-seen-ts> at a conservative cadence (no more than once every five seconds).
Security notes
- Append-only at the database level; the underlying table has no UPDATE or DELETE grant for any application role.
- A tamper-evident database channel mirrors every write at the storage layer, so even an actor with database-administrator access leaves a second-source trail.
- Per-tenant audit retention is configurable from one day to ten years; older events are pruned automatically.
- The
request_idfield correlates with the same id surfaced in webhook deliveries and in the platform request log; use it to stitch a single customer request across multiple subsystems. - Free-text filter parameters are regex-whitelisted at the API boundary in addition to PDO parameter binding, so the audit log itself cannot become an injection vector even with a leaked token.
Related
Last reviewed: 2026-06-13. Reviewed by: Engineering. Content type: Developer documentation. Reach the maintainers: [email protected] .