Skip to content

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

ActionTriggered when
assignment_createdThe user finishes the new-assignment wizard.
user_assignment_createdThe user opens an assignment workspace for the first time.
assignment_input_createdThe 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_deletedThe user removes a single submission. The payload includes studentName and the parent assignmentTitle.
user_assignment_uploadedA pending assignment transitions to uploaded because submissions arrived.
user_assignment_grading_startedThe user kicks off AI grading.
user_assignment_evaluatedThe grading worker finishes the last job and the assignment moves to evaluated. The payload includes submissionCount (the number of students graded).
user_assignment_calibration_startedThe user submits calibration samples and the calibration job is queued.
user_assignment_calibration_skippedThe user accepts AI grades without calibration.
user_assignment_calibratedThe calibration worker finishes re-scoring the cohort.
user_assignment_exportedThe user downloads the final CSV.
frontier_answer_generatedThe AI produces a benchmark answer during the new-assignment wizard.
settings_updatedThe 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=0

Returns the authenticated user's activity log, newest first.

ParameterDefaultDescription
limit50Number of rows to return. Capped at 200.
offset0Number 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.