How-To: Querying Audit Logs
Brokkr records significant operations to an append-only audit trail. This guide shows how to query it to investigate events, track changes, and monitor security. Entries are never edited or deleted except by the retention job — but writes are asynchronous and best-effort, so read Delivery Guarantees before treating a query result as a complete record.
Audit Log API
Section titled “Audit Log API”All audit log queries go through the admin API:
GET /api/v1/admin/audit-logsAuth: Admin rights. Both an admin PAK and the broker’s zero-config read-only console token satisfy that gate; agent and generator PAKs are rejected. Anyone who can reach the broker’s HTTP port can load the console and therefore read the audit trail, IP addresses and user agents included — restrict access to that port accordingly.
Basic Query
Section titled “Basic Query”List the most recent audit events:
curl -s "http://localhost:3000/api/v1/admin/audit-logs" \ -H "Authorization: <admin-pak>" | jq .Default: returns the 100 most recent entries, ordered by timestamp (newest first). The response is a JSON object with a logs array, plus total, count, limit, and offset fields for pagination.
Two things about entry shape will bite scripts that assume otherwise:
- Every entry carries an
actor_namealongsideactor_id, resolved when you read it — the entity’s current name, ornullif the ID no longer resolves. Prefer it over joining against the agents/generators endpoints yourself. ip_addressanduser_agentare omitted from the JSON entirely when they were not recorded, rather than serialized asnull. Supply a fallback injq(.ip_address // "unknown") so those entries don’t silently collapse into nulls.
Filtering
Section titled “Filtering”By Actor Type
Section titled “By Actor Type”See all actions performed by agents:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?actor_type=agent" \ -H "Authorization: <admin-pak>" | jq '.logs[] | {action, resource_type, timestamp}'Valid actor types: admin, agent, generator, system
By Action
Section titled “By Action”Find all agent creation events:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=agent.created" \ -H "Authorization: <admin-pak>" | jq .Actions support wildcard prefix matching. To see all agent-related events:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=agent.*" \ -H "Authorization: <admin-pak>" | jq .By Resource
Section titled “By Resource”Track all changes to a specific agent:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?resource_type=agent&resource_id=${AGENT_ID}" \ -H "Authorization: <admin-pak>" | jq .By Time Range
Section titled “By Time Range”Query events within a specific window:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?from=2025-01-15T00:00:00Z&to=2025-01-16T00:00:00Z" \ -H "Authorization: <admin-pak>" | jq .By Actor ID
Section titled “By Actor ID”See everything a specific generator has done:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?actor_type=generator&actor_id=${GEN_ID}" \ -H "Authorization: <admin-pak>" | jq .Pagination
Section titled “Pagination”Use limit and offset for large result sets:
# First pagecurl -s "http://localhost:3000/api/v1/admin/audit-logs?limit=50&offset=0" \ -H "Authorization: <admin-pak>" | jq .
# Second pagecurl -s "http://localhost:3000/api/v1/admin/audit-logs?limit=50&offset=50" \ -H "Authorization: <admin-pak>" | jq .Maximum limit is 1000.
Common Investigation Patterns
Section titled “Common Investigation Patterns”Who Changed This Agent?
Section titled “Who Changed This Agent?”curl -s "http://localhost:3000/api/v1/admin/audit-logs?resource_type=agent&resource_id=${AGENT_ID}" \ -H "Authorization: <admin-pak>" \ | jq '.logs[] | {actor_type, actor_name, actor_id, action, timestamp, details}'Failed Authentication Attempts
Section titled “Failed Authentication Attempts”curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=auth.failed" \ -H "Authorization: <admin-pak>" \ | jq '.logs[] | {timestamp, ip_address: (.ip_address // "unrecorded"), user_agent: (.user_agent // "unrecorded"), details}'Security Monitoring: Failed Auth by Source IP
Section titled “Security Monitoring: Failed Auth by Source IP”Aggregate failed attempts over the last 24 hours to spot attack patterns:
FROM=$(date -u -v-24H +%Y-%m-%dT%H:%M:%SZ) # GNU date: date -u -d '24 hours ago' ...curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=auth.failed&from=${FROM}&limit=1000" \ -H "Authorization: <admin-pak>" \ | jq '[.logs[].ip_address // "unrecorded"] | group_by(.) | map({ip: .[0], count: length}) | sort_by(-.count)'Compliance Export
Section titled “Compliance Export”Export all actions for a reporting period to a file:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?from=2025-01-01T00:00:00Z&to=2025-02-01T00:00:00Z&limit=1000" \ -H "Authorization: <admin-pak>" > audit-january-2025.jsonFor periods with more than 1000 entries, page through with offset and concatenate.
Do not present an export as a guaranteed-complete record without a caveat. Audit writes are queued in memory and flushed to the database in batches, so a batch whose insert fails is dropped rather than retried, and anything still queued when the broker stops abruptly never lands. Both cases show up in the broker’s error logs, not in the export. If your compliance process needs a completeness guarantee, reconcile the export against broker logs for the same window.
Recent PAK Rotations
Section titled “Recent PAK Rotations”curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=pak.rotated" \ -H "Authorization: <admin-pak>" \ | jq '.logs[] | {actor_type, resource_type, resource_id, timestamp}'All Admin Actions Today
Section titled “All Admin Actions Today”TODAY=$(date -u +%Y-%m-%dT00:00:00Z)curl -s "http://localhost:3000/api/v1/admin/audit-logs?actor_type=admin&from=${TODAY}" \ -H "Authorization: <admin-pak>" | jq .Webhook Configuration Changes
Section titled “Webhook Configuration Changes”curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=webhook.*" \ -H "Authorization: <admin-pak>" \ | jq '.logs[] | {action, resource_id, timestamp, details}'Stack Deletion History
Section titled “Stack Deletion History”curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=stack.deleted" \ -H "Authorization: <admin-pak>" \ | jq '.logs[] | {actor_type, actor_name, resource_id, timestamp}'Who Granted This Agent Its Tenant Scopes?
Section titled “Who Granted This Agent Its Tenant Scopes?”Registration is the consent boundary that decides which generators’ stacks reach an agent, so its grants and revocations are audited:
curl -s "http://localhost:3000/api/v1/admin/audit-logs?action=agent.registered&resource_id=${AGENT_ID}" \ -H "Authorization: <admin-pak>" \ | jq '.logs[] | {actor_type, actor_name, timestamp, details}'Swap in action=agent.deregistered to see revocations — the destructive direction, since deregistering prunes the agent’s Kubernetes resources for that generator’s stacks.
Audit Event Types
Section titled “Audit Event Types”Actions follow the pattern resource.verb (e.g., agent.created, pak.rotated, auth.failed). You can use wildcard queries like action=agent.* to match all events for a resource type.
For the complete list of all audit event types, see the Audit Logs Reference.
Retention
Section titled “Retention”Audit logs are retained for 90 days by default (broker.audit_log_retention_days). The broker runs a daily cleanup task to purge older entries.
To change retention:
BROKKR__BROKER__AUDIT_LOG_RETENTION_DAYS=365Related Documentation
Section titled “Related Documentation”- Audit Logs Reference — schema and data model details
- Security Model — authentication and authorization
- Managing PAKs — PAK rotation and security