Outcome
By the end of this lesson, you will be able to construct an audit-log query that answers a specific forensic or compliance question, sort and paginate efficiently for large result sets, and export the filtered view for SIEM or audit consumption.
| Prerequisites | M4.3.L1 (what’s logged) · M4.3.L2 (body capture) |
| Time | 9 minutes |
1. Concept
The filters exist so that the usual questions can be answered in one query rather than by reading.
That is the difference between paging through 4,000 entries and looking at the 7 that matter.
Filter dimensions
DIMENSION VALUES EXAMPLE──────────────────────────────────────────────────────────────────Date range start date and end date (whole days) last 7 daysMethod POST / PUT / PATCH / DELETE (GET DELETE appears only on MCP read rows)User email, partial match jane@example.comResource path, partial match schedulesStatus success (2xx) or failed (400+) failedSearch text in the path or request body "sch_xyz789"Source All sources, MCP, or Platform MCP (Platform = everything except MCP)Filters compose. “POST + resource contains schedules + status = success + user = jane@example.com” narrows the scope precisely. There is no filter for exact status codes, response-body text, PAT or trace ID, and the date range works in whole days, not hours.
Canonical queries
These appear over and over in real customer ops:
QUESTION FILTER COMBINATION──────────────────────────────────────────────────────────────────"What did user X do last week?" user = x@example.com, last 7 days"All schedule modifications" resource contains "schedules""All failed API calls yesterday" status = failed, date = yesterday"Who deleted resource Y?" method = DELETE, resource contains Y's id"All RBAC changes this month" resource contains "roles", then "assignments", then "teams" (one filter at a time)"Cloud-account changes" resource contains "cloud-accounts" (bodies are not stored here)"What did our assistants change?" Source = MCP"Bulk action by user X" user = X, then count entries per minute in the exportThe last one is a frequency query, not a content query. The page does not filter by frequency, so export and post-process. Sign-in attempts are not in the audit log at all (L1), so there is no “failed logins” query here.
Sorting
DEFAULT timestamp descending (newest first)ALTERNATIVE timestamp ascending (oldest first)Time is the only sort. For investigations, oldest first within a window is often more useful: it shows the sequence of events as they happened.
Pagination
DEFAULT page size: 20 rowsMAXIMUM: 100 rowsNAVIGATION: page number and page size (offset paging)NO infinite scroll: intentional: bookmarkable pagesThe “no infinite scroll” choice is a small UX decision with a useful side effect: a filtered audit-log page can be linked, shared in a ticket, or pasted into an incident channel and the recipient sees the same view.
Export
Once the filter is right, export the result for offline analysis, ticket attachments, or compliance evidence collection.
FROM THE UI: Click "Export" on the filtered view Format: CSV only Scope: matches the current filter Delivery: by email, as a download link (L5 covers the details) Credential bodies: still absent; nothing is added back
FROM THE API: curl -H "Authorization: Bearer ${PAT}" \ "https://api.zop.dev/orgs/{orgID}/audit-logs? method=POST&resource=schedules&startDate=2026-05-13&page=1&size=100"
Response: JSON, one page at a time Pagination: increase page until a page comes back shortThe API is the one to use if you want your own copy in a SIEM. ZopNight does not ship a SIEM integration, so this is something you build.
Building your own SIEM feed
For long-term analysis or organisation-wide log aggregation, some customers copy the audit log into their SIEM themselves:
PATTERN YOU BUILD────────────────────────────────────────────────────────────Cadence: Daily job (the date filter is day-grained)Filter: startDate = endDate = yesterdayPaging: page through 100 rows at a timeDestination: Splunk / Datadog / Sumo / S3, your choiceDeduplication: your own key, e.g. timestamp + user + method + path (there is no trace ID)Authentication: PAT for a user holding audit-log:viewZopNight keeps every row indefinitely itself, so the SIEM copy is for your convenience, not a retention requirement.
Evidence for audits
There are no pre-formatted compliance reports. You build the evidence with the same filters and export it:
EVIDENCE YOU NEED FILTER TO RUN────────────────────────────────────────────────────────────Quarterly RBAC changes resource contains "roles", then "assignments", date = the quarterCloud-account changes resource contains "cloud-accounts"Schedule changes resource contains "schedules"Assistant (MCP) activity Source = MCPFailed mutations status = failedRun these directly, export the CSV, and file it with the audit evidence.
2. Demo
Three real workflows where filtering is the bottleneck for response time:
DAILY USE: FinOps analyst, 5 minutes────────────────────────────────────────────────────────T+0 Open Audit LogsT+10 s Filter: resource contains "schedules", date = todayT+30 s 47 entries returnedT+45 s Skim for anomalies: 2 unusual ones identifiedT+1 min Click each for body detail; confirm or escalateT+2 min Mark reviewed; close loop
WEEKLY USE: Security reviewer, 10 minutes────────────────────────────────────────────────────────T+0 Open Audit LogsT+10 s Filters from the runbook: "roles", "assignments", "teams"T+15 s 23 entries: role changes, user invites, team assignmentsT+2 min Walk through each: ensure each was authorizedT+5 min Cross-reference 1 anomaly against ticketing systemT+8 min Mark cleared; capture export for the quarterly report
INVESTIGATION: On-call, 15 minutes────────────────────────────────────────────────────────T+0 Incident report: "schedule sch_xyz789 misfired"T+30 s Search: "sch_xyz789", oldest firstT+1 min 4 entries chronologically: creation and 3 editsT+3 min Identify the change that broke it (the cron-typo from L2)T+5 min Revert; document; closeEach workflow follows the same pattern: filter narrows scope, body capture provides detail, export captures the evidence.
3. Hands-on (6 min)
Run three queries on your audit log:
QUERY 1: "Mutations in the last 24 hours, by your user" Filter: user = me, date = today Result count: _____ Top action types: __________
QUERY 2: "All deletions in the last 7 days" Filter: method = DELETE, last 7 days Result count: _____ Surprise count (unauthorized or mysterious)? _____
QUERY 3: "Failed mutations this month" Filter: status = failed, this month Result count: _____ Most common failure path: __________
OPTIONAL: write your first runbook filter: Name: __________________ Filter: __________________ Reason you'd run this regularly: __________________If query 2 surfaces unexpected deletions, investigate before closing the lesson.
Do it through MCP. The same task you just did in the console, asked in one sentence.
BEFORE A ZopNight account with one cloud connected. A month of activity in the org, otherwise the export is empty.ASK "Export last month's audit log of DELETE actions on schedules."CHECK the export covers the window you meant. Async exports have their own guarantees, which is L5.Tools behind it: list_audit_logs (read, Govern), export_audit_logs (write, tier 1, metadata only). Connection details are in the MCP integration docs.
4. Knowledge check
Q1
Server-side pagination is used because:
A. It is purely a user-experience preference in the UI
B. Audit logs accumulate to months or years of mutations
C. It is a performance optimization for screen rendering
D. Network speed between the client and the API server
Show answer
Correct: B. Rows are kept indefinitely, so the table only grows. Client-side pagination would mean fetching every entry; server-side returns one page (20 rows by default, up to 100). Scalability is the driver.
Q2
The audit log’s text search matches:
A. Text in the request path or in the request body
B. Text in the response body, but never in the path
C. Request headers and the query string of each call
D. Any field at all, including the user’s IP address
Show answer
Correct: A. Search looks in the path and the request body. Response bodies are not searched, and headers, query strings and IP addresses are not stored at all.
Q3
Export of the audit log returns:
A. The complete audit log, entirely unfiltered by anything
B. Everything matching the current filter, as a CSV by email
C. Only the page that is currently visible, which is 20 rows
D. A random sample drawn from across the matching rows
Show answer
Correct: B. The export is a CSV of every row matching the filter, delivered by email as a download link. Bodies that were dropped for credential areas stay absent. Confirm the filter matches what the auditor needs before clicking Export.
5. Apply
The Audit Logs page at Activity → Audit Logs supports all filters and the CSV export. For programmatic access, the Audit API (GET /orgs/{orgID}/audit-logs) takes the same filters with page and size parameters.
For org-wide log aggregation, build a daily job that pulls the previous day through the API with a PAT and forwards it to your SIEM.
Related lessons
- L1: What gets logged
- L2: Body capture trade-offs
- L4: Compliance posture (next)
- T4.M4.1.L6: Frontend gating with usePermission