Export
Download audit logs as a JSON file with date-range and filter scoping
Export
The audit module exposes a single export endpoint — for audit logs only. It returns a JSON file scoped to the caller's tenant, optionally narrowed by date range, action, resource type, and module.
Endpoint
GET /v1/admin/audit/audit-logs/export| Requirement | Value |
|---|---|
| Auth | JWT |
| Permission | audit.read |
| Tenant scoping | Always — from the JWT, never from the body |
Request Body
Despite being a GET, the handler binds an ExportAuditLogsRequest (audit-service/dto/audit_log.go):
type ExportAuditLogsRequest struct {
StartDate time.Time `json:"start_date"` // required
EndDate time.Time `json:"end_date"` // required
Format string `json:"format"` // required, "csv" or "json"
Actions []string `json:"actions"`
ActorIDs []uuid.UUID `json:"actor_ids"`
ResourceTypes []string `json:"resource_types"`
Modules []string `json:"modules"`
}Validation:
start_date,end_date,formatare required.formatmust be"csv"or"json"— but see the next section.
Output Format
Today: JSON only. Although
formataccepts"csv", the service hard-codesjson.MarshalIndentand emits a.jsonfilename. Passingformat=csvwill be accepted by validation, but the response body will still be JSON. Treat the parameter as forward-looking; CSV emission is not yet implemented.
The response body is a pretty-printed JSON array of audit log objects (the same AuditLogResponse shape returned by the list endpoint, sans pagination wrapper).
The filename is set on Content-Disposition:
audit_logs_2026-04-21_15-04-05.jsonFilter Behaviour
This is the most surprising part of the export endpoint — read carefully:
Array filters are truncated to the first element.
Actions,ResourceTypes, andModulesare sent as arrays in the request body. The service uses onlyreq.Actions[0],req.ResourceTypes[0],req.Modules[0]when building the underlying filter.ActorIDsis ignored entirely.
Source: audit-service/services/audit_log_service.go::ExportAuditLogs:
if len(req.Actions) > 0 {
filter.Action = req.Actions[0]
}
if len(req.ResourceTypes) > 0 {
filter.ResourceType = req.ResourceTypes[0]
}
if len(req.Modules) > 0 {
filter.Module = req.Modules[0]
}Practically, this means:
- Sending
actions: ["create", "delete"]will export onlycreaterows. - Sending
actor_ids: [...]will be silently dropped — the export will include all actors. - Date range and tenant scoping are honoured normally.
Frontend Wiring
The admin Audit Logs page exposes a bulk Export toolbar action through useExportAction (frontend/src/lib/audit/actions.ts::exportAuditLogs). It always hits the same endpoint and downloads the resulting Blob as a JSON file.
The Activity Logs page also surfaces an Export button in its toolbar — but there is no activity-log export endpoint on the backend. The button currently routes through the same generic export plumbing as audit logs; treat it as a known UI gap until an activity-logs/export endpoint exists.
Pagination
The export does not paginate — it serializes whatever the underlying paginated query returns for the default page size of 50. To export a longer date range you currently need to:
- Call the list endpoint repeatedly with explicit
page/per_page, or - Tighten
start_date/end_dateuntil each export fits in 50 rows, or - Run an offline query against the database directly.
This is a known sharp edge. If your retention window is small (default 90 days for activity, 365 days for audit), 50 rows often won't cover much.
Recommended Use
Until the export endpoint paginates, treat it as a convenience for short-window snapshots:
- "Give me the last hour of deletes" — works well.
- "Give me everything from the last quarter for compliance review" — use the API list endpoint with explicit pagination instead, or query Postgres directly with
audit.read-equivalent privileges.
Source
- DTO:
audit-service/dto/audit_log.go - Handler:
audit-service/api/handlers/audit_log_handler.go::ExportAuditLogs - Service:
audit-service/services/audit_log_service.go::ExportAuditLogs - Frontend client:
frontend/src/lib/audit/actions.ts::exportAuditLogs

