Audit log
The audit log is an append-only record of security-relevant actions: logins, membership, configuration, credentials, links and billing. Each entry is chained to the previous one with a SHA-256 hash. A SIEM pulls it with an export token and a cursor.
Availability: grovs Cloud instances with an enterprise subscription, and every self-hosted Enterprise deployment. Other instances get 403. The open-source image has no audit endpoints.
Auth: an export token (aet_…). An instance admin creates it in Settings › Audit Logs › Export tokens › Create token. The token is shown once.
The audit log overview lists what is recorded. The SIEM integration guide covers the poll loop and how to verify the hash chain.
Find the instance ID
The log covers a whole grovs project, production and test together. Paths use the numeric {instance_id}, not an environment's project ID.
The dashboard does not show the instance ID yet. To find it:
- Open Settings › Audit Logs.
- Open your browser's network panel.
- Click Export tokens.
- Read the number in the request path
/api/v1/instances/INSTANCE_ID/audit_export_tokens.
A token used with another instance's ID gets 403.
/api/v1/instances/1395/audit_events
^^^^
instance ID (integer)The audit event object
idintegersequenceintegeroccurred_atstringactionstringoutcomeenumsuccess, failure or pending.actorobjecttype, id, email, and via, the credential channel (for example dashboard, password, sso:google_oauth2, api_key).targetobject{ "type": "instance_role", "id": 9, "email": "…" }.changesobjectbefore / after diff where one applies. Secrets appear as "[FILTERED]".ipstring | nulluser_agentstring | nullrequest_idstring | nullX-Request-Id, so use it for correlation, not as evidence.prev_hashstring | nullhash of the previous entry. null on the first one.hashstring{
"id": 42,
"sequence": 12,
"action": "instance.member_added",
"outcome": "success",
"actor": { "type": "user", "id": 3, "email": "[email protected]", "via": "dashboard" },
"target": { "type": "instance_role", "id": 9, "email": "[email protected]" },
"ip": "203.0.113.9",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) …",
"request_id": "b1e5c0d2-7a41-4f6e-9a0b-3c2d1e0f9a8b",
"prev_hash": "9f2c41e0…",
"occurred_at": "2026-10-22T09:12:04.123456Z",
"changes": { "after": { "role": "member" } },
"hash": "4a7d93b1…"
}List audit events
/api /v1 /instances /{instance_id} /audit_eventsReturns a page of audit events ordered by sequence. A SIEM starts at after=0 and sets after to next_after until events comes back empty.
Send an export token as Authorization: Bearer aet_….
instance_idintegerRequiredafterintegerOptionalsequence greater than this.beforeintegerOptionalsequence less than this. Use with order=desc to page backwards.limitintegerOptional100.orderenumOptionalasc (default, oldest first) or desc.event_actionstringOptionalafter alone.actor_emailstringOptionalfromstringOptionaltostringOptionalReturns schema_version, events (an array of audit event objects), next_after (the highest sequence on the page) and next_before (the lowest). Both are null on an empty page.
In production, each export token can make 60 requests a minute. Over that, the API answers 503.
Errors
curl -X GET "https://api.sqd.link/api/v1/instances/{instance_id}/audit_events?after=0&limit=1000" \
-H "Authorization: Bearer $GROVS_AUDIT_KEY"{
"schema_version": 1,
"events": [
{
"id": 42,
"sequence": 12,
"action": "instance.member_added",
"outcome": "success",
"actor": { "type": "user", "id": 3, "email": "[email protected]", "via": "dashboard" },
"target": { "type": "instance_role", "id": 9, "email": "[email protected]" },
"ip": "203.0.113.9",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) …",
"request_id": "b1e5c0d2-7a41-4f6e-9a0b-3c2d1e0f9a8b",
"prev_hash": "9f2c41e0…",
"occurred_at": "2026-10-22T09:12:04.123456Z",
"changes": { "after": { "role": "member" } },
"hash": "4a7d93b1…"
}
],
"next_after": 12,
"next_before": 12
}{ "error": "Audit log is not enabled for this instance" }Get the chain head
/api /v1 /instances /{instance_id} /audit_events /headReturns the latest sequence and hash without fetching entries. Compare it with your stored cursor to see how far behind a poller is, or with the last entry you ingested to detect a gap.
instance_idintegerRequiredReturns schema_version, sequence (0 when the log is empty) and hash (null when empty). Shares the 60 requests a minute per token limit.
Errors
curl -X GET "https://api.sqd.link/api/v1/instances/{instance_id}/audit_events/head" \
-H "Authorization: Bearer $GROVS_AUDIT_KEY"{ "schema_version": 1, "sequence": 4182, "hash": "4a7d93b1…" }