Platform guides
05 · Audit + compliance

The audit trail.

Every mutation on your workspace writes an append-only audit row. Read it for support, export it for SOC2, query it for incident response.


1

What gets captured

The workspace audit log records:

  • Member lifecycle — invites created / accepted / revoked, members added / removed / role-changed.
  • Role + policy mutations — custom role create / update / delete, managed policy bindings.
  • Service activation — services enabled / disabled, settings changes.
  • PAT lifecycle — mints, rotations, revokes, policy edits.
  • Workspace mutations — updates to display name, slug renames (admin-only), deletion.
  • Workspace deletion — the last row is the deletion itself; everything before is preserved per legal- hold rules (see Section 6).

What's NOT in workspace audit: per- service mutations live in the service's own audit log. Auth app changes → Auth's audit_log. Mail message-log entries → Mail's message log. Social moderation actions → Social's moderation audit. C-6 in the coherence review notes the dispersion; a unified feed is a planned follow-up.


2

Read it

GET /v1/workspaces/:slug/audit-logs
curl 'https://api.platform-auth.productcraft.co/v1/workspaces/acme/audit-logs?limit=50' \
  -H 'authorization: Bearer <cookie-or-pak>'
{
  "data": [
    {
      "id":          "...",
      "action":      "workspace.member.role_changed",
      "actor_id":    "<owner-account>",
      "actor_type":  "platform_user",
      "resource":    "workspace_member",
      "resource_id": "<member-account>",
      "metadata":    { "from_role": "admin", "to_role": "owner" },
      "ip":          "203.0.113.4",
      "created_at":  "2026-05-11T..."
    },
    {
      "id":          "...",
      "action":      "workspace.api_key.created",
      "actor_id":    "<owner-account>",
      "actor_type":  "platform_user",
      "resource":    "platform_api_key",
      "resource_id": "<pak-id>",
      "metadata":    { "name": "github-actions-staging-deploy", "scopes_count": 5 },
      "ip":          "192.0.2.1",
      "created_at":  "2026-05-11T..."
    }
  ],
  "pagination": { "next_cursor": "...", "has_more": true }
}

3

Query patterns

Common filters:

  • action=workspace.member.role_changed — every role change.
  • actor_id=<pat-id> — what did this PAT do?
  • since=2026-04-01T00:00:00Z&until=2026-04-30T... — a date range for the monthly compliance review.
  • resource=platform_api_key — every PAT- related action.
  • Combine: action=workspace.api_key.* & since=... for “all PAT lifecycle in the last quarter.”

4

Export for compliance

For SOC2 / ISO27001 / annual review-style asks, paginate through the full log + cache to your own object storage. Per-call cap: 1000 rows. The cursor-paginated stream covers an arbitrary date range; throttled to a couple requests/sec so a single tab doesn't hammer.

Export script (bash, jq)
#!/usr/bin/env bash
set -euo pipefail
WS=acme
SINCE="2026-01-01T00:00:00Z"
UNTIL="2026-12-31T23:59:59Z"
PAT="${PRODUCTCRAFT_PAT}"

CURSOR=""
PAGE=0
> audit-export.jsonl

while :; do
  RESP=$(curl -fsS "https://api.platform-auth.productcraft.co/v1/workspaces/$WS/audit-logs?since=$SINCE&until=$UNTIL&limit=1000&cursor=$CURSOR" \
    -H "authorization: Bearer $PAT")

  echo "$RESP" | jq -c '.data[]' >> audit-export.jsonl

  HAS_MORE=$(echo "$RESP" | jq -r '.pagination.has_more')
  if [[ "$HAS_MORE" != "true" ]]; then break; fi

  CURSOR=$(echo "$RESP" | jq -r '.pagination.next_cursor')
  PAGE=$((PAGE+1))
  echo "page $PAGE complete"
  sleep 0.5
done

echo "Done. $(wc -l < audit-export.jsonl) rows."

5

Permission-denial audit

A subtle but useful capture: every 403 from the workspace-policy gate writes an audit row. Useful for spotting under-scoped PATs.

action=authz.denied rows include the requested action + resource URN + which policy refused it. Frequent denials on the same PAT usually mean it needs to be re-scoped (or that someone is fishing for permissions they shouldn't have).


6

Retention + workspace deletion

Audit rows are append-only. We don't expose a delete endpoint, and operator-side bulk deletion requires a legal-hold-style customer-support ticket.

When you delete a workspace (Chapter 1), the audit rows are purged 30 days after the deletion event — that retention window covers most regulator+customer dispute windows. For longer retention, export before deletion.


7

Common compliance asks + how to answer them

  • “Who has access to our workspace?” — list members + PATs, plus their roles + scopes. Console renders this; API: GET .../members + GET .../api-keys.
  • “Show me every action taken by the user we just offboarded.” ?actor_id=<account>. Plus their PATs: filter workspace.api_key.created + by actor_id to find which they minted.
  • “What changed in the last 30 days?” ?since=<30d-ago>. Group by action for the executive view, by actor_id for the per-person view.
  • “Prove we revoked the leaked PAT.” ?action=workspace.api_key.revoked&resource_id=<pat-id>. The row carries timestamp + actor + the IP that did the revoke.

What's next

You've finished the series

From here: