Skip to content
HRTEQDocs
Documentation API

DocsDocuments & compliance

Audit trail

What the append-only audit log records, the full list of canonical action names, how gate overrides are snapshotted, and the reporting that does not exist yet.

Every consequential action writes a row to AuditLog. The table is append-only: nothing in the product updates or deletes a row once written. It is the answer to “who verified this, and when” months after everyone has forgotten.

What a row holds#

ColumnContents
organizationIdThe tenant. Platform-level actions can override it.
actorUserId, actorTypeWho did it and on which surface. Null for system actions.
actionA canonical dotted name from the list below.
entityType, entityIdWhat it was done to — Application, Document, Applicant, and so on.
before, afterJSON snapshots. Dates are serialised as ISO strings and decimals as strings, so a stored row never depends on a live object.
reasonRequired for overrides and rejections. This is the field an inspection reads.
ip, userAgentRequest provenance.
createdAtWhen.
An audit failure never breaks the user’s action. Audit writes are deliberately non-fatal — if the write fails it is logged loudly to the server console and the operation still completes. Losing a log row is bad; refusing to verify a passport because logging hiccuped is worse.

Canonical action names#

Actions use fixed dotted names so filters and reports stay stable. Grouped by area:

Organization and users#

ActionWhen
org.createdA workspace is provisioned.
org.activatedMoved out of trial or suspension.
org.suspendedAccess withdrawn.
user.invitedReserved. Nothing emits this yet.
user.role_changedReserved. Nothing emits this yet.
user.deactivatedReserved. Nothing emits this yet.
auth.loginA successful sign-in.
auth.login_failedA rejected sign-in.

Applicants and applications#

ActionWhen
applicant.createdIntake. Written alongside privacy.consent_recorded.
applicant.updatedAny profile edit.
applicant.parse_appliedA résumé extraction was accepted into the profile.
application.createdAn applicant was endorsed to a position.
application.stage_advancedA normal stage move, with before and after stage codes.
application.stage_overriddenA move that pushed past a blocking requirement. Separate action so overrides are countable.
application.closedThe application reached a terminal state.
checklist.completed, checklist.reopenedA checklist item was ticked or unticked.

Interviews#

ActionWhen
interview.plan_createdWith title and participant count.
interview.plan_updatedPlan-level edits.
interview.startedWith the participant that became current.
interview.time_addedThe increment and the resulting duration.
interview.participant_updatedA result set outside the live runner.
interview.completedThe result and who became current next.

Documents#

ActionWhen
document.uploadedWith the document type code and filename.
document.verifiedWith who verified it.
document.rejectedWith the mandatory rejection reason in reason.

Finance#

ActionWhen
charge.postedA fee was charged to a worker or an employer.
payment.recordedA payment was received.
application.financial_clearedAccounts signed the financial clearance.
invoice.issuedAn employer invoice was issued.

Selection and messaging#

ActionWhen
endorsement.sentAn application was put in front of a principal.
endorsement.decidedThe principal’s decision was recorded.
endorsement.contact_revealedApplicant contact details became visible on selection. See Endorsements.
email.sentA transactional email was sent.
campaign.sentA marketing campaign was dispatched.
automation.firedAn automation rule ran. Also recorded in AutomationRun.

Privacy#

ActionWhen
privacy.consent_recordedData privacy consent captured. Written at applicant creation.
privacy.data_exportedA subject access export was produced.
privacy.data_deletedPersonal data was erased.

Stage history is a second, richer record#

Stage moves are recorded twice, deliberately. The audit row gives you the flat “who, what, when” alongside everything else. ApplicationStageHistory gives you the structured detail:

  • fromStageId and toStageId — the exact transition.
  • byUserId — who moved it.
  • wasOverride — whether a blocking requirement was pushed past.
  • overrideReason — the written justification, minimum ten characters.
  • unmetSnapshotthe full set of unmet blocking requirements as they were at that moment, each with its label, type, refCode and detail string.
  • note — an optional free-text note on any move.
The snapshot is the important part. Fixing the document afterwards does not rewrite the record. Months later you can still answer precisely what was missing when someone decided to proceed, which is the question an inspection actually asks.

Where you can see it today#

The dashboard surfaces the audit log in two places: a recent-activity feed of the last 18 rows with the action, entity and actor name, and a staff activity leaderboard derived from counting each officer’s audit rows.

There is no audit viewer and no audit export. The log is complete and queryable in the database, but the product gives you no way to browse, filter or export it — no date range, no action filter, no per-application history view, no CSV. The audit module is defined as read-only even for an Owner, and no endpoint serves it. If you need an inspection pack today, it comes out of the database directly. This is the largest gap between what the data model supports and what the UI exposes.

Until that lands, the two things worth doing are: keep override reasons genuinely descriptive, since they are the rows an inspector will read; and treat a rising application.stage_overridden count as a signal that your stage configuration needs fixing rather than as normal practice.

Was this page helpful?