Appearance
Activity log and audit trail
Coequal records every meaningful user action against the activity_logs table and exposes it through /api/activity-logs and the in-app Activity log page. The log is per-user — each teacher sees their own actions — and gives you an immutable audit trail you can use for incident review or compliance reporting.
What we record
| Action | Triggered when |
|---|---|
assignment_created | The user finishes the new-assignment wizard. |
user_assignment_created | The user opens an assignment workspace for the first time. |
assignment_input_created | The user adds one or more student submissions. The payload contains a comma-joined list of student names (studentName) and a submissionCount — a single batch creates a single log entry, not one per student. |
assignment_input_deleted | The user removes a single submission. The payload includes studentName and the parent assignmentTitle. |
user_assignment_uploaded | A pending assignment transitions to uploaded because submissions arrived. |
user_assignment_grading_started | The user kicks off AI grading. |
user_assignment_evaluated | The grading worker finishes the last job and the assignment moves to evaluated. The payload includes submissionCount (the number of students graded). |
user_assignment_calibration_started | The user submits calibration samples and the calibration job is queued. |
user_assignment_calibration_skipped | The user accepts AI grades without calibration. |
user_assignment_calibrated | The calibration worker finishes re-scoring the cohort. |
user_assignment_exported | The user downloads the final CSV. |
frontier_answer_generated | The AI produces a benchmark answer during the new-assignment wizard. |
settings_updated | The user saves changes on the Settings page. |
Every entry stores id, user_id, action, an optional JSON payload, and created_at. The payloads always carry enough context (assignment title, submission counts, related ids) to render a meaningful row without further lookups.
API
GET /api/activity-logs?limit=50&offset=0Returns the authenticated user's activity log, newest first.
| Parameter | Default | Description |
|---|---|---|
limit | 50 | Number of rows to return. Capped at 200. |
offset | 0 | Number of rows to skip. |
Response shape:
json
{
"items": [
{
"id": "0e9c…",
"action": "user_assignment_evaluated",
"createdAt": 1746273600,
"payload": {
"userAssignmentId": "…",
"assignmentTitle": "Macroeconomics Midterm",
"submissionCount": 124
}
}
],
"total": 384,
"limit": 50,
"offset": 0
}The dashboard fetches the latest 10 entries with ?limit=10. The dedicated Activity log page (/activity in the SPA) paginates 50 at a time.
Compliance
The log is immutable from the application — there is no edit or delete endpoint. If you need to purge a user's data for GDPR/CCPA reasons, do it directly against the database and document the operation. We recommend keeping at least 12 months of activity available; do not truncate the table on a regular schedule.
Data retention
activity_logs has no automatic cleanup. The table is small (one row per user action, never high-volume) so this is rarely an operational concern. If you do want to expire old rows, schedule a periodic DELETE FROM activity_logs WHERE created_at < now() - interval '2 years' from your database tooling — Coequal will not touch existing rows.