Skip to content

Audit log

PerSQL records consequential actions to two append-only audit logs. Together they answer “who changed X?” — whether X is a table, a member’s role, or an API token.

LogScopeWhat it recordsWhere to find it
Namespace auditnamespaceDatabase CRUD, member/role changes, invitations, tokens, settings, billingSidebar → Audit log
DDL auditper databaseEvery schema-changing SQL statementDatabase → Migrations tab

A single append-only feed across the whole namespace, surfaced under Audit log in the sidebar. Visible to owners and admins only.

ActionNotes
database.created / .deleted / .forkedPlus branched, branch_reset, branch_deleted
namespace.updatedDisplay-name changes
namespace.member_role_changed / .member_removed
invitation.created / .revoked / .accepted
token.created / .revoked
endpoint.created / .published / .unpublished / .deleted
schedule.created / .deleted
policy.created / .updated / .deletedRLS policies
billing.checkout_startedStripe top-up Checkout session opened

Each row carries:

  • action — the dotted identifier above
  • actorUserId / actorTokenId — who did it (one or the other)
  • actorIpcf-connecting-ip at the time
  • targetType / targetId — what was acted on
  • metadata — JSON blob with action-specific context (slug, role, etc.)
  • createdAt — when PerSQL recorded the row
Terminal window
curl --cookie cookies.txt \
"https://api.persql.com/api/namespaces/acme/audit?limit=200&action=token.created"

Query parameters:

  • limit — up to 500, default 100
  • before — ISO timestamp; pagination cursor
  • action — exact-match filter on the action key

Response is an array ordered newest-first, with the actor’s { id, name, email } joined in for convenience.

The namespace audit log is append-only and currently retained indefinitely. Reach out if you need an export or a pruning policy.

The DDL audit log is per-database and records every schema-changing statement against that database. It lives on the Migrations tab next to the diff panel.

Anything matching the schema-DDL classifier:

  • CREATE (table, index, view, trigger)
  • ALTER
  • DROP
  • TRUNCATE
  • REINDEX
  • VACUUM
  • ATTACH / DETACH

Captured fields:

FieldNotes
sqlTextFirst 4 000 chars
statusok or error
errorMessageFirst 500 chars, only on error
userIdSet if the statement came via cookie auth
tokenIdSet if the statement came via bearer auth
createdAtWhen PerSQL recorded the row

Exactly one of userId / tokenId is populated; both being null means the statement came from internal machinery (e.g. import applying SQL inside the DO without a per-statement caller).

The audit log captures DDL from every path:

  • /api/.../query (console query box)
  • /v1/.../query and /v1/.../batch (bearer token)
  • /migrations/:id/apply (the Migrations tab)
  • /import and /load-csv (the bulk loaders) — recorded as ok once the underlying transaction succeeds
  • Per-database cap: 1 000 rows.
  • Cron prune runs alongside the slow-query prune (~10 % of minute ticks). Older rows fall off as new ones land.
Terminal window
curl --cookie cookies.txt \
"https://api.persql.com/api/namespaces/acme/databases/orders/audit?limit=200"

Response is an array of { id, sqlText, status, errorMessage, userId, tokenId, createdAt } ordered newest-first.