Docs / audit-log-api

Audit log API

← All docs

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

ParamTypeNotes
actionstringAction 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_kindstringShort slug, e.g. finding, endpoint, scan, api_token.
resultenumsuccess or failure.
fromISO 8601Inclusive lower bound on ts. Must include zone (Z or +HH:MM).
toISO 8601Exclusive upper bound on ts.
limitinteger1 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

CodeBodyReason
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_id field 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] .