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.
What gets recorded
| Event | When |
|---|---|
login.success | Valid credentials + MFA, tokens issued |
login.failed | Wrong password on an existing account |
password.reset_requested | Someone hit the reset-password endpoint with your email |
password.changed | A reset completed |
mfa.enabled | You verified a TOTP setup |
mfa.disabled | You turned off MFA |
api_key.created | A new API key was minted |
api_key.revoked | An API key was revoked |
subscription.changed | Paddle webhook posted a plan change |
account.deleted | You triggered delete-account (logged before the cascade runs) |
Each entry contains
id— monotonic, for pagingevent_type— one of the strings aboveevent_data— event-specific JSON payload (e.g.{ "key_prefix": "rpt_abcd1234" })ip_address— best-effort, using Cloudflare → Nginx →request.clientprecedenceuser_agent— truncated to 1024 charscreated_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 CASCADEwhen 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 param | Default | Meaning |
|---|---|---|
limit | 50 | Max 200 |
offset | 0 | For paging |
event_type | — | Optional 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.