Audit log
Every project-affecting action, who did it, and when - the actual event catalog, not an aspirational one.
What gets recorded
Every state-changing action against a project lands in an append-only audit trail. Each event carries:
- action - a dotted event name from the catalog below
- actor - who did it: a dashboard user, an API key, a platform operator, or the system itself
- metadata - action-specific context (job IDs, backup IDs, labels, ticket references)
- created_at - when
For asynchronous operations, the event is recorded when the action is accepted (the job enqueued), so the trail reflects intent the moment it happens - the job's own success or failure is tracked on the job itself.
Event catalog
These are the events the backend emits today. No others exist.
| Event | Recorded when |
|---|---|
project.deleted | Project deletion enqueued |
project.claimed | An ephemeral database claimed into the organization |
project.credentials_rotated | Credential rotation enqueued |
project.restore_overwrite_started | A production-overwrite restore enqueued |
project.restore_pitr_overwrite_started | An in-place point-in-time overwrite enqueued (carries the requested and effective restore times) |
project.restore_time_clamped | A point-in-time target past the latest restorable point was clamped (clamped_by: control_plane or host) |
project.provisioning_retried | A failed provisioning retried |
project.major_upgrade_requested / project.major_upgrade_confirm_requested / project.major_upgrade_rollback_requested | A major upgrade, its confirm or its rollback enqueued |
project.app_role_enable_requested / project.app_role_rotation_requested | The runtime role enabled / rotated |
backup.expired | A backup's stored object was found gone and the backup marked expired |
project.import_preflight | Import preflight run against a source |
project.import_upload_created | Import upload slot created |
project.import_started | Import enqueued |
project.pre_operation_backup_created | The existing database was backed up before a recreate import or restore dropped it (carries the backup ID and label) |
project.integration.connected | Vercel / Netlify / Clerk integration connected |
project.integration.upsert | Integration settings created or updated |
project.integration.delete | Integration disconnected |
project.integration.env_synced | An on-demand env re-push to Vercel, Netlify or Cloudflare queued |
preview.ttl_extended | A preview database's TTL pushed out |
restore_point.created / restore_point.deleted | Restore point pinned / removed |
cutover.requested | Cutover request opened |
cutover.acked / cutover.rejected | Operator acknowledged / rejected the request |
cutover.promote_started | Operator started executing the cutover |
production_overwrite_restore | Cutover promotion completed (carries the pre-cutover backup ID) |
api_key.created / api_key.revoked | Organization or project API key minted / revoked |
webhook_endpoint.created / webhook_endpoint.updated / webhook_endpoint.deleted | Webhook endpoint lifecycle |
webhook_endpoint.secret_rotated | Webhook signing secret rotated |
studio.row_inserted / studio.row_updated / studio.row_deleted | Row edited through Studio |
organization.notification_preferences_updated | The organization's email preferences replaced (organization trail only) |
Worth noticing what is not here: reads. Listing projects, viewing connection strings, and running SELECTs do not generate audit events. The trail answers "who changed what", not "who looked at what".
Viewing the trail
- Dashboard - the project overview shows recent activity, rendered with friendly actor and action labels.
- API -
GET /v1/projects/{id}/audit-eventsreturns the full trail, newest first, with alimitparameter.GET /v1/organizations/{orgID}/audit-eventsreturns the organization's trail, including events with no project, such as webhook endpoint and email preference changes. See the API reference.
curl -s -H "X-API-Key: $CAPYDB_API_KEY" \
"https://capydb.dev/api/capydb/v1/projects/$PROJECT_ID/audit-events?limit=50" \
| jq '.audit_events[] | {action, actor_kind, created_at}'There is no CLI command for the audit trail today.
Retention and durability
- No retention window is applied. Events are not pruned on a schedule; the trail persists for the life of the organization.
- Events survive project deletion. Deleting a project does not erase its audit history - the events remain attached to the organization (with the project reference cleared), so "who deleted it" is itself answerable afterwards.
- Audit writes on destructive paths are designed not to fail silently: if an audit write ever fails after a destructive action was already accepted, the miss is logged loudly on our side rather than swallowed.
Two trails, two jobs
The audit log records administrative actions against the platform. If you need to know what happened inside the database - slow queries, table activity - that is Observability's job. And if you want events pushed to you instead of queried, webhooks cover lifecycle outcomes.