Appearance
Audit & notifications
Every state change in Nucleus writes an audit event. A subset of those events project into the notifications customers see in the portal bell + dropdown. Same data, two layers — you can rebuild the second from the first.
Audit events
When something happens — a spoke is provisioned, a federation request is approved, a token is revoked, a backup fails — the control plane writes a row:
json
{
"id": "...",
"kind": "spoke_provisioned",
"actor": { "id": "...", "email": "..." },
"objectType": "spoke",
"objectId": "...",
"metadata": { "size": "medium", "domain": "..." },
"ip": "...",
"userAgent": "...",
"at": "2026-04-28T08:43:11.123Z"
}The event is the source of truth. Dashboards, notifications, and customer reports all derive from it.
Notifications
Customers don't see every audit event — most are operational noise (auth_login, internal_metric_emit). Notifications are a curated whitelist of customer-visible kinds, projected onto a per-user feed:
spoke_provisioned,spoke_destroyed,spoke_actionedfederation_request_approved/_rejectedtenant_backup_failedbilling_subscription_refunded,billing_subscription_past_due- … and roughly a dozen more
A notification is just an audit event the user is allowed to see, filtered by the user's federation/spoke memberships. There's no separate notifications table — the read state is tracked separately, the content is the audit row.
TIP
That's why marking a notification "read" takes the audit-event id, not a notifications id — the notification is the event, not a copy.
What customers see
In the portal:
- Bell in the top bar — count of unread, dropdown with the latest 10
- Toasts — anything that arrives while the customer is logged in pops as a transient toast (errors stick longer than info)
- /notifications page — full archive with All / Unread tabs, paginated by
beforetimestamp cursor
On the API:
GET /api/notifications— list (cursor-paginated)GET /api/notifications/unread-count— for the badgePOST /api/notifications/:id/read— mark one readPOST /api/notifications/read-all— mark everything visible read
See Notifications API for the full surface.
Retention
Audit events are retained indefinitely on the hosted plan; on self-host it's whatever your Postgres retention strategy allows. The audit log is append-only — events never get edited or deleted, even when the underlying object (spoke, user, token) is destroyed. That's deliberate: a post-mortem usually needs to ask "what did we do to this thing before it disappeared?".