Backend
Activity log
An audit trail of who did what, when and from where, recorded automatically for account events and on demand for yours.
pleate_backend.audit records who did what, when and from where. Account events are logged automatically; your own actions take one call. Users see their own history (a security page), staff see everything (a back office table), and entries expire on their own.
An entry
json
{
"id": "6720d3b1c2d3e4f5a6b7c8d9",
"actor": "6720c1f4a3b2c1d0e9f8a7b6",
"actor_email": "ada@plate.dev",
"action": "billing.plan_changed",
"target_type": "pleate_billing.subscription",
"target_id": "6720d2aa…",
"target_repr": "Pro (monthly)",
"changes": { "plan": ["free", "pro"] },
"metadata": { "reason": "upgrade" },
"ip": "203.0.113.24",
"user_agent": "Mozilla/5.0 …",
"request_id": "9f8e7d6c5b4a3f2e",
"created_at": "2026-10-26T10:30:00Z"
}Recorded automatically
| Action | When |
|---|---|
| auth.login / auth.logout | Sign in and out. |
| auth.login_failed | Wrong password. Only the attempted email is kept. |
| auth.signup, auth.email_verified | Account created, email confirmed. |
| auth.password_changed, auth.password_reset | Password changed while signed in, or through a reset link. |
| auth.email_changed, auth.sessions_revoked | Email changed, devices signed out. |
| account.deleted | The account was deleted. |
| mfa.enabled, mfa.disabled, mfa.backup_code_used | When the mfa app is installed. |
| oauth.linked, oauth.unlinked | When the oauth app is installed. |
Logging your own
python
from pleate_backend.audit import log_activity, register
log_activity(
"billing.plan_changed",
target=subscription,
changes={"plan": ["free", "pro"]},
reason="upgrade", # extra keyword arguments go to metadata
)
# Every create, update and delete of a model, with field diffs:
register(Product, exclude=("password",)) # catalog.product.created|updated|deletedThe actor defaults to the signed in user of the current request, and IP, user agent and request id come from it too, thanks to AuditContextMiddleware (installed right after AuthenticationMiddleware). log_activity never raises: auditing must not break the action it records. Sensitive keys (password, secret, token, key_hash...) are masked in changes and metadata.
Endpoints
Mounted at /api/activity.
| Method | Path | Purpose |
|---|---|---|
| GET | /activity | My activity, newest first. Cursor list (cursor, page_size). Filters action and action__startswith (e.g. auth.). |
| GET | /activity/all | Staff only. Page list, search over actor email, target and action. Filters action, action__startswith, actor, target_type, target_id, created_after, created_before (ISO 8601). Ordering created_at. |
| GET | /activity/all/<id> | Staff only. One entry. |
A security page
const client = usePleateClient();
const feed = useCursorList(["activity", "auth"], (p) => client.activity.mine({ ...p, action__startswith: "auth." }));
<Timeline>
{feed.items.map((e) => (
<TimelineItem key={e.id} title={e.action} time={new Date(e.created_at).toLocaleString()} description={e.ip} />
))}
</Timeline>A staff table
const log = useList<ActivityLog>("/activity/all", { ordering: "-created_at", syncUrl: true });
<DataTable columns={columns} getRowId={(e) => e.id} {...log.tableProps} />Settings
PLEATE["AUDIT"]:
| Key | Default | Meaning |
|---|---|---|
| RETENTION_DAYS | 365 | Entries expire after this many days (TTL index). |
| LOG_READS | False | Also log successful GET requests as api.read. |