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:

ParameterMeaning
qPrefix search over the summary, actor name, target name and action words - every word must match.
categoryOne of interrupt, session, member, role, team, api_key, webhook, classification, settings, billing, organisation.
actionOne action, e.g. interrupt.decided or api_key.created.
actorTypemember, api_key, system or argus.
actorIdA member's user id, or an API key's id.
targetType, targetIdWhat the entry was done to - interrupt, session, user, invitation, role, team, api_key, webhook, classification or organisation, and its id.
from, toISO 8601 timestamps; from is inclusive, to exclusive.
cursorThe previous response's nextCursor.
limitEntries 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"
}
FieldMeaning
action, categoryThe entry's kind, as <category>.<verb>, and its category. New actions may appear over time; treat unknown values as opaque.
actorType, actorId, actorNameWho 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, targetNameWhat it was done to, when there is one.
summaryThe sentence the dashboard shows.
metadataThe structured detail behind the summary; varies by action and may be null.
ipAddressThe 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

StatusMeaning
401The key is missing, invalid, disabled or expired - see API keys.
402PLAN_REQUIRED: the organisation is not on Pro.
400A malformed cursor, timestamp or filter value.

On this page