Concepts

Audit log

Every consequential thing that happens in your project — authentication, organization changes, recovery requests, webhook activity — lands in audit_events. Filter, export, or stream it.

audit_events is the single platform-wide event log. Every Authio service (auth-core, management-api, scim, sso, fga, webhooks, audit-worker, billing) writes here for any state change worth surfacing. It's the source of truth for the dashboard's /audit view, the webhook subscriber, and audit-stream destinations (Datadog, Splunk, S3, Snowflake, …).

Event taxonomy

Event names follow <namespace>.<subject>.<verb> — e.g. auth.signin_attempt, webhook.endpoint.revoked, organization.member.invited. The current canonical list, by namespace:

auth.*

  • auth.signin_attempt — every primary auth (passkey, magic-link, OAuth) regardless of outcome. Carries score, decision, reasons, and the device/ip/UA in metadata.
  • auth.risk_evaluated — compact mirror of the decision with decision_id pointing at therisk_decisions row. See risk engine.
  • auth.blocked_by_risk_policy — emitted when a score lands ≥ threshold_block.
  • auth.step_up_required — emitted when a step-up challenge is created.
  • auth.step_up_satisfied — emitted when a step-up completes via a fresh passkey assertion or magic link.

organization.* / membership.* / invitation.*

  • organization.created, organization.updated, organization.deleted
  • membership.created, membership.role_changed, membership.status_changed, membership.removed
  • invitation.created, invitation.accepted, invitation.revoked

webhook.*

  • webhook.endpoint.created, webhook.endpoint.revoked, webhook.endpoint.secret_rotated
  • webhook.delivery.replayed

risk.*

  • risk.policy.updated — emitted by management-api on PUT /v1/risk/policy. Metadata carries the new thresholds and enabled-signal count.

Others

  • api_key.created, api_key.revoked
  • scim.user.synced, scim.group.synced
  • dashboard_operator.bootstrapped, dashboard_operator.added, dashboard_operator.removed
  • recovery.request.created, recovery.request.approved, recovery.request.denied
  • incident.created, incident.updated, incident.resolved
  • maintenance.scheduled, maintenance.started, maintenance.completed
New event types appear in your project's filter pills automatically — the dashboard reads GET /v1/audit/events/types to discover the action strings that have actually flowed through your project in the last 30 days, so you only see types you emit.

Retention & partitioning

audit_events is range-partitioned by created_at at monthly granularity (see migration 0016_audit_events_partition.sql). The authio_audit worker runs a partition manager loop at startup and every 24h:

  • Forward roll: CREATE TABLE IF NOT EXISTS next AUTHIO_AUDIT_FORWARD_MONTHS partitions (default 3). Inserts crossing month boundaries always land in a pre-allocated partition.
  • Drop old: any partition whose upper bound is older than AUTHIO_AUDIT_RETENTION_MONTHS (default 3) is DROP TABLE'd. This is the fast O(1) retention path that motivated partitioning in the first place — DELETE-by-date on a ~100M row audit_events would be catastrophic vacuum work; DROP PARTITION is metadata-only.

Set those env vars on the audit worker to extend the window — e.g. AUTHIO_AUDIT_RETENTION_MONTHS=12 for a-year-of-history projects.

Exporting

One-shot CSV / JSONL (dashboard)

On /audit, click Export CSV or Export JSONL. Both stream from GET /v1/audit/events.{csv,jsonl} with the currently-applied filters and cap at 100 000 rows per request. Cursor pagination internal to the export keeps memory flat regardless of result-set size.

# CSV columns
id, created_at, action, actor_type, actor_id,
user_id, project_id, target_type, target_id, ip,
user_agent, description

# JSONL: one row per line, full JSON shape including metadata

Programmatic export (API key)

curl -sN \
  -H "Authorization: Bearer sk_live_…" \
  "https://authiomanagement-api-production.up.railway.app/v1/audit/events.jsonl?from=2026-04-01T00:00:00Z&type=auth.risk_evaluated" \
  > events.jsonl

Real-time webhook subscriptions

Create a webhook endpoint at /webhooks/new and subscribe to specific event types (or * for everything). The webhooks worker delivers with HMAC signatures and an exponential-backoff retry; see Webhooks for the payload shape and verification snippets.

Audit-streams (SIEMs and warehouses)

Create an audit stream on /audit/streams to forward every event to Datadog Logs, Splunk HEC, S3, Snowflake, Sumo Logic, or a generic JSON webhook. Streams are ordered, cursor-tracked per stream, and survive worker restarts without dupes/gaps.

Filtering & search

GET /v1/audit/events supports keyset pagination plus the following filters — all combinable, all backed by indexed scans:

  • ?type=auth.signin_attempt,risk.policy.updated (comma-separated)
  • ?user=user_… — exact match on user_id
  • ?actor=… — exact match on actor_id
  • ?from=…&to=… — ISO timestamps
  • ?q=foo — substring across action / actor_id / target_id / metadata->>description
  • ?cursor=…&limit=… — keyset on (created_at, id)
The legacy GET /v1/audit-events endpoint (with the before= ISO cursor and action_prefix= filter) stays available indefinitely — existing customer integrations keep working.

See also