Every action you take in Outworx Docs that mutates state — pushing a spec version, changing a collaborator's role, rotating an API token, flipping a project from private to public — gets a row in the audit log. SOC2 auditors expect that. Compliance teams ask for that. And the moment something goes sideways ("who set the production docs to private last Tuesday?") you'll be glad it's there.
This guide covers what we record, where to find it, how to pipe it into your SIEM, and how the SOC2 evidence dump path works.
What's recorded
Every mutation that goes through the dashboard, the v1 REST API, or a system path (cron jobs, webhook deliveries, plan-limit enforcement) generates one audit log row. The action codes are stable, UPPER_SNAKE strings — pattern-match on them in your log pipeline without parsing the human-readable label.
| Category | Codes |
|---|---|
| Projects | PROJECT_CREATED, PROJECT_UPDATED, PROJECT_DELETED, PROJECT_VISIBILITY_CHANGED, PROJECT_CUSTOM_DOMAIN_CHANGED |
| Branding & theme | BRANDING_UPDATED, THEME_UPDATED, CUSTOMIZATION_UPDATED, ENDPOINT_CUSTOMIZATION_UPDATED, ENDPOINT_CUSTOMIZATION_DELETED |
| Versions | VERSION_CREATED, VERSION_UPDATED, VERSION_DELETED, VERSION_PROMOTED_DEFAULT, VERSION_DEACTIVATED, VERSION_REACTIVATED, VERSION_BASE_URL_CHANGED |
| Specs | SPEC_UPLOADED, SPEC_EDITED, SPEC_DELETED, SPEC_SYNCED, SPEC_URL_CHANGED |
| Previews | PREVIEW_CREATED, PREVIEW_DELETED |
| Collaborators | COLLABORATOR_INVITED, COLLABORATOR_REMOVED, COLLABORATOR_ROLE_CHANGED |
| API access | TOKEN_CREATED, TOKEN_REVOKED, UPLOAD_TOKEN_GENERATED, UPLOAD_TOKEN_ROTATED, UPLOAD_TOKEN_REVOKED |
Each row carries:
actor_user_id— who performed the action (NULL for system events).action— one of the codes above.target_type+target_id— the resource that was touched.project_id— set on every project-scoped event so per-project queries are one indexed scan.metadata— JSONB with the field-level details. Always includessource(dashboard/api/system), and includestoken_idwhen the action came from a personal access token. The forensic search "find every action this leaked token ever performed" is one filter:metadata->>token_id = '<uuid>'.created_at— server timestamp.
The audit log is a Business-plan feature. Free / Pro accounts skip the writes entirely — there's no viewer for them and we'd rather not pay for unread rows.
Reading the log in the dashboard
The fastest way to inspect activity is the dashboard:
/settingshas a "Recent activity" card with the last 5 events at a glance./settings/activityis the full viewer with filters: date range (UTC day boundaries), actor, project, and action category. Filters live in URL params, so deep-linking to a specific window for an auditor is just sending them a URL.
Each row shows a one-line description that summarises the metadata in plain English ("bob@acme.com: editor → viewer (project override)", "Changed: Name, Visibility, Accent color"). Click "Details" on any row to see the raw metadata JSON.
CSV export for SOC2 evidence
When an auditor asks for "the full activity log for Q1", you do not want to be screen-scraping. Click Export CSV on /settings/activity and the same filter window downloads as a 14-column RFC-4180 CSV:
"timestamp","action","action_label","actor_id","actor_email","actor_name","source","token_id","project_id","project_slug","project_name","target_type","target_id","metadata"- Project + actor uuids are resolved to slug + email so the CSV reads cleanly without joins.
- The
metadatacolumn carries the full JSON blob for every row — auditors can drill in without losing the structured data. - Filename includes the date window:
outworx-audit_from-2026-01-01_to-2026-04-01.csv. - The endpoint sets
Cache-Control: private, no-store— these dumps must not sit in any proxy or CDN cache.
The CSV path is also exposed at /api/audit/export.csv?since=YYYY-MM-DD&until=YYYY-MM-DD&actor=<uuid|system|all> for scripted pulls. Authenticated as the owner via session cookie; if you need a token-authed dump for a CI evidence pipeline, drop us a line — that's tracked.
Real-time pipe to your SIEM
Settings → Audit log delivery & retention → set a Webhook URL to start streaming events as they happen. We POST one JSON body per row:
POST https://your-receiver.example.com/audit
Content-Type: application/json
X-Outworx-Event: PROJECT_UPDATED
X-Outworx-Delivery: 8c7f...e1a3
X-Outworx-Signature: sha256=ab12...cd9f
User-Agent: Outworx-Audit-Webhook/1
{ "id": "8c7f...e1a3",
"account_user_id": "...",
"actor_user_id": "...",
"action": "PROJECT_UPDATED",
"target_type": "project",
"target_id": "p_...",
"project_id": "p_...",
"metadata": { "source": "dashboard", "fields": ["name", "is_public"] },
"created_at": "2026-05-09T20:23:14.000Z"
}Verify the signature in your receiver:
import { createHmac, timingSafeEqual } from "crypto";
function verify(rawBody: string, header: string | undefined, secret: string) {
if (!header?.startsWith("sha256=")) return false;
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const provided = header.slice("sha256=".length);
if (provided.length !== expected.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}Use the raw body bytes for HMAC — don't re-serialize JSON before checking. The X-Outworx-Delivery header carries the audit event uuid, so receivers that occasionally see retries can dedupe by it.
Delivery is best-effort: a 5xx or network failure is logged on our side but does not block the underlying mutation, and we don't queue retries. If you need durability, the receiver should write to its own queue immediately and ack with a 200 — same pattern as Stripe webhooks.
Retention
Rows live in the audit_log table for the retention window you set (Settings → Audit log delivery & retention). Default is 730 days (2 years), which covers the standard SOC2 Type II evidence window. Allowed range: 30 days to 10 years.
A daily prune job at 03:23 UTC deletes rows past the cutoff. If you need rows older than the prune cutoff, the canonical store is your SIEM via the webhook above — that's why webhook delivery is independent of retention. Rows ship in real time, so your receiver has a copy regardless of how short the dashboard retention is.
Permissions for collaborators
Only the account owner can read the audit log. Collaborators — even Editors — never see Settings, so they don't see the activity feed. This is by design: the audit log is forensic evidence and shouldn't be visible to the actors whose actions it records.
If you need a delegated "compliance auditor" role, talk to us — that's tracked but not yet shipped.
What audit-log rows are NOT
A few things to set expectations:
- Reads aren't audited. Opening the docs page, hitting
GET /api/v1/projects, or running a/chatquery doesn't generate a row. The audit log is a mutation log; if you need read-side analytics, the Project Analytics tab is the surface for that. - Mock-server traffic isn't in the audit log. Mock-server requests are captured separately on the Mock tab — that table is for debugging your integrations, not for SOC2 evidence.
- Webhook deliveries to YOUR webhooks aren't audited. If you're using Outworx's webhook tester / proxy, the event captures are in
/webhooks, not the audit log.
See also
- Personal access tokens guide — how to mint a PAT, and how
metadata.token_idflows from there to the audit log. - REST API reference — every action that has a v1 endpoint generates the same audit row a dashboard click would.
- SOC2 trust page — broader controls beyond the audit log.
Ship your API docs in under a minute.
Upload your OpenAPI, Swagger, or GraphQL spec and get beautiful hosted docs with AI chat and a per-project MCP server — free forever for 2 projects.