Audit log API
Read the organisation's audit log with an API key, page by page.
GET /api/audit-log returns the same entries the dashboard's audit log shows, newest first, for the key's organisation. It is read-only and needs no permission beyond a valid API key - but the organisation must be on Pro; a free organisation gets 402 PLAN_REQUIRED.
Request
Authenticate with the x-api-key header. Every parameter is optional:
| Parameter | Meaning |
|---|---|
q | Prefix search over the summary, actor name, target name and action words - every word must match. |
category | One of interrupt, session, member, role, team, api_key, webhook, classification, settings, billing, organisation. |
action | One action, e.g. interrupt.decided or api_key.created. |
actorType | member, api_key, system or argus. |
actorId | A member's user id, or an API key's id. |
targetType, targetId | What the entry was done to - interrupt, session, user, invitation, role, team, api_key, webhook, classification or organisation, and its id. |
from, to | ISO 8601 timestamps; from is inclusive, to exclusive. |
cursor | The previous response's nextCursor. |
limit | Entries per page: 1-200, default 50. |
curl "https://api.vigilator.dev/api/audit-log?category=interrupt&from=2026-09-01T00:00:00Z&limit=100" \
-H "x-api-key: vgl_..."Response
{
"entries": [
{
"id": "8f14e45f-ceea-4672-8657-a1b2c3d4e5f6",
"createdAt": "2026-09-09T09:41:00.000Z",
"action": "interrupt.decided",
"category": "interrupt",
"actorType": "member",
"actorId": "usr_01J...",
"actorName": "Ada Lovelace",
"targetType": "interrupt",
"targetId": "3c9d2b7a-1f0e-4b6a-9d21-abcdefabcdef",
"targetName": "Send onboarding email",
"summary": "Ada Lovelace decided approve on \"send_email\" (Send onboarding email)",
"metadata": { "actionName": "send_email", "decision": "approve", "becameAnswered": true },
"ipAddress": "203.0.113.7"
},
{
"id": "0b7c2a90-5d3e-4f6a-8b1c-2d3e4f5a6b7c",
"createdAt": "2026-09-09T09:38:00.000Z",
"action": "interrupt.created",
"category": "interrupt",
"actorType": "api_key",
"actorId": "key_01J...",
"actorName": "Production agent",
"targetType": "interrupt",
"targetId": "3c9d2b7a-1f0e-4b6a-9d21-abcdefabcdef",
"targetName": "Send onboarding email",
"summary": "API key \"Production agent\" opened interrupt \"Send onboarding email\"",
"metadata": { "externalId": "run_42", "actionRequests": ["send_email"] },
"ipAddress": "198.51.100.2"
}
],
"nextCursor": "MTc1NzQwNjY4MDAwMDowYjdjMmE5MC01ZDNlLTRmNmEtOGIxYy0yZDNlNGY1YTZiN2M"
}| Field | Meaning |
|---|---|
action, category | The entry's kind, as <category>.<verb>, and its category. New actions may appear over time; treat unknown values as opaque. |
actorType, actorId, actorName | Who did it. system is Vigilator itself (no id); argus is the auto-classifier. Names are kept as they were at the time, so a renamed key or a removed member still reads correctly. |
targetType, targetId, targetName | What it was done to, when there is one. |
summary | The sentence the dashboard shows. |
metadata | The structured detail behind the summary; varies by action and may be null. |
ipAddress | The address the request came from, when it came from a request. |
Paging
Cursors are opaque strings. Repeat the request with cursor set to the last response's nextCursor, keeping every other parameter identical, until nextCursor is null. Entries written after your first request are not folded into an in-flight walk - start again from the top to pick them up.
import requests
def audit_log(key: str, **params):
cursor = None
while True:
response = requests.get(
"https://api.vigilator.dev/api/audit-log",
headers={"x-api-key": key},
params={**params, "cursor": cursor, "limit": 200},
)
response.raise_for_status()
page = response.json()
yield from page["entries"]
cursor = page["nextCursor"]
if not cursor:
break
for entry in audit_log("vgl_...", category="member"):
print(entry["createdAt"], entry["summary"])Keeping your own copy
To mirror the log into a SIEM or a warehouse, poll with from set to the newest createdAt you have already stored and walk the pages that come back; entries are never edited or removed, so an id you have seen is final. For a one-off extract, the dashboard's Export CSV covers the most recent 5,000 entries for a filter.
Errors
| Status | Meaning |
|---|---|
401 | The key is missing, invalid, disabled or expired - see API keys. |
402 | PLAN_REQUIRED: the organisation is not on Pro. |
400 | A malformed cursor, timestamp or filter value. |