Audit log

Immutable record of security-relevant events on your account.

Every security-relevant action on your account is recorded in an audit log. It's designed for three use cases:

  • Situational awareness: if your account shows up logged in from a new IP at 3am, you find out
  • Incident forensics: when something goes wrong, reconstructing "who did what when" should take seconds, not hours
  • Compliance: SOC 2, ISO 27001, and most enterprise security questionnaires require an immutable log of authentication + admin events

The log is visible under Settings → Security → Recent activity and via the API.

GET/audit/logJWT

What gets recorded

EventWhen
login.successValid credentials + MFA, tokens issued
login.failedWrong password on an existing account
password.reset_requestedSomeone hit the reset-password endpoint with your email
password.changedA reset completed
mfa.enabledYou verified a TOTP setup
mfa.disabledYou turned off MFA
api_key.createdA new API key was minted
api_key.revokedAn API key was revoked
subscription.changedPaddle webhook posted a plan change
account.deletedYou triggered delete-account (logged before the cascade runs)

Each entry contains

  • id — monotonic, for paging
  • event_type — one of the strings above
  • event_data — event-specific JSON payload (e.g. { "key_prefix": "rpt_abcd1234" })
  • ip_address — best-effort, using Cloudflare → Nginx → request.client precedence
  • user_agent — truncated to 1024 chars
  • created_at — ISO-8601 UTC

Attribution

Entries are per-user, not per-workspace:

  • Your own logins, MFA toggles, key CRUD appear on your log
  • When you're a team viewer, you see your own activity — not the owner's and not other members'
  • The owner sees their own log — team activity of members is not aggregated there (by design; members' security posture is their own)

This is the opposite of usage, where we aggregate at the workspace level. Audit is per-identity.

Privacy

  • We never log passwords, tokens, full API keys, or webhook secrets — only prefixes / metadata
  • Entries are deleted via ON DELETE CASCADE when the user deletes their account, so GDPR erasure is automatic
  • Failed logins for unknown emails are intentionally not logged (no user to attribute them to, and logging them would enable email enumeration)

Request / response

GET /audit/log?limit=50&offset=0&event_type=login.success
Authorization: Bearer <jwt>
Query paramDefaultMeaning
limit50Max 200
offset0For paging
event_typeOptional exact-match filter

Response:

{
  "events": [
    {
      "id": 1291,
      "event_type": "api_key.created",
      "event_data": { "key_name": "Production", "key_prefix": "rpt_4f1e2d3c" },
      "ip_address": "203.0.113.42",
      "user_agent": "curl/8.7.1",
      "created_at": "2026-05-01T14:23:09.341Z"
    },
    {
      "id": 1290,
      "event_type": "login.success",
      "event_data": { "email": "alice@acme.com" },
      "ip_address": "203.0.113.42",
      "user_agent": "Mozilla/5.0 …",
      "created_at": "2026-05-01T14:22:55.012Z"
    }
  ],
  "total": 347,
  "has_more": true
}

Integrating with your SIEM

If you want audit events streamed to your SIEM in real time, subscribe to our webhooks — subscription.changed and api_key.revoked are already delivered there. For a full audit-log stream we recommend polling /audit/log with pagination every few minutes against your own cursor (the id field is monotonic).

Alerting on anomalies

A quick-win rule: alert when login.success appears from an IP or user-agent you haven't seen before on your account. The response body has everything you need — just diff against history.