Workspace operations
A workspace accumulates state: keys, members, rules, settings, and a few million events. This page covers the controls that manage that state at the workspace level — the audit log that records every change and who made it, the archive switch that stops ingestion without destroying anything, the sandbox wipe that does destroy something (on purpose, with a typed confirmation), and the production / sandbox partition those operations respect.
TL;DR
- Every state-changing action lands in an append-only audit log — keyed by event ID, created once, never updated. A duplicate write is silently ignored; the first record always stands.
- Audit entries record who and where from: the actor's UID, email, IP address, and User-Agent, alongside what changed and why.
- Archiving a workspace is soft and reversible. It stamps an archive flag, stops new ingestion (API keys fail closed), and freezes entitlements at their last state. You can recover an archived workspace for 30 days; after that the data is permanently deleted.
- Clearing sandbox data is hard and irreversible. Every
env=sandboxrecord is deleted from both stores. Production data is never touched. There is no undo. - Both destructive actions require typing the workspace name — checked in the dashboard and again on the server, so a malformed request can't bypass the gate.
- Production and sandbox are one workspace, two partitions. The environment is determined by which API key the request presented; the dashboard's env switcher flips every surface between the two views.
The audit log
The audit log is the workspace's immutable change record. Every state-changing action — a key rotated, a member removed, a setting flipped, an entitlement granted, a subscription event processed — appends one entry. Entitlement-affecting decisions are recorded regardless of outcome: applied, rejected, and no-op decisions all land, so the log can always answer "why does this customer have access?" — including when the answer is "we received the event and deliberately did nothing."
Append-only, by construction
Entries are keyed by event ID and written with a create-only operation — there is no code path that updates or deletes an audit entry. If the same action is somehow audited twice, the second write is discarded and the first record stands untouched. Nothing in the dashboard re-derives or reformats stored values either: every field renders verbatim as written.
Who, and where from
Every human-initiated entry captures four actor columns: the actor's UID, email, IP address, and User-Agent — taken from the authenticated request that performed the action, not self-reported. System-generated entries (a Stripe webhook processed, a scheduled job) record the system as the actor instead. The point is forensic: an audit log that says what changed but not who, from where can't settle the question it exists for.
Categories
All categories share one log; the dashboard filters by category. The full set:
| Category | What lands in it |
|---|---|
security | Auth events, key lifecycle (created / rotated / revoked), sign-in and sign-out, PII allow-list edits. |
settings | Workspace settings edits — allowed origins, framework, app identity. Sandbox clears land here too. |
member | Team invite, accept, remove, role change. |
billing | Tier transitions, payment method changes, ledger reconciliations. |
error | Error-issue lifecycle: resolved, assigned, ignored, priority overridden, commented. |
workspace | Project-level lifecycle: rename, archive, business-model switch. |
alert_rules | Custom alert rule created, updated, deleted. |
catalog | Catalog backfill triggers and mapping changes. |
support | Support ticket lifecycle. |
migration | Migration discovery runs, backfill triggers, signal status flips. |
manual | Manual entitlement grants and revokes from the dashboard. |
rail | Subscription state changes, refunds, and disputes from Stripe, Apple, and Google — every rail decision, with its outcome (applied, no-op, rejected). |
Reading it
The Audit log page in the dashboard gives you time ranges (24h / 7d / 30d / 90d / All), the category tabs above, and a free-text search across event IDs, actors, types, subscription and customer IDs, reasons, and metadata. Entries load 100 at a time with a cursor-paginated Load more. Clicking a row opens a detail drawer with the full record — identifiers, actor (email, UID, IP), the decision and its reason, both timestamps (when the action happened and when it was processed), and the raw metadata JSON.
Export produces a flat file with every stored column: event ID, category, type, actor email / UID / IP, rail, decision, derived signal, subscription and customer IDs, fingerprint, reason, metadata, and both timestamps in ISO 8601. The export contains exactly what the table shows under your current filters — same rows, all columns.
Archiving a workspace
Archiving stops a workspace without destroying it. It is the right tool for a product you're sunsetting, a workspace created by mistake, or anything you want out of the way but not gone.
Concretely, archiving does three things:
- Marks the project archived — a timestamp, the archiving user, and an optional reason are written to the project. No data is deleted.
- Stops new ingestion, immediately. The archive flag is fanned out to every API key the project owns, and key resolution fails closed: any SDK or server call presenting one of the project's keys is rejected from that moment. This check sits on the key-lookup hot path, so there is no window where an archived project quietly keeps eating events.
- Freezes entitlements at their last known state. No new rail events are processed, so nothing transitions.
The archive itself is audited automatically — a workspace archived entry records the actor and timestamp, and a matching entry is written if the workspace is later unarchived (which restores ingestion by clearing the same flag the keys check).
The confirmation gate
Archiving requires typing the workspace's exact name. The dashboard checks it, and the server checks it again — trimmed, case-sensitive — before doing anything. A request that skips the dashboard cannot skip the gate. You must also be a member of the project, and archiving an already-archived workspace is rejected.
Archive is destructive in spirit, recoverable in fact: an archived workspace can be restored within 30 days, after which the archived data is permanently deleted. Restore deliberately doesn't live next to the Archive button in the Danger zone — you don't put "undo destruction" alongside destructive actions.
Clearing sandbox data
Clearing sandbox data wipes every env=sandbox record from the workspace so you can start test runs fresh. Unlike archiving, this one is irreversible — the records are deleted from both storage layers, and there is no recovery window.
What gets deleted — everything tagged env=sandbox for the project:
- Events, customers, subscriptions, and purchases in the primary store, plus each sandbox customer's entitlements.
- Analytics rows in the warehouse — the events table and both property tables. The warehouse delete runs as a background mutation, so sandbox rows disappear from queries within minutes rather than instantly.
What survives — everything that isn't a sandbox record:
- All production data. Live events, customers, subscriptions, purchases, and entitlements are untouched, by query construction: every delete is scoped to
env = sandbox. - The workspace itself — apps, API keys (your sandbox keys keep working, so new test data can flow immediately), members, settings, and alert rules.
- The audit log. In fact the wipe adds to it: a
sandbox.clearedentry records who cleared, when, and exactly how many records of each kind were deleted.
The same gates as archiving apply: you must be a project member, and the workspace name must be typed exactly — verified on the server, not just in the dialog. When the wipe completes, the dashboard reports the counts back ("Wiped 1,847 events, 12 subs") so you can confirm what happened.
Internal-traffic filtering and bot filtering are query-time views over data that's still stored. Clearing sandbox data is not — the rows are gone from both stores. If a test run produced data you want to keep, export it first.
Environments
Every record in a workspace — event, customer, subscription, purchase, entitlement — carries an env of production or sandbox. The two never mix: separate keys, separate webhooks, separate ledger. Sandbox events cannot contaminate production metrics.
The key decides the environment. Every app carries two publishable keys — a live key and a test key. An event ingested with the live key is production; the test key, sandbox. The environment is derived from which key the request presented at the door, never from a stored app setting — so there is no configuration state that can silently flip where your data lands. (Apps created before dual keys existed carry a single legacy key, and only there does the app's stored environment setting decide.)
The dashboard mirrors the same partition with the environment switcher in the top bar: Live · Production or Sandbox. Switching reloads the dashboard so every surface re-reads in the new environment — there is no half-switched state where one chart shows live data and another shows test data. Production is the default. The full story — test-mode rails, sandbox webhooks, when to use which — is in Sandbox vs production.
Related
- Sandbox vs production — the environment partition in depth: test-mode rails, sandbox webhooks, and the dual-key model.
- API keys & authentication — key types, rotation, and revocation; the same fail-closed resolution that enforces archive.
- Internal traffic — the other dashboard filter axis: your own activity, excluded at query time, never deleted.