Audit Logs
Record admin, billing, and security events to the audit_logs table with @repo/audit, and read them from the admin and org audit views.
@repo/audit records who did what to which entity - admin actions, billing changes, and auth security events - into the audit_logs table. It has no config flag: audit logging is always-on infrastructure, gated only by read access (platform and org admins) and a retention window.
Recording an entry
In a request handler, build a request-scoped logger that pre-fills actor, IP, and user-agent, then call .log(action, entityType, entityId, options?).
import { AUDIT_ACTIONS, createAuditContext } from "@repo/audit";
const auditCtx = createAuditContext(c.req.raw, session); // actorType from session role
await auditCtx.log(AUDIT_ACTIONS.USER_DELETED, "user", userId);| Function | Use when | actorType |
|---|---|---|
createAuditContext(req, session?, clientIp?).log(...) | Inside a request handler | "admin" or "user" (from session role) |
auditSystem(action, entityType, entityId, metadata?) | Background jobs and webhooks (no request) | "system" |
audit(entry) | Lowest-level insert; never throws (failures are logged) | from entry |
Every write path routes through audit(), so a logging failure can never break the caller's flow - no try/catch needed at the call site.
Actions and metadata
AUDIT_ACTIONS (packages/audit/src/types.ts) is the single source of truth for what can be audited - pass a constant, never a raw string.
| Group | Example constants |
|---|---|
| Admin | USER_BANNED, USER_UNBANNED, USER_DELETED, USER_ROLE_CHANGED, ORG_DELETED |
| Billing | PLAN_SET, PLAN_CLEARED, CREDITS_ADDED, CREDITS_REMOVED, CREDITS_SET, AUTO_TOPUP_TRIGGERED, PRODUCT_QUANTITY_SET |
| Auth security | LOGIN_SUCCESS, PASSWORD_CHANGED, TWO_FACTOR_ENABLED, TWO_FACTOR_DISABLED |
| Org management | MEMBER_INVITED, MEMBER_ROLE_CHANGED, MEMBER_REMOVED |
| API keys | API_KEY_CREATED, API_KEY_REVOKED, API_KEY_USED |
entityType is "user" | "organization"; actorType is "user" | "admin" | "system" | "api_key". The metadata JSON column is free-form, but typed shapes are exported for structured detail.
| Shape | Fields |
|---|---|
BillingChangeMetadata | before/after (plan, credits), reason, source |
BanMetadata | reason, expiresAt |
RoleChangeMetadata | before, after |
MemberMetadata | memberId, memberEmail, role |
An admin acting through impersonation is recorded twice over: the actor stays the impersonated user, and impersonated_by carries the admin's id.
Reading logs
Two routes read the table. Both paginate and enrich rows via enrichLogsWithEntityDetails, which attaches the current entityName, entityImage, and entityEmail from the live user or org - so an entry still shows a readable label after a rename.
| Route | File | Guard | Scope | Filters | Sort |
|---|---|---|---|---|---|
GET /admin/audit-logs | packages/api/src/routes/internal/admin/audit-logs.ts | adminGuard (platform admin role) | All logs | action, entityType, entityId, actorId, startDate/endDate | timestamp, action, or entityType (sortOrder asc/desc) |
GET /audit | packages/api/src/routes/internal/audit.ts | orgAdminGuard (org owner/admin) | Caller's orgId only | action, entityType | timestamp desc (fixed) |
Six indexes back these queries: four composite (entity, org, action, actor, each paired with timestamp), a standalone audit_logs_timestamp_idx, and a partial audit_logs_impersonated_by_idx (WHERE impersonated_by IS NOT NULL) that keeps the impersonation review query cheap.
Retention
A weekly maintenance job deletes rows older than cacheConfig.retentionDays.auditLogs (default 90 days); adjust it in packages/config/src/cache.ts. See Background jobs for the scheduler.
Authorization
adminGuard gate audited admin actions and audit-log reads.Background jobs
Notifications
Impersonation
API
Finance dashboard
The admin home at /admin - revenue per currency, stamped vendor cost, margin, whether usage pays for itself, and a ledger-integrity check.
Impersonation
Sign in as another user to reproduce their problem - how a session starts and ends, what the self-target guard covers, and what the audit log attributes.