Run your workspace
Audit log
Every workspace has an immutable audit trail. It's a
super-admin (platform operator) surface — not a
customer feature — so it lives on the platform admin, not the customer
workspace. Open any workspace under
/admin/workspaces and click Audit log
(/admin/workspaces/{workspace}/audit) to browse,
filter, and inspect its entries. Customers never see it; the route
404s anyone who isn't a super-admin. Audit rows keep being written for
every workspace regardless of who can read them.
The entries can contain PII (lead emails, originating IPs), which is why viewing is restricted to the operator. To see exactly what a customer sees in their own interface, impersonate them from the workspace page instead.
What gets logged
The application writes to audit_logs for any action
that materially changes workspace state or surfaces compliance
exposure. Examples include:
- Member invitations, role changes, removals.
- Integration connect / disconnect (Slack, Teams, webhooks).
- Integration webhook delivery failures (so broken Slack URLs are visible without tailing logs).
- GDPR data-subject-request exports
(
dsr.exported) and erasures (dsr.erased). - Billing-plan changes initiated from the admin UI.
Action vocabulary
The application writes the following audit actions today. Filter
on the Action toolbar field to surface a specific
slug (substring match — agent shows every
agent.* row).
agent.created/agent.updated/agent.deleted/agent.bulk_deleted— workspace owner creates / edits / removes an AI agent. Bulk-delete fires one row per affected agent so cross-tenant scoping is visible.agent.published/agent.republished/agent.rolled_back— the publish lifecycle. Republished fires when a draft change is re-published on top of an existing live version.source.created/source.deleted— knowledge sources attached to an agent (URL crawls, files, Notion, Google Docs, etc.).workflow.created/workflow.updated/workflow.deleted/workflow.bulk_deleted— scripted-reaction workflows.member.invited/member.added/member.removed— invitation, direct-add (when the invitee already had a Pitchbar account), and seat removal.api_token.created/api_token.revoked/api_token.forgotten/api_token.purged— workspace API token lifecycle. Forgotten = hard-deleted (single row). Purged = bulk hard-delete of every revoked row for the workspace.platform_settings.updated— super-admin saved a section of/settings/system. Theaftercolumn records the section name + a list of changed non-sensitive keys; secret values (API keys, webhook secrets, SMTP passwords) are stripped before write so the audit row cannot leak credentials.integration.webhook_secret_rotated— admin rotated a webhook delivery secret.dsr.exported/dsr.erased— GDPR data-subject-request fulfillment.impersonation.started/impersonation.stopped— super-admin acted on behalf of a workspace member.
When an action fires from a context with no authenticated user
(queue worker, scheduled command, webhook callback), the row's
after.system_origin field carries a marker:
console, webhook_or_queue, or
background. Use this to distinguish "system did this"
from "we forgot to capture the actor".
Each row carries: action slug, entity type / id, actor user (or
System when triggered by a queue job), originating IP,
user agent, and a before / after JSON
snapshot.
Filtering
The toolbar accepts:
- Action — substring match (case-insensitive),
e.g.
dsrshows both exported and erased rows. - Entity type — dropdown populated with the distinct entity types currently in your workspace's log, so you only see options that actually have history.
- Actor email — exact match on the user's email; pass an unknown email to surface only system-initiated rows.
- From / To — bracket
created_at. Either bound is optional.
Filters compose; pagination respects the active filter set.
Retention
Audit rows are pruned daily via the scheduled command
audit:prune --days=365. Default retention is one
year — long enough to investigate "what changed two quarters
ago?" but bounded so the table doesn't grow without limit.
Customers with stricter compliance requirements (SOC2, HIPAA,
long-tail GDPR holds) should export their audit log via the
artisan command on their own cadence before this prune fires.
Override the window by passing a different --days
value in your routes/console.php.
# Dry-run: report what would be pruned, change nothing.
php artisan audit:prune --days=365 --dry-run
# Force a longer retention window.
php artisan audit:prune --days=730
Why no edit / delete
Audit rows are append-only. There is no UI to edit a row, and
the database has no updated_at column on
audit_logs. This is intentional — a compliance log
that can be rewritten is not a compliance log.
Performance
The table is indexed on (workspace_id, created_at)
and (entity_type, entity_id). A workspace with
tens of thousands of rows still serves the first page in under
50ms. If you need to bulk-export, query the table directly via
the workspace:audit-export Artisan command (planned)
rather than scraping the UI.