Audit and compliance
All API access (and relevant failures) can be recorded in an audit log. Secret values are never stored in audit events; only metadata (e.g. path, action, actor, timestamp) is recorded.
Querying audit events
GET /v1/audit/events — Returns events for the caller’s organization. Query parameters may include:
resource_id— Filter by resource (e.g. secret path).actor_id— Filter by actor (user or agent UUID).action— Filter by action type.from,to— Time range (ISO 8601).limit,offset— Pagination.
Example request
curl "https://api.1claw.co/v1/audit/events?action=secret.read&limit=25" \
-H "Authorization: Bearer $TOKEN"
Example response
{
"events": [
{
"id": "a7e2c...",
"action": "secret.read",
"actor_id": "agent-uuid",
"actor_type": "agent",
"resource_type": "secret",
"resource_id": "secret-uuid",
"org_id": "org-uuid",
"details": { "vault_id": "vault-uuid", "path": "keys/eth-signer" },
"ip_address": "203.0.113.50",
"created_at": "2026-02-27T14:00:00Z"
}
],
"count": 1
}
Each event includes id, org_id, actor_type, actor_id, action, resource_type, resource_id, details, ip_address, and created_at.
Typical events
secret.read,secret.write,secret.delete— Secret access (path and actor; no value).auth.success,auth.failure— Authentication attempts (no credentials logged).policy.create,policy.update,policy.delete— Access policy changes.agent.register,agent.rotate_key,agent.deactivate— Agent lifecycle events.transaction.submit— Transaction submitted via Intents API.transaction.simulate— Transaction simulation requested.
Tamper-evident hash chain
Every audit event includes a cryptographic hash chain that links it to the previous event:
prev_event_id— UUID of the immediately preceding event in the org's audit log.integrity_hash— HMAC-SHA256, keyed, overprev_hash,org_id,actor_type,actor_id,action,metadataandtimestamp.
Two things follow from metadata being inside the hash. A verifier must read the event's metadata exactly as stored, and a field cannot be edited after the fact without breaking the chain from that event onward. The hash is keyed rather than a plain digest, so the chain cannot be recomputed by someone who has the rows but not the key.
Not covered by the hash: resource_type, resource_id and request_id. They are recorded and returned, but altering them would not break the chain.
This makes the audit log append-only and tamper-evident: modifying or deleting any event breaks the hash chain for all subsequent entries. You can verify integrity by walking the chain from the latest event back to the first (prev_event_id = NULL).
Retention
Audit events are never deleted. There is no retention window and no expiry job. DELETE and UPDATE on audit_events are rewritten to no-ops by the no_delete_audit and no_update_audit rules and further blocked by the audit_events_immutable trigger, so immutability does not depend on the application behaving correctly, or on which database role it connects as.
The consequence worth planning for: anything written into an event's metadata is there permanently, because the hash covers metadata and editing it would invalidate every event after it. Personal identifiers are therefore stored as keyed one-way digests (email_digest rather than email) rather than in clear, and identifiers in events written before 2026-10-11 are masked on the way out by the query and export endpoints.
The from parameter on export defaults to 90 days ago. That is a default, not a limit: pass an earlier from to reach older events, up to 100,000 events per request.
Audit insert hardening
The audit log is protected at the database level, and it is worth being precise about which protections apply to every connection and which depend on configuration.
Applies to every connection, including the application's own:
DELETEandUPDATEare rewritten to no-ops by theno_delete_auditandno_update_auditrules, and blocked again by theaudit_events_immutabletrigger.TRUNCATEis refused by theaudit_events_no_truncatetrigger. This needed its own mechanism: rules only rewriteDELETEandUPDATE, the row trigger does not fire onTRUNCATE, and row-level security does not apply toTRUNCATEat all, so for a time a single statement would have emptied the table with nothing in the way.- Row-level security is enabled with no permissive policy, which denies the PostgREST
anonandauthenticatedroles everything regardless of the grants they hold. Their write grants have also been revoked, becauseTRUNCATEis the case row-level security would not have covered.
These are statement-level rules and triggers, so they apply to every connection including a superuser's. What they do not survive is a connection that deliberately removes them: the role the service connects as owns the table and could drop a trigger first. Running the service as a non-owning role is what would close that, and is the same change described below.
Depends on which role the application connects as:
- A restricted
vault_approle exists withINSERTrevoked, alongside aSECURITY DEFINERfunction (insert_audit_event) intended to be the only write path. Connecting asvault_appis what would prevent a compromised application connection from fabricating events. - As configured today the service connects as a role with
BYPASSRLS, so that particular protection is not currently in the path. Immutability of existing events (above) is unaffected: a compromised connection could append a false event but could not alter or delete a true one, and an appended event joins the keyed chain, so it cannot be back-dated into the middle of the history.
Combined with the hash chain above: the chain and the immutability rules detect and prevent tampering with recorded history, and moving the service onto vault_app is what closes the remaining gap at write time.
Best practices
Use the audit log for compliance reviews, incident response, and access analysis. Export or forward events to your SIEM or logging pipeline as needed.
SDK AuditSink plugin
The TypeScript SDK supports an AuditSink plugin interface for forwarding audit events to external systems (e.g. Splunk, Datadog, or a custom webhook). Register a sink when constructing the client and every event your SDK session produces will be mirrored to your target in real time. See the SDK overview for details on the plugin architecture.