Skip to main content

Audit Log API

Align records an audit trail of mutations and access-control denials. Organization admins can retrieve it through a filterable API.

Append-only is enforced by the database

The application only ever inserts into the audit table, and the database backs that up: a trigger on audit_logs rejects any UPDATE, DELETE or TRUNCATE against an existing row, for every role, including the one the application itself connects as. A wrong entry is corrected by writing a new row, never by editing or removing one. This does not stop the table's owner disabling or dropping the trigger by DDL (ALTER TABLE ... DISABLE TRIGGER, DROP TRIGGER), or a superuser additionally setting session_replication_role = replica for a session. Those two need different privileges. Measured on PostgreSQL 16.15 with a NOSUPERUSER table owner: session_replication_role is refused with permission denied to set parameter, while the DDL succeeds and the write then goes through. So an owner who is not a superuser has the DDL route and not the replication-role one, and no in-database mechanism can stop the owner of a table. Each of those is a deliberate, auditable act rather than an accidental or casual mutation, which is the property this control actually gives you.

Endpoint​

GET /admin/audit-logs
  • Auth: authenticated request (session cookie or Authorization: Bearer <token>), org_admin role required (403 otherwise).
  • Scope: results are restricted to the caller's tenant.

Query parameters​

ParameterTypeDescription
actionstringFilter by action. Values are namespace:verb with a colon, e.g. access:denied, decision:deleted, connector:enabled, data:exported. Exact match only.
actorstringFilter by the actor (user) who performed the action.
resourceTypestringFilter by resource type.
resourceIduuidFilter by a specific resource.
fromISO 8601Start of the time range.
toISO 8601End of the time range.
limitint (1-1000)Page size (default 100).
offsetintPagination offset.

Example​

curl -s "$ALIGN_API/admin/audit-logs?action=access:denied&from=2026-06-01T00:00:00Z&limit=50" \
-H "Authorization: Bearer $ADMIN_TOKEN"

Each entry includes the actor, action, resource, a details object, and a timestamp.

Coverage and scope​

What is recorded today: access-control denials (including the org_admin guard on this endpoint and the five other admin endpoints that share it - data export, right to erasure, legal hold, session revocation - when the caller is authenticated), connector enable/disable, credential access, an OAuth connection completing and a workspace being linked, MCP tool invocation, decisions being created, detected or deleted, a decision's visibility changing, permission grants and revocations, decision-link reconciliation, user role, permission and activation changes, authentication events (login, logout and the authentication-failure cases below), a GitHub webhook failing to resolve or post a comment against a linked ticket, and the GDPR data-lifecycle events (export, deletion, legal hold, session revocation).

Authentication failures are recorded only when the request carries a known tenant: a deactivated account's login attempt, an OIDC token with no email claim, and a revoked token or session presented again. A request with no token, an invalid or expired token, an unmatched API token, or a missing tenant header in demo or service-token mode is rejected but not written to this table - audit_logs is tenant-scoped, and writing a row would mean inventing a tenant for a caller we cannot identify. That would also let an unauthenticated caller drive unbounded writes into the audit table.

Legal hold applies to decision snapshots, not to audit records - see Data Retention. No retention job purges audit_logs, so the trail is retained for the life of the tenant.