Filters & Search
All supported filter dimensions, sorting, and pagination across audit and activity log endpoints
Filters & Search
Both audit_logs and activity_logs endpoints accept a strict, well-defined set of query-string filters. The UI surfaces a deliberately small subset; everything below is callable directly against the API.
Audit Log Filters
GET /v1/admin/audit/audit-logs?…
| Param | Type | Bound to column |
|---|---|---|
actor_id | UUID | actor_id |
actor_type | string (≤50 chars) | actor_type |
action | string (≤100 chars) | action |
resource_type | string (≤100 chars) | resource_type |
resource_id | string | resource_id |
module | string (≤100 chars) | module |
start_date | RFC3339 timestamp | created_at >= ? |
end_date | RFC3339 timestamp | created_at <= ? |
Source: audit-service/dto/audit_log.go::AuditLogFilter. All filters are AND-combined. Tenant scope is always applied automatically from the JWT — there is no way to query across tenants.
What the Admin UI Exposes
The audit-logs-table.tsx toolbar shows only Action, Resource Type, and Module as select dropdowns. Date range, actor ID, resource ID, and actor type need to be sent as query params directly.
Activity Log Filters
GET /v1/admin/audit/activity-logs?… and GET /v1/user/audit/activity-logs?…
| Param | Type | Bound to column |
|---|---|---|
user_id | UUID | user_id (admin endpoint only — user endpoint forces caller's ID) |
action | string (≤100 chars) | action |
module | string (≤100 chars) | module |
method | string (≤10 chars) | method |
status_code | int (100–599) | status_code |
start_date | RFC3339 timestamp | created_at >= ? |
end_date | RFC3339 timestamp | created_at <= ? |
Source: audit-service/dto/activity_log.go::ActivityLogFilter. status_code outside 100..599 is rejected at validation; the others are length-checked.
What the Admin UI Exposes
The activity-logs-table.tsx toolbar shows only Method and Module as select dropdowns. The user view (user-activity-logs.tsx) exposes no filter chips at all.
Sorting
Both endpoints accept the same pagination contract (base_dto.PaginationRequest):
| Param | Default | Notes |
|---|---|---|
sort_by | created_at | Any column name; if it doesn't already contain a ., the table alias is automatically prepended (al. for audit, acl. for activity) |
sort_dir | desc | asc or desc |
Because the alias is auto-prepended, you can pass sort_by=action and the SQL becomes ORDER BY al.action. If you need to sort by a joined column (currently none are exposed), pass it fully qualified.
Pagination
| Param | Default | Notes |
|---|---|---|
page | 1 | 1-indexed |
per_page | 50 | No hard cap is enforced server-side, but the UI sticks to 50 |
The response always includes a pagination block with total, page, per_page, and a has_next/has_previous pair.
Tenant Scoping
Tenant scoping is enforced by the service layer, not by user-supplied params:
GetAuditLogs(ctx, tenantID, ...)always addsWHERE al.tenant_id = ?.GetActivityLogs(ctx, tenantID, ...)does the same withacl.tenant_id.- The
tenantIDargument comes from the resolved tenant context middleware — never from the query string.
There is no API surface that lets a caller view another tenant's rows.
Common Recipes
| Goal | Query |
|---|---|
| Last 24 hours of admin deletes in the auth module | ?action=delete&module=auth&start_date=2026-04-20T00:00:00Z |
| Everything user X did this week | ?actor_id=<uuid>&start_date=2026-04-14T00:00:00Z |
| All 5xx activity rows for triage | ?status_code=500 (loop status codes if you need a band) |
| All deletes on a specific course | ?action=delete&resource_type=course&resource_id=<uuid> |
| One admin's mutations on courses, newest first | ?actor_id=<uuid>&resource_type=course&sort_by=created_at&sort_dir=desc |
Limitations
- Single value per filter. Every filter accepts exactly one value. To OR across actions or modules, page through twice and merge client-side.
- No full-text search. There is no
q/searchparameter. Filtering happens on indexed columns only. - Status code is exact-match. There's no
status_code_min/status_code_max— to scan all 5xx, query each code or pre-aggregate. description,metadata,before_value,after_valueare not searchable. Indexes exist on the dimensions in the table above; JSONB content is opaque to the API.
For free-form historical analysis beyond these dimensions, use the Export endpoint and process the JSON offline.

