Audit Log
The audit log is an immutable record of every significant action in a workspace: who did what, when, from which IP, on which object, and at what risk level. Every event carries a HMAC-SHA256 MAC field that proves the record has not been tampered with. Events are retained for 7 years. The audit log cannot be cleared, truncated, or modified by any role.
When to use
Use the audit log to:
- Investigate a failed or unexpected run
- Verify who approved or rejected an approval step
- Confirm when a secret was last rotated or accessed
- Respond to a security incident by reconstructing the sequence of events
- Satisfy compliance requirements (SOC2 B2 evidence)
- Review GDPR erasure completeness
Key concepts
Event — a single audit record. Every event has a globally unique eventId with prefix evt_ and is time-sortable.
Risk levels — each event is assigned a risk level:
low— routine operational events (run started, workflow draft saved)medium— configuration or data access events (agent updated, connection updated)high— sensitive operations (secret accessed, authz denied, connection accessed)critical— highest-impact operations (member erased, gateway policy changed)
MAC field — a HMAC-SHA256 digest computed over the canonical event body at write time. The MAC is stored alongside the event and cannot be altered retroactively. Use it to verify event integrity.
PersonID — a field on every event that links the actor to a data subject identity for GDPR purposes. On POST /workspace/members/{userId}/erase, all personId values for that user are replaced with a tombstone across the event history. The event data itself is preserved.
Audit digest — a scheduled email summarising audit events. Configurable frequency (daily, weekly, monthly), recipients, and risk level filter. Sent via SES.
Event structure
| Field | Description |
|---|---|
eventId | evt_* prefixed, time-sortable unique ID |
eventType | Dot-separated event name (e.g. run.started) |
actorId | ID of the user, agent, system, or integration that caused the event |
actorKind | user | agent | system | integration |
actorName | Human-readable actor name |
personId | GDPR-erasable link to data subject |
objectType | The type of resource affected (e.g. workflow, secret) |
objectId | ID of the affected resource |
objectName | Name of the affected resource |
risk | low | medium | high | critical |
runId | Associated run ID if applicable |
workflowId | Associated workflow ID |
workflowName | Associated workflow name |
environment | Workspace environment at event time |
ip | Actor IP address |
sessionId | Session ID for user events |
reason | Reason for authz denial (on authz.denied events) |
policyMatched | DLP policy that triggered (on policy.violation events) |
authMethod | Authentication method used |
connectionRef | Connection involved (on connection events) |
secretRef | Secret involved (on secret events) |
versionBefore | Previous value (on update events) |
versionAfter | New value (on update events) |
occurredAt | ISO 8601 timestamp |
extra | Arbitrary additional context |
TTL | platform database TTL — 7 years from event creation |
mac | HMAC-SHA256 digest over canonical event body |
Full event taxonomy
Runs (5 events)
run.started, run.canceled, run.completed, run.failed, run.deleted
Workflows (3 events)
workflow.edited, workflow.published, workflow.draft_saved
Secrets (4 events)
secret.created, secret.updated, secret.rotated, secret.accessed (HIGH)
Connections (2 events)
connection.updated, connection.accessed (HIGH)
Agents (5 events)
agent.updated, agent.memory_set, agent.memory_deleted, agent.listed, agent.read
Workspace (4 events)
workspace.updated, workspace.created, workspace.environment_updated, workspace.notification_updated
Members (4 events)
member.invited, member.updated, member.removed, member.erased (CRITICAL)
API keys (2 events)
api_key.created, api_key.revoked
Approvals (4 events)
approval.granted, approval.rejected, approval.reassigned, approval.sla_breach
Grants (2 events)
grant.approved, grant.denied
Auth (4 events)
user.login, user.workspace_switched, auth.failed, authz.denied (HIGH)
Bus (9 events)
bus.message_published, bus.message_delivered, bus.message_fanout, bus.message_delivery_failed, bus.cycle_blocked, bus.subscription_created, bus.subscription_updated, bus.subscription_deleted, bus.api_key_created, bus.api_key_revoked
Gateway and DLP (4 events)
gateway_policy.created, gateway_policy.updated, gateway_policy.deleted, policy.violation
Datastores (4 events)
datastore.created, datastore.updated, datastore.deleted, datastore.object_deleted
SOC2 enumeration events (4 events)
secret.listed, connection.listed, connection.read, api_key.listed
How it works
Querying audit events
# List recent events, filtered to high and critical risk
curl "https://api.provenanceone.ai/audit?risk=high,critical&limit=50" \
-H "x-api-key: YOUR_KEY"
# Get a specific event
curl "https://api.provenanceone.ai/audit/{eventId}" \
-H "x-api-key: YOUR_KEY"
The GET /audit endpoint accepts query parameters:
eventType— filter by event type (e.g.secret.accessed)actorId— filter by actorrisk— filter by risk level (comma-separated)from/to— date range (ISO 8601)workflowId— filter to a specific workflowrunId— filter to a specific run
Investigating a failed run
- Navigate to Runs and open the failed run.
- The run detail page links to Audit → Run trail. This view shows all audit events with
runIdmatching the run, in chronological order. - Alternatively, call
GET /audit?runId={runId}to retrieve the same events via API. - Look for
run.failedto see the failure reason, andauthz.deniedorpolicy.violationevents that may have caused the failure.
Audit digest setup
- Go to Settings → Audit Digest.
- Enable the digest and set the frequency (
daily,weekly,monthly). - Choose
dayOfWeek(weekly),dayOfMonth(monthly), andhourUTCfor delivery time. - Set
includeRiskLevelsto filter which risk levels are included in the digest. - Add recipient email addresses.
- Click Save. The digest is sent via SES on the configured schedule.
Configuration options
Audit digest fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| enabled | boolean | Yes | false | Whether the digest is active |
| frequency | enum | Yes | — | daily | weekly | monthly |
| dayOfWeek | integer | Weekly only | — | 0 (Sunday) to 6 (Saturday) |
| dayOfMonth | integer | Monthly only | — | 1 to 28 |
| hourUTC | integer | Yes | — | Hour (0–23) for delivery |
| includeRiskLevels | array | Yes | — | Subset of low, medium, high, critical |
| recipients | array | Yes | — | Email addresses to receive the digest |
Examples
Retrieve all secret.accessed events in the last 30 days
curl "https://api.provenanceone.ai/audit?eventType=secret.accessed&from=2026-04-01T00:00:00Z" \
-H "x-api-key: YOUR_KEY"
Check who approved a specific approval
curl "https://api.provenanceone.ai/audit?eventType=approval.granted&runId={runId}" \
-H "x-api-key: YOUR_KEY"
Verify event integrity using the MAC field
Each event response includes a mac field. To verify:
- Retrieve the event:
GET /audit/{eventId} - Recompute the HMAC-SHA256 over the canonical event body using the platform key management key
- Compare the computed value to the stored
macfield
A mismatch indicates the event record has been altered.
Common mistakes
- Filtering audit logs to
lowrisk only. Most security-relevant events arehighorcritical. If your audit digest only includeslowrisk events, you will miss secret access, authz denials, and member erasures. - Not monitoring
bus.cycle_blockedevents. These indicate routing loops in the Bus configuration that are silently dropping messages. - Assuming
personIderasure deletes event data. GDPR erasure replaces thepersonIdfield but does not delete the audit event. The action history is preserved for compliance; only the data subject identifier is removed. - Querying with too broad a date range. Seven years of events is a large dataset. Always filter by
eventType,risk, orworkflowIdto narrow queries.
Troubleshooting
GET /audit returns 403 — the API key or JWT does not have the audit:read scope. Update the API key's scopes in Settings, or use an admin or editor JWT.
Audit digest emails are not arriving — check that the SES sender domain is verified for your workspace region and that recipients are not on the SES suppression list. Verify the digest is enabled and the hourUTC has passed.
Cannot find audit events for a specific run — ensure you are querying with the correct runId. Run IDs are case-sensitive. Also confirm the run actually started (a run that was rejected before starting will only have a run.failed event with no steps).
mac field is missing on older events — events created before MAC signing was introduced may not have a mac field. This is expected for legacy events; all new events include the MAC.
Security and permissions
| Operation | admin | editor | viewer |
|---|---|---|---|
| Read audit events | Yes | Yes | No |
| Configure audit digest | Yes | No | No |
Export via API (audit:read scope) | Yes | Yes | No |
Audit events cannot be deleted, modified, or filtered out by any role or API key. The immutability is enforced at the storage layer. The mac field provides cryptographic proof of integrity for each event.
Related pages
FAQ
How long are audit logs retained?▾
Audit events are retained for 7 years. Each event has a platform database TTL set 7 years from the event's `occurredAt` timestamp. Events older than 7 years are automatically expired from the primary store. There is no mechanism for early deletion; the 7-year floor exists for compliance purposes.
Can audit logs be tampered with?▾
No. Every audit event includes a `mac` field: a HMAC-SHA256 digest computed over the canonical event body at write time. The MAC is stored with the event and is recomputable using the platform key management key. Any alteration to the event body would produce a MAC mismatch. This provides cryptographic evidence of integrity for each event record.
What is the MAC field?▾
The `mac` field is a HMAC-SHA256 message authentication code computed over the canonical serialisation of the audit event body. It is stored alongside the event and serves as tamper evidence. If an audit event were altered after being written, recomputing the MAC would yield a different value than the stored `mac`. This is the primary SOC2 B2 control for audit log immutability.
How do I investigate a failed run?▾
Open the run in the Runs page and look at the step-level error. From there, navigate to the run's audit trail (linked from the run detail page, or via `GET /audit?runId={runId}`). Look for `run.failed` for the failure reason, `authz.denied` if an authorisation check blocked a step, and `policy.violation` if a gateway DLP rule was triggered. The audit trail shows the full sequence of events in the order they occurred.
Can I export audit logs?▾
Yes. Use `GET /audit` with the `audit:read` scope to retrieve events via the API. You can filter by event type, actor, risk level, and date range. Paginate through results using the response cursor. For scheduled summaries, configure the audit digest to send email reports to your compliance or security team. There is no one-click bulk export UI, but the API provides full programmatic access.