Skoolsewa - School Management System Docs
Developer ResourcesAttendance

Attendance API Reference

Complete API contracts for Attendance, including routes, auth, DTOs, responses, errors, examples, and integration notes.

Attendance - API Reference

Audience: Frontend engineers, mobile engineers, backend engineers, QA, and API consumers. Scope: Admin (student and staff registers, overview, history, settings) and mobile/consumer (teacher, student, guardian portal) APIs owned by Attendance.

1. Documentation Evidence

AreaFiles InspectedWhat Was Verified
Controllersapps/api/src/modules/attendance/admin/*.controller.ts, staff/*.controller.ts, settings/attendance-settings.controller.ts, portal/{teacher,student,guardian}/*.controller.tsRoutes, methods, guards, decorators, status codes, Swagger tags.
DTOsapps/api/src/modules/attendance/admin/dto/*.ts, staff/dto/*.ts, settings/dto/*.ts, portal/{teacher,student,guardian}/dto/*.tsRequest, query, response, validation, examples.
Servicesapps/api/src/modules/attendance/{admin,staff,settings,portal,shared}/*.service.tsBehavior, side effects, response mapping, errors.
Schemapackages/db/src/schema/school/attendance.ts, packages/db/src/migrations/0020_attendance_module.sqlIDs, enums, persisted fields, constraints.
Queuepackages/jobs/src/index.ts (QueueName.ATTENDANCE, AttendanceJob, AttendanceSweepPayload)Async job contract behind the two sweeps.
Error registryapps/api/src/common/types/error-codes.tsEvery ATTENDANCE_*/STAFF_ATTENDANCE_* code.
Permissions/guardpackages/db/src/authorization/permission-catalog.ts, packages/db/src/seed/seed-auth.ts, apps/api/src/common/authorization/role.guard.ts, apps/api/test/structure/route-permissions.spec.tsPermission codes, seeded grants (none), and the no-permission allowlist for the six portal handlers.
Testsapps/api/src/modules/attendance/__tests__/*.spec.tsConfirmed edge cases and example behaviors used below.

2. Module Summary

FieldValue
Module nameAttendance
Module slugattendance
Primary actorsAdministrator (StudentAttendance_*, StaffAttendance_*, AttendanceSettings_*), class teacher (audience staff, no permission), pupil (audience student, no permission), guardian (audience guardian, no permission), internal system (BullMQ)
API surfacesadmin (registered in ADMIN_MODULES via AttendanceAdminModule, AttendanceStaffModule, AttendanceSettingsModule), mobile (registered in MOBILE_CHILDREN via TeacherAttendancePortalModule, StudentAttendancePortalModule, GuardianAttendancePortalModule)
Base route prefixes/api/attendance/students, /api/attendance/staff, /api/attendance/overview, /api/attendance/settings, /api/mobile/teacher/attendance, /api/mobile/student/attendance, /api/mobile/guardian/children
Auth modelJwtAuthGuard (global) + RoleGuard on every route. Admin/staff/settings routes additionally carry @Permissions(). All six portal routes carry no @Permissions() and are audience-asserted instead (assertPortalAudience / assertTeacherStaffRow).
PersistencePostgreSQL only — no Redis cache, no MongoDB collection owned by this module.
Runtime source of truthstudent_attendance_sessions/_records, staff_attendance_records, attendance_change_log, attendance_notification_settings; the academic calendar and academic sessions are read-only dependencies owned elsewhere.
Sibling docsBackend: /docs/developer/attendance/backend. Features/flows: /docs/developer/attendance/feature.

3. Concepts and Terminology

TermMeaningSource FileUsed By
Register / sessionOne class's attendance record for one date — student_attendance_sessionsattendance.ts:90Every student-attendance endpoint
Runtime rowThe system-written scheduling facts for a session, carrying no versionattendance.ts:161The reminder sweep, the idempotency check
RecordOne pupil's or staff member's status for one dayattendance.ts:210,329Every mark/correct endpoint
Change logThe append-only history of every attendance transition, including the first markingattendance.ts:405Both history endpoints
Statuspresent | absent | late | excused — closed, no "unmarked" memberattendance.ts:60-65Every DTO carrying a status
Session statusnot_started | in_progress | completed — the register's own lifecycleattendance.ts:75-79StudentAttendanceSessionDto
Subject kindstudent | staff — which population a change-log row describesattendance.ts:81-84AttendanceHistoryEntryDto/staff equivalent
Force writeAn administrator's override of the calendar's "not a school day" refusal, with a stated reasondto/student-attendance.dto.ts:103-114BulkMarkAttendanceDto (admin only)
Expected versionThe optimistic-concurrency token — the session's or the record's own versionschema version columnsEvery mutating request except the staff bulk write
Submission idA client-generated id that makes a bulk write idempotentattendance.ts:188-196BulkMarkAttendanceDto, TeacherMarkAttendanceDto
Attendance expectedWhether the calendar classification requires attendance on a date, derived, never storedcalendar-contract.ts:115-121AttendanceCalendarDayDto.attendanceExpected
RosterEvery live pupil on a class's roll for a date, with their record left-joined onattendance-roster.service.ts:126-142StudentAttendanceStateDto.roster
TallyCounts derived from one roster read, never storedattendance-roster.service.ts:144-152AttendanceTallyDto, StaffAttendanceTallyDto
Schedule statepublished | no_published_timetable | no_period_today — a class's timetable readiness for a datedto/attendance-overview.dto.ts:12-18AttendanceOverviewRowDto

4. API Surface Map

SurfaceMethodPathActorAuth/GuardPermissionControllerPurpose
AdminGET/api/attendance/studentsAdminJwtAuthGuard, RoleGuardStudentAttendance_READStudentAttendanceControllerRead a class's register for one date.
AdminPUT/api/attendance/studentsAdminJwtAuthGuard, RoleGuardStudentAttendance_CREATE, StudentAttendance_UPDATEStudentAttendanceControllerBulk mark or correct a class's register.
AdminPATCH/api/attendance/students/:recordIdAdminJwtAuthGuard, RoleGuardStudentAttendance_UPDATEStudentAttendanceControllerCorrect one pupil's record.
AdminPOST/api/attendance/students/completeAdminJwtAuthGuard, RoleGuardStudentAttendance_UPDATEStudentAttendanceControllerComplete a register.
AdminPOST/api/attendance/students/reopenAdminJwtAuthGuard, RoleGuardStudentAttendance_UPDATEStudentAttendanceControllerReopen a completed register.
AdminPOST/api/attendance/students/resetAdminJwtAuthGuard, RoleGuardStudentAttendance_DELETEStudentAttendanceControllerClear a register entirely.
AdminGET/api/attendance/students/:studentId/historyAdminJwtAuthGuard, RoleGuardStudentAttendance_READStudentAttendanceHistoryControllerA pupil's attendance change log.
AdminGET/api/attendance/overviewAdminJwtAuthGuard, RoleGuardStudentAttendance_READAttendanceOverviewControllerThe whole school's attendance state for one date.
AdminGET/api/attendance/staffAdminJwtAuthGuard, RoleGuardStaffAttendance_READStaffAttendanceControllerList the staff register for one date.
AdminPUT/api/attendance/staffAdminJwtAuthGuard, RoleGuardStaffAttendance_CREATE, StaffAttendance_UPDATEStaffAttendanceControllerMark the staff register for one date.
AdminPATCH/api/attendance/staff/:recordIdAdminJwtAuthGuard, RoleGuardStaffAttendance_UPDATEStaffAttendanceControllerCorrect one staff attendance record.
AdminGET/api/attendance/staff/:staffId/historyAdminJwtAuthGuard, RoleGuardStaffAttendance_READStaffAttendanceHistoryControllerOne staff member's attendance change history.
AdminGET/api/attendance/settingsAdminJwtAuthGuard, RoleGuardAttendanceSettings_READAttendanceSettingsControllerRead both attendance notification settings.
AdminPUT/api/attendance/settings/:kindAdminJwtAuthGuard, RoleGuardAttendanceSettings_UPDATEAttendanceSettingsControllerUpdate one attendance notification setting.
MobileGET/api/mobile/teacher/attendance/classesTeacherJwtAuthGuard, RoleGuardNone — audience-assertedTeacherAttendancePortalControllerThe classes I am the class teacher of.
MobileGET/api/mobile/teacher/attendanceTeacherJwtAuthGuard, RoleGuardNone — audience-assertedTeacherAttendancePortalControllerRead one of my classes' register for one date.
MobilePUT/api/mobile/teacher/attendanceTeacherJwtAuthGuard, RoleGuardNone — audience-assertedTeacherAttendancePortalControllerMark today's register for one of my classes.
MobilePOST/api/mobile/teacher/attendance/completeTeacherJwtAuthGuard, RoleGuardNone — audience-assertedTeacherAttendancePortalControllerComplete one of my classes' register.
MobileGET/api/mobile/student/attendancePupilJwtAuthGuard, RoleGuardNone — audience-assertedStudentAttendancePortalControllerMy own attendance, date-ranged.
MobileGET/api/mobile/student/attendance/summaryPupilJwtAuthGuard, RoleGuardNone — audience-assertedStudentAttendancePortalControllerMy attendance totals for the current academic session.
MobileGET/api/mobile/guardian/children/:studentId/attendanceGuardianJwtAuthGuard, RoleGuardNone — audience-assertedGuardianAttendancePortalControllerOne of my children's attendance, date-ranged.
MobileGET/api/mobile/guardian/children/:studentId/attendance/summaryGuardianJwtAuthGuard, RoleGuardNone — audience-assertedGuardianAttendancePortalControllerOne of my children's attendance totals for the current academic session.

AttendanceSharedModule and AttendanceWorkersModule declare no controller and contribute nothing to either Swagger document — the two BullMQ sweeps have no HTTP surface at all.

5. Auth, Identity, and Permissions

SurfaceGuard/DecoratorIdentity ShapePermissionGuest AllowedNotes
Admin (student, staff, settings)JwtAuthGuard, RoleGuard, @Permissions(...)req.user, activeRoleAs listed in §4NoNone of these three permission modules is granted to any seeded role — see below.
Teacher portalJwtAuthGuard, RoleGuard, no @Permissions()req.user acting as the role named teacher, resolved to a live staff.id via assertTeacherStaffRowN/ANoHandler key must be in NO_PERMISSION_ADMIN_HANDLERS (role.guard.ts) and its mirror route-permissions.spec.ts, or every caller including superadmin gets 403.
Student portalJwtAuthGuard, RoleGuard, no @Permissions()req.user, matched to students.user_idN/ANoTakes no id parameter at all — the pupil is resolved entirely from the token.
Guardian portalJwtAuthGuard, RoleGuard, no @Permissions()req.user, matched through guardians.user_id then a ward checkN/ANoA non-ward studentId answers 404, never 403.

Why no seeded role holds StudentAttendance_*, StaffAttendance_* or AttendanceSettings_*. packages/db/src/seed/seed-auth.ts grants none of these codes to any role. TEACHER_PERMISSIONS spreads STAFF_PERMISSIONS in that file, so anything granted to staff reaches every teacher, and both seeded roles carry scopeKind: "all" — so granting StudentAttendance_READ to staff would give every teacher an unscoped read over every pupil's attendance and history, with none of the class-teacher predicate the portal enforces. A school that wants a non-administrator to hold these codes creates a dedicated role for it; grantToRoles never revokes a grant, so withholding by default is the recoverable direction.

AttendanceView — the screen code, which gates no route. A fourth module, AttendanceView, gates the panel's attendance screens: a pupil's own (/dashboard/my-attendance), a guardian's per-child view (/dashboard/children/{studentId}/attendance) and a class teacher's register (/dashboard/my-class/attendance), plus their sidebar entries. It appears on no API route — the three portal surfaces above carry no @Permissions() and assert their audience instead — so holding it grants no data access. AttendanceView_READ is seeded to staff (and through the spread to teacher), guardian and student. It is one of the person-bound screen modules a superadmin's GET /api/auth/permissions list omits, because a superadmin account has no pupil, ward or class of its own; see the auth API page, §3.5. The calendar code, AcademicCalendarView_READ, stays separate because the calendar is also an administrator screen.

The portal's audience assertion is not a permission, and is not a row scope either. assertPortalAudience(actor, "student" | "guardian") and assertTeacherStaffRow(db, actor) (apps/api/src/modules/mobile/shared/portal-audience.ts:74,176) prove only who is calling. Which rows come back is decided entirely by the query each service builds — classes.class_teacher_id = staffId for the teacher, students.user_id = actor.id for the pupil, the student_guardian join for the guardian. None of the three ever delegates to PeopleAccessService.scopeFor, whose {kind: "all"} result for a staff/teacher session would defeat the whole point of a per-route predicate.

6. DTO and Model Reference

6.1 MarkAttendanceEntryDto (nested request)

FieldTypeRequiredDefaultValidationExampleSource
studentIdstring (uuid)YesN/A@IsUUID"018f..."dto/student-attendance.dto.ts:44-47
status"present"|"absent"|"late"|"excused"|nullYes (present in payload)N/A@IsIn(ATTENDANCE_STATUS), nullable"present":54-57
notestring | nullNoN/A@MaxLength(500), no leading/trailing whitespace, no control characters"Called in sick":59-64

status: null deletes that pupil's record for the date rather than storing a null status.

6.2 BulkMarkAttendanceDto (request, admin PUT)

FieldTypeRequiredDefaultValidationExampleSource
classPublicIdstring (uuid)YesN/A@IsUUID—:73-75
datestringYesN/A@IsDateString"2026-09-13":77-79
expectedVersionnumber | nullYes (present in payload)N/A@IsInt, @Min(0), nullable3:86-90
submissionIdstring (uuid)YesN/A@IsUUID—:92-95
entriesMarkAttendanceEntryDto[]YesN/A@ArrayMinSize(1), @ValidateNested—:97-101
forcebooleanNoN/A@IsBooleantrue:103-107
forcedReasonstringNoN/A@MaxLength(200), whitespace/control-char format"Emergency makeup class":109-114

expectedVersion: null means "I read a class/date with no session row yet" — the first write of the day has no token to send.

6.3 GetStudentAttendanceQueryDto (query, GET .../students)

FieldTypeRequiredDefaultValidationExampleSource
datestringYesN/A@IsDateString"2026-09-13":118-120
classPublicIdstring (uuid)YesN/A@IsUUID—:122-124

6.4 PatchStudentAttendanceRecordDto (request, PATCH .../:recordId)

FieldTypeRequiredDefaultValidationExampleSource
status"present"|"absent"|"late"|"excused"YesN/A@IsIn(ATTENDANCE_STATUS) (not nullable — PATCH always addresses an existing record)"present":131-133
notestring | nullNoN/ASame format rule as MarkAttendanceEntryDto.note—:135-140
reasonstringNoN/A@MaxLength(500), same format rule; written to the CHANGE LOG's reason, not to the record"Parent called in":142-148
expectedVersionnumberYesN/A@IsInt, @Min(0) (never null — a record to PATCH already has a real version)1:150-153

6.5 CompleteAttendanceDto / ReopenAttendanceDto / ResetAttendanceDto (requests)

FieldTypeRequiredDefaultValidationExampleSource
classPublicIdstring (uuid)YesN/A@IsUUID—:157-159
datestringYesN/A@IsDateString"2026-09-13":161-163
expectedVersionnumber | nullYesN/A@IsInt, @Min(0), nullable3:165-169

ReopenAttendanceDto and ResetAttendanceDto extend CompleteAttendanceDto verbatim, with no additional fields (:172,180).

6.6 StudentAttendanceRosterEntryDto (response, nested)

FieldTypeRequiredDefaultValidationExampleSource
studentIdstring (uuid)YesN/A——:185
rollNumbernumber | nullYesN/A—12:188-189
admissionNumberstringYesN/A——:190
studentCodestringYesN/A——:191
fullNamestringYesN/AServer-composed, collapsed whitespace"Aashna Rai":192
recordIdstring | null (uuid)YesN/ANull when unmarked—:193-194
status"present"|"absent"|"late"|"excused"|nullYesN/ANull when unmarked—:195-196
notestring | nullYesN/A——:197
markedAtstring | null (ISO)YesN/ANull when unmarked—:198
recordVersionnumber | nullYesN/ANull when unmarked; the token a subsequent PATCH must echo0:199-201

6.7 AttendanceTallyDto (response, nested)

FieldTypeSource
totalStudents, markedCount, unmarkedCount, presentCount, absentCount, lateCount, excusedCountnumber:204-212, all derived, never stored

6.8 StudentAttendanceSessionDto / AttendanceCalendarDayDto (response, nested)

FieldTypeNotesSource
idstring (uuid)—:215
status"not_started"|"in_progress"|"completed"—:216-217
completedAtstring | null (ISO)Non-null iff status = completed:218
versionnumberThe session's own optimistic-concurrency token:219
classificationone of the seven calendar_day_classification valuesOwned by the academic-calendar module; consumed here:228-229
attendanceExpectedbooleanDerived from classification, never stored:230-233
eventNamestring | nulle.g. "Dashain":234

6.9 StudentAttendanceStateDto (response, the shared envelope)

FieldTypeRequiredNotesSource
classPublicIdstring (uuid)Yes—:238
classLabelstringYese.g. "Grade 5 A (Morning)", composed server-side:239-243
datestringYes—:244
calendarAttendanceCalendarDayDtoYes—:245-246
sessionStudentAttendanceSessionDto | nullYesNull when never opened for this class/date — NOT a 404:247-248
versionnumber | nullYesMirrors session?.version, flattened; null exactly when session is null:249-252
rosterStudentAttendanceRosterEntryDto[]Yes—:253-254
tallyAttendanceTallyDtoYes—:255-256

This exact shape is returned by every read, write, and lifecycle endpoint under /attendance/students and /mobile/teacher/attendance — always a fresh re-read, never an echo of the request.

6.10 StudentAttendanceRecordDto (response, PATCH .../:recordId)

FieldTypeSource
id, sessionId, studentIdstring (uuid):260-262
status"present"|"absent"|"late"|"excused":263
note, forcedReasonstring | null:264-265
markedAtstring (ISO):266
versionnumber:267

6.11 StudentAttendanceHistoryQueryDto / AttendanceHistoryEntryDto

FieldTypeRequiredDefaultValidationSource
from, tostringNoN/A@IsDateString, inclusivedto/attendance-history.dto.ts:14-22
cursorstringNoN/AOpaque, base64url([changedAtIso, id]):24-29
sizenumberNo20@Min(1), @Max(100):31-36
idstring (uuid)responseN/A—:41
attendanceDatestringresponseN/A—:42
previousStatusstatus or nullresponseN/ANull means "was unmarked" — the FIRST marking, not only a correction:43-49
newStatusstatus or nullresponseN/ANull means "reset to unmarked":50-55
reason, classLabelstring | nullresponseN/A—:56-57
changedByNamestringresponseN/ASnapshotted, survives the actor's own deletion:58
changedAtstring (ISO)responseN/A—:59

6.12 AttendanceOverviewQueryDto / AttendanceOverviewRowDto

FieldTypeRequiredNotesSource
datestringYes@IsDateStringdto/attendance-overview.dto.ts:8-9
classPublicIdstring (uuid)response—:22
classLabelstringresponse—:23
sessionStatussession status or nullresponseNull means no session row exists yet:24-26
totalStudents, markedCount, presentCount, absentCount, lateCount, excusedCountnumberresponseGrouped SQL, never a per-class round trip:27-32
classTeacherNamestring | nullresponse—:33
scheduleState"published"|"no_published_timetable"|"no_period_today"responseSee §3:34-35
reminderSentAtstring | null (ISO)response—:36
unalertedAbsencesnumberresponseAbsent, past the cutoff, guardian_alerted_at IS NULL:37-38

6.13 ListStaffAttendanceQueryDto

FieldTypeRequiredDefaultValidationSource
datestringYesN/A@IsDateStringdto/staff-attendance.dto.ts:62-64
designationIdnumberNoN/A@IsInt — filters the Teachers/Staff tab:66-72
pagenumberNo1@Min(1):74-79
sizenumberNo20@Min(1), @Max(100):81-87

6.14 StaffAttendanceRowDto / StaffAttendanceTallyDto / StaffAttendanceListDto (response)

FieldTypeNotesSource
staffIdstring (uuid)—:93
employeeCode, namestring—:94-95
designationName, departmentNamestring | null—:96-97
statusstatus or nullNull when unmarked for the date:98-99
notestring | null—:100
recordIdstring | null (uuid)Null when unmarked:102-103
versionnumber | nullNull when unmarked; informational on the list — the bulk write carries no token at all:104-107
(list) datestring—:121
(list) entriesStaffAttendanceRowDto[]—:122-123
(list) tallyStaffAttendanceTallyDto (totalStaff, markedCount, unmarkedCount, presentCount, absentCount, lateCount, excusedCount)—:110-126

6.15 StaffAttendanceEntryDto / BulkStaffAttendanceDto (request, PUT /attendance/staff)

FieldTypeRequiredValidationNotesSource
staffIdstring (uuid)Yes@IsUUID—:128-131
statusstatus or nullYes@IsIn(ATTENDANCE_STATUS), nullableNull deletes the record for the date:133-138
notestring | nullNo@MaxLength(500), format rule via custom @NoControlCharacters() decorator—:140-148
datestringYes@IsDateString—:159-162
entriesStaffAttendanceEntryDto[]Yes@IsArray, @ValidateNestedNo expectedVersion, no submissionId — this write is last-write-wins by design:164-168

6.16 PatchStaffAttendanceDto (request, PATCH /attendance/staff/:recordId)

FieldTypeRequiredValidationNotesSource
statusstatusYes@IsIn(ATTENDANCE_STATUS) — never null; unmarking goes through the bulk endpoint—:178-180
notestring | nullNoSame format rule—:182-190
reasonstring | nullNoSame format rule; written to the change log—:192-200
expectedVersionnumberYes@IsIntThis path DOES have a real row/token, unlike the bulk write:202-208

6.17 StaffAttendanceHistoryQueryDto / StaffAttendanceHistoryEntryDto

Same shape and semantics as §6.11, applied to subjectKind = 'staff' (dto/staff-attendance.dto.ts:211-250).

6.18 AttendanceSettingRowDto / AttendanceSettingsListDto / UpdateAttendanceSettingsDto

FieldTypeRequiredDefaultValidationNotesSource
kind"attendance.first_period_reminder"|"attendance.student_absent"responseN/A——dto/attendance-settings.dto.ts:34-35
enabledbooleanYes (request) / responseN/A@IsBoolean—:37,56
channelsstring[]Yes (request) / responseN/A@ArrayMaxSize(4), @IsString({each:true}); membership against NOTIFICATION_CHANNEL and distinctness checked in the SERVICE, not hereResponse typed as NotificationChannel[]:39-40,63-67
versionnumberresponse / requestN/A@IsInt (request)—:42,69-71
effectiveNotestringresponseN/A—The subtractive-only explanation, always the same constant string:44-45
expectedVersionnumberYes (request)N/A@IsInt—:69-71
(list) settingsAttendanceSettingRowDto[]responseN/A—Always both kinds, in ATTENDANCE_NOTIFICATION_KINDS order:48-51

6.19 Portal DTOs

DTOFieldTypeRequiredNotesSource
TeacherAttendanceQueryDtodate, classPublicIdstringYes—dto/teacher-attendance-portal.dto.ts:14-22
TeacherMarkAttendanceDtoclassPublicId, date, expectedVersion, submissionId, entries(as §6.2, minus force/forcedReason)Yes/Yes/Yes/Yes/YesIdentical shape to BulkMarkAttendanceDto with the two admin-only fields REMOVED — sending either is a 400 via forbidNonWhitelisted:30-54
TeacherCompleteAttendanceDtoclassPublicId, date, expectedVersion(as §6.5)Yes/Yes/Yes—:56-70
TeacherClassSummaryDtoclassPublicId, classLabel, totalStudentsresponseYes—:72-76
StudentAttendancePortalQueryDtofrom, tostringYes@IsDateStringdto/student-attendance-portal.dto.ts:6-14
StudentAttendancePortalEntryDtoattendanceDate, status, classLabel, bsYear, bsMonth, bsDay, noteresponseYesNo markedBy, no staff identity:18-38
GuardianAttendancePortalQueryDtofrom, tostringYes@IsDateStringdto/guardian-attendance-portal.dto.ts:6-14
GuardianAttendancePortalEntryDtoattendanceDate, status, classLabel, bsYear, bsMonth, bsDay, noteresponseYesNo markedBy, no staff identity:18-38

PortalAttendanceSummaryDto (response of both summary routes; portal/shared/portal-attendance-summary.dto.ts):

FieldTypeRequiredNotes
academicSessionNamestring | nullYes (nullable)Name of the current academic session, for example "2082/83". null when no session is current.
fromstring | nullYes (nullable)First day of the window, YYYY-MM-DD: the session's start date.
tostring | nullYes (nullable)Last day of the window: school-local today, or the session's end date once the session has ended.
expectedDaysnumberYesDays in the window on which the pupil held an enrolment in a class and the calendar requires attendance (school_day, special_school_day).
markednumberYesExpected days that have a student attendance record for the pupil.
presentnumberYesMarked days with status present.
latenumberYesMarked days with status late.
absentnumberYesMarked days with status absent.
excusednumberYesMarked days with status excused.
unmarkednumberYesmax(expectedDays - marked, 0).
ratePctnumber | nullYes (nullable)(present + late) as a percentage of marked, rounded to two decimals. A share strictly between none and all is clamped to 0.01–99.99, so a single absence never reads 100. null when nothing is marked.

With no current session, or a window that has not opened yet (to earlier than the session start), every count is 0 and from, to and ratePct are null; academicSessionName is still set when a session exists.

7. Enum Reference

EnumValueMeaningRuntime EffectSource
attendance_statuspresentPupil/staff was present.—attendance.ts:60-65
attendance_statusabsentPupil/staff was absent.Qualifies for the guardian absence sweep (students only).attendance.ts:60-65
attendance_statuslatePupil/staff arrived late.Never alerted by the absence sweep.attendance.ts:60-65
attendance_statusexcusedPupil/staff was excused.Never alerted by the absence sweep.attendance.ts:60-65
attendance_session_statusnot_startedNo record has been written for the register yet.—attendance.ts:75-79
attendance_session_statusin_progressAt least one record exists; not yet completed.Eligible for complete.attendance.ts:75-79
attendance_session_statuscompletedEvery live roster pupil was marked and the office closed the day.Bulk write requires refuseIfCompleted opt-out on the admin surface; the portal always refuses.attendance.ts:75-79
attendance_subject_kindstudentA change-log row about a pupil.—attendance.ts:81-84
attendance_subject_kindstaffA change-log row about a staff/teacher.—attendance.ts:81-84
AttendanceScheduleStatepublishedThe class has a published timetable with a period today.Reminder sweep can find a candidate.dto/attendance-overview.dto.ts:12-18
AttendanceScheduleStateno_published_timetableThe class has no published timetable at all.Reminder sweep will never find a candidate for this class.same
AttendanceScheduleStateno_period_todayThe class has a published timetable but no period today.Reminder sweep skips today only.same
AttendanceNotificationKindattendance.first_period_reminderThe class-teacher reminder.push,in_app,sms,email builders all exist.attendance.types.ts:30-33, template-registry.ts:275
AttendanceNotificationKindattendance.student_absentThe guardian alert.Same four channel builders.attendance.types.ts:30-33, template-registry.ts:327

8. Endpoint Reference

8.1 GET /api/attendance/students

Purpose

Returns a class's full register for one date — every live pupil, marked or not — for the admin attendance screen to render. A session: null in the response is normal for a class/date never opened and is not a 404.

Source Evidence

EvidencePath
Controllerapps/api/src/modules/attendance/admin/student-attendance.controller.ts:56-71
DTOapps/api/src/modules/attendance/admin/dto/student-attendance.dto.ts:117-125
Serviceapps/api/src/modules/attendance/admin/student-attendance-read.service.ts:139-192
Schemapackages/db/src/schema/school/attendance.ts
Testsapps/api/src/modules/attendance/__tests__/student-attendance.integration.spec.ts

Auth and Permissions

  • Auth: JWT required.
  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: StudentAttendance_READ.
  • Guest support: No.
  • Rate limit: Global default only.
  • Idempotency: N/A (read).

Request

PartRequiredDetails
HeadersYesAuthorization: Bearer <token>.
ParamsNo—
QueryYesdate (YYYY-MM-DD), classPublicId (uuid).
BodyNo—

Response

200, StudentAttendanceStateDto (§6.9):

{
  "success": true,
  "message": "Attendance fetched.",
  "data": {
    "classPublicId": "018f...",
    "classLabel": "Grade 5 A (Morning)",
    "date": "2026-09-13",
    "calendar": { "classification": "school_day", "attendanceExpected": true, "eventName": null },
    "session": { "id": "018f...", "status": "in_progress", "completedAt": null, "version": 2 },
    "version": 2,
    "roster": [
      { "studentId": "018f...", "rollNumber": 1, "admissionNumber": "A-1024", "studentCode": "S-2091", "fullName": "Aashna Rai", "recordId": "018f...", "status": "present", "note": null, "markedAt": "2026-09-13T02:35:00.000Z", "recordVersion": 0 },
      { "studentId": "018f...", "rollNumber": 2, "admissionNumber": "A-1025", "studentCode": "S-2092", "fullName": "Bibek Thapa", "recordId": null, "status": null, "note": null, "markedAt": null, "recordVersion": null }
    ],
    "tally": { "totalStudents": 2, "markedCount": 1, "unmarkedCount": 1, "presentCount": 1, "absentCount": 0, "lateCount": 0, "excusedCount": 0 }
  }
}

Side Effects

None — read-only. No cache read/write, no job, no notification, no audit write beyond the global request log.

Error Cases

HTTP StatusError CodeConditionUser-Facing MeaningSource
404CLASS_NOT_FOUNDclassPublicId does not resolve.Verify the class id.student-attendance-read.service.ts:89-94
422ATTENDANCE_DATE_OUTSIDE_SESSIONdate is outside the class's own academic session [start_date, end_date), whichever session is current.The class belongs to a different academic year than the date; pick a class of the session that contains the date.calendar-day.service.ts (requireWithinSession)
409ACADEMIC_CALENDAR_NOT_GENERATEDdate is inside the class's session but the calendar has no row for it.Generate the calendar for this date first.calendar-day.service.ts (require)

Edge Cases

  • Never-opened register: session: null, version: null, every roster row status: null.
  • A pupil with no assigned roll number sorts last, ordered after by admission number.
  • Empty class (zero active enrolments): roster: [], tally.totalStudents: 0.

Example Requests

GET /api/attendance/students?date=2026-09-13&classPublicId=018f2c4a-... HTTP/1.1
Authorization: Bearer TOKEN
curl -G "$API_URL/api/attendance/students" \
  -H "Authorization: Bearer TOKEN" \
  -d date=2026-09-13 \
  -d classPublicId=018f2c4a-...

8.2 PUT /api/attendance/students

Purpose

Marks or corrects a class's register for one date, as a set of changes rather than a full-state rewrite. Opens the register lazily if this is the first write of the day. Call this from the class register screen on every "save" action.

Source Evidence

EvidencePath
Controllerstudent-attendance.controller.ts:73-89
DTOdto/student-attendance.dto.ts:44-115
Serviceadmin/student-attendance-write.service.ts:87-367
Schemaattendance.ts:90-207,209-315
Testsstudent-attendance.integration.spec.ts:277-525

Auth and Permissions

  • Auth: JWT required.
  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: StudentAttendance_CREATE AND StudentAttendance_UPDATE.
  • Guest support: No.
  • Rate limit: Global default only.
  • Idempotency: Yes — submissionId; a replay writes nothing and returns 200 with the current state.

Request

PartRequiredDetails
HeadersYesAuthorization: Bearer <token>.
ParamsNo—
QueryNo—
BodyYesBulkMarkAttendanceDto (§6.2).

Minimal valid body:

{
  "classPublicId": "018f2c4a-...",
  "date": "2026-09-13",
  "expectedVersion": null,
  "submissionId": "018f2c4b-...",
  "entries": [
    { "studentId": "018f2c4c-...", "status": "present" }
  ]
}

Full body with a force-write:

{
  "classPublicId": "018f2c4a-...",
  "date": "2026-09-14",
  "expectedVersion": 0,
  "submissionId": "018f2c4d-...",
  "entries": [
    { "studentId": "018f2c4c-...", "status": "present", "note": "Makeup session" },
    { "studentId": "018f2c4e-...", "status": null }
  ],
  "force": true,
  "forcedReason": "Emergency makeup class on a public holiday"
}

Response

200, StudentAttendanceStateDto (same shape as §8.1).

Side Effects

  • Database writes: student_attendance_sessions (insert-or-find), student_attendance_records (upsert marked entries, delete unmarked entries), attendance_change_log (one row per real status change, including the first marking), student_attendance_session_runtime (last_submission_id, and a status-advance UPDATE on student_attendance_sessions when the first record is written).
  • Cache: None.
  • Jobs: None enqueued synchronously.
  • Notifications: None synchronous — the reminder sweep may later see this session already opened and skip its own candidate check.
  • Audit: ActivityAuditInterceptor records the call under StudentAttendance/CREATE (the first declared permission).

Error Cases

HTTP StatusError CodeConditionUser-Facing MeaningSource
422ATTENDANCE_EMPTY_PAYLOADentries is empty.Submit at least one entry.student-attendance-write.service.ts (bulkMark)
422ATTENDANCE_DUPLICATE_STUDENTThe same studentId appears twice.De-duplicate the payload.student-attendance-write.service.ts (bulkMark)
422ATTENDANCE_DATE_IN_FUTUREdate is after today, school-local.Resend with a non-future date.student-attendance-write.service.ts (bulkMark)
422ATTENDANCE_DATE_OUTSIDE_SESSIONdate is outside the class's academic session range.Resend with a valid date.calendar-day.service.ts (requireWithinSession)
409ACADEMIC_CALENDAR_NOT_GENERATEDNo calendar row for this date.Generate the calendar first.calendar-day.service.ts (require)
422ATTENDANCE_NOT_A_SCHOOL_DAYNot a school day and force unset.Resend with force: true and a reason.student-attendance-write.service.ts (bulkMark)
409ATTENDANCE_VERSION_CONFLICTStale expectedVersion, or null when the register was already opened.Reload and retry.student-attendance-write.service.ts (bulkMark)
422ATTENDANCE_STUDENT_NOT_ENROLLEDA studentId has no active enrolment in this class.Remove it from the payload.student-attendance-write.service.ts (bulkMark)
500WRITE_RETURNED_NO_ROWThe session insert-then-reselect found nothing (practically unreachable).Retry; report if persistent.student-attendance-write.service.ts (bulkMark)
409ACADEMIC_SESSION_LOCKEDThe class's academic session has locked_at set.Reopen the year before writing.Postgres trigger, not this service — see the academic-sessions backend doc.

Edge Cases

  • Replayed submissionId: no error, no new side effect, returns the current state.
  • force: true with forcedReason omitted: accepted; forced_reason stays null.
  • A single entry with status: null on a class that was never marked: no session existed, none created (the delete matches nothing), no error.
  • Every entry omits a pupil currently on the roll: that pupil is simply left unchanged — never implicitly unmarked.

Example Requests

PUT /api/attendance/students HTTP/1.1
Authorization: Bearer TOKEN
Content-Type: application/json
curl -X PUT "$API_URL/api/attendance/students" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"classPublicId":"018f2c4a-...","date":"2026-09-13","expectedVersion":null,"submissionId":"018f2c4b-...","entries":[{"studentId":"018f2c4c-...","status":"present"}]}'

8.3 PATCH /api/attendance/students/:recordId

Purpose

Corrects one pupil's already-existing record, keyed on the record's own version rather than the session's. Use this for a single fix after the fact — including against a completed register, which this route is deliberately permitted to touch.

Source Evidence

EvidencePath
Controllerstudent-attendance.controller.ts:91-108
DTOdto/student-attendance.dto.ts:130-154
Serviceadmin/student-attendance-write.service.ts:372-478
Testsstudent-attendance.integration.spec.ts

Auth and Permissions

  • Auth: JWT required.
  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: StudentAttendance_UPDATE.
  • Guest support: No.
  • Idempotency: No — a repeated identical PATCH still requires the current expectedVersion and bumps version again.

Request

PartRequiredDetails
ParamsYesrecordId (uuid, path).
BodyYesPatchStudentAttendanceRecordDto (§6.4).
{ "status": "excused", "note": "Doctor's appointment", "reason": "Parent provided a note after the fact", "expectedVersion": 0 }

Response

200, StudentAttendanceRecordDto (§6.10).

Side Effects

  • Writes: student_attendance_records (the one row), attendance_change_log (one row, reason carried from the DTO).
  • No cache, job, or notification effects.

Error Cases

HTTP StatusError CodeConditionSource
404ATTENDANCE_RECORD_NOT_FOUNDrecordId does not exist.:388-393,439-442
404ATTENDANCE_SESSION_NOT_FOUNDThe record's session is missing (should not occur under normal FK integrity).:403-408
409ATTENDANCE_VERSION_CONFLICTexpectedVersion mismatched.:444-448
409ACADEMIC_SESSION_LOCKEDThe record's academic session has locked_at set.Postgres trigger — see the academic-sessions backend doc.

Edge Cases

  • Correcting a completed register succeeds — this is the correction path for a closed day.
  • reason is written only to the change log, never to the record itself.

Example Requests

curl -X PATCH "$API_URL/api/attendance/students/018f2c4c-.../history" \
  -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
  -d '{"status":"excused","expectedVersion":0}'

8.4 POST /api/attendance/students/complete

Purpose

Closes a register once every pupil on the current roll has a record. Called from the "finish taking attendance" action.

Source Evidence

student-attendance.controller.ts:110-125; admin/student-attendance-lifecycle.service.ts:57-102; dto/student-attendance.dto.ts:156-170.

Auth and Permissions

StudentAttendance_UPDATE. Same guard chain as above.

Request

Body: CompleteAttendanceDto (§6.5) — classPublicId, date, expectedVersion.

Response

200, StudentAttendanceStateDto with session.status: "completed".

Side Effects

Writes student_attendance_sessions only (status, completed_at, completed_by_user_id). No record/log write.

Error Cases

HTTP StatusError CodeCondition
404ATTENDANCE_SESSION_NOT_FOUNDNo register exists for the class/date.
409ATTENDANCE_VERSION_CONFLICTStale token.
409ATTENDANCE_ALREADY_COMPLETEDAlready completed.
422ATTENDANCE_INCOMPLETE_ROSTERA live roster row has no record.

Edge Cases

A pupil enrolled after the register was opened but before completion is included in the same-transaction roster read, so completion still correctly requires them to be marked.

8.5 POST /api/attendance/students/reopen

Purpose

Returns a completed register to in_progress so it can be bulk-corrected via PUT again.

Source Evidence

student-attendance.controller.ts:127-139; admin/student-attendance-lifecycle.service.ts:104-137.

Auth and Permissions

StudentAttendance_UPDATE.

Request

Body: ReopenAttendanceDto (identical shape to CompleteAttendanceDto).

Response

200, StudentAttendanceStateDto with session.status: "in_progress", completedAt: null.

Error Cases

HTTP StatusError CodeCondition
404ATTENDANCE_SESSION_NOT_FOUNDNo register exists.
409ATTENDANCE_VERSION_CONFLICTStale token.
409ATTENDANCE_NOT_COMPLETEDThe register was never completed.

8.6 POST /api/attendance/students/reset

Purpose

Clears a class's entire register for one date — deletes every record, logs one deletion per previously-marked pupil, and returns the session to not_started. The most destructive route in this module; use it to recover from a mismarked day, not for a routine correction (which is PATCH .../:recordId).

Source Evidence

student-attendance.controller.ts:141-157; admin/student-attendance-lifecycle.service.ts:155-215.

Auth and Permissions

StudentAttendance_DELETE — a distinct permission code from the mark/correct/complete/reopen paths, which all use _UPDATE.

Request

Body: ResetAttendanceDto (identical shape to CompleteAttendanceDto). expectedVersion is required here for the same reason it is required on complete and reopen — a stale screen must not be able to erase marks someone else just saved.

Response

200, StudentAttendanceStateDto with session.status: "not_started", roster entirely unmarked, tally.markedCount: 0.

Side Effects

  • Writes: DELETE on every student_attendance_records row for the session; one attendance_change_log row per deleted record (newStatus: null); student_attendance_sessions reset; student_attendance_session_runtime.last_submission_id cleared.

Error Cases

HTTP StatusError CodeCondition
404ATTENDANCE_SESSION_NOT_FOUNDNo register exists.
409ATTENDANCE_VERSION_CONFLICTStale token.

Edge Cases

Resetting a register with nothing marked yet: succeeds as a near no-op (session status write only; nothing to delete or log).

8.7 GET /api/attendance/students/:studentId/history

Purpose

A pupil's full attendance change history, including the first marking of every day, keyset-paginated. Use this for a pupil's attendance detail/history screen.

Source Evidence

student-attendance-history.controller.ts:20-45; admin/attendance-history.service.ts; dto/attendance-history.dto.ts.

Auth and Permissions

StudentAttendance_READ.

Request

PartRequiredDetails
ParamsYesstudentId (uuid).
QueryNofrom, to (date, inclusive), cursor (opaque), size (1–100, default 20).

Response

200:

{
  "success": true,
  "message": "History fetched.",
  "data": [
    { "id": "018f...", "attendanceDate": "2026-09-13", "previousStatus": null, "newStatus": "present", "reason": null, "classLabel": "Grade 5 A (Morning)", "changedByName": "Sunita Adhikari", "changedAt": "2026-09-13T02:35:00.123Z" }
  ],
  "nextCursor": "eyJ..."
}

data is the array; nextCursor is a top-level envelope field, null on the last page.

Error Cases

HTTP StatusError CodeCondition
400PAGINATION_CURSOR_INVALIDMalformed cursor.

Edge Cases

  • A day the pupil was marked once, never corrected: one entry, previousStatus: null.
  • A bulk mark of many pupils on one day, viewed for one pupil: their single entry among the tied changed_at timestamps is still returned exactly once, at the correct page boundary.
  • Empty result (no history yet): data: [], nextCursor: null.

8.8 GET /api/attendance/overview

Purpose

The whole school's attendance state for one date, one row per active class in the academic session containing that date — the office's "who hasn't taken the register yet" screen.

Source Evidence

attendance-overview.controller.ts; attendance-overview.service.ts; dto/attendance-overview.dto.ts.

Auth and Permissions

StudentAttendance_READ.

Request

Query: date (required).

Response

200, array of AttendanceOverviewRowDto (§6.12). Empty array when no active session contains the date, or the session has no active classes.

Error Cases

None specific — an unresolvable date returns [] rather than erroring.

Edge Cases

  • A class with a published timetable but no period on that weekday: scheduleState: "no_period_today".
  • A class never opened for the date: sessionStatus: null, all counts 0.
  • unalertedAbsences can be nonzero even after the sweep has run, briefly, if the sweep tick has not yet reached that class's records.

8.9 GET /api/attendance/staff

Purpose

Lists the staff/teacher register for one date, optionally filtered by designationId for the Teachers/Staff tabs, paginated.

Source Evidence

staff-attendance.controller.ts:46-70; staff-attendance.service.ts:98-169.

Auth and Permissions

StaffAttendance_READ.

Request

Query: date (required), designationId (optional), page, size (default 1/20).

Response

200, StaffAttendanceListDto (§6.14), with pagination metadata (count, page, size) via PaginationUtil.

Side Effects

None — read-only.

Edge Cases

An unmarked staff member appears in the list with status: null, recordId: null — never omitted.

8.10 PUT /api/attendance/staff

Purpose

Marks the staff/teacher register for one date, including on a holiday or exam day (the calendar gate is never consulted here). Last write for the date wins — there is no concurrency token on this path.

Source Evidence

staff-attendance.controller.ts:72-90; staff-attendance.service.ts:183-311; dto/staff-attendance.dto.ts:159-169.

Auth and Permissions

StaffAttendance_CREATE AND StaffAttendance_UPDATE.

Request

Body: BulkStaffAttendanceDto — date, entries[] (§6.15). No expectedVersion, no submissionId.

{ "date": "2026-09-13", "entries": [{ "staffId": "018f...", "status": "present" }] }

Response

200, { date, entries: StaffAttendanceRowDto[], tally: StaffAttendanceTallyDto }.

Side Effects

Writes: staff_attendance_records (upsert marked, delete status: null), attendance_change_log (one row per real change, including the first marking).

Error Cases

HTTP StatusError CodeCondition
422ATTENDANCE_EMPTY_PAYLOADentries empty.
422ATTENDANCE_DUPLICATE_STUDENTSame staffId twice.
422ATTENDANCE_DATE_IN_FUTUREFuture date.
422ATTENDANCE_DATE_OUTSIDE_SESSIONDate not inside any academic session.
404STAFF_ATTENDANCE_RECORD_NOT_FOUNDA staffId does not resolve.

Edge Cases

  • Marking on a public holiday: succeeds.
  • Two administrators submitting the same day simultaneously: whichever transaction commits last wins entirely; both are individually logged, with no conflict surfaced to either caller.

8.11 PATCH /api/attendance/staff/:recordId

Purpose

Corrects one staff attendance record, using that record's own version — unlike the bulk write, this path has a real row to check optimistically.

Source Evidence

staff-attendance.controller.ts:92-107; staff-attendance.service.ts:315-404; dto/staff-attendance.dto.ts:171-209.

Auth and Permissions

StaffAttendance_UPDATE.

Request

Params: recordId. Body: PatchStaffAttendanceDto (§6.16).

Response

200, StaffAttendanceRowDto — re-read after the write, never an echo.

Error Cases

HTTP StatusError CodeCondition
404STAFF_ATTENDANCE_RECORD_NOT_FOUNDrecordId does not exist.
409ATTENDANCE_VERSION_CONFLICTexpectedVersion mismatched.

8.12 GET /api/attendance/staff/:staffId/history

Purpose

One staff member's attendance change history, keyset-paginated identically to the student history route.

Source Evidence

staff-attendance-history.controller.ts; staff-attendance.service.ts:414-490; dto/staff-attendance.dto.ts:211-250.

Auth and Permissions

StaffAttendance_READ.

Request

Params: staffId. Query: from, to, cursor, size.

Response

200, { data: StaffAttendanceHistoryEntryDto[], nextCursor } — same envelope shape as §8.7, built from Postgres's own ::text rendering of changed_at rather than date_trunc, closing the identical precision gap by a different route.

Error Cases

HTTP StatusError CodeCondition
400PAGINATION_CURSOR_INVALIDMalformed cursor.

8.13 GET /api/attendance/settings

Purpose

Reads both attendance notification settings rows, for the settings screen.

Source Evidence

attendance-settings.controller.ts:43-55; attendance-settings.service.ts:39-71.

Auth and Permissions

AttendanceSettings_READ.

Response

200, AttendanceSettingsListDto — always both kinds, each carrying effectiveNote.

Error Cases

HTTP StatusError CodeCondition
404ATTENDANCE_SETTINGS_NOT_FOUNDEither seeded row is missing (an operational fault, not a normal client path).

8.14 PUT /api/attendance/settings/:kind

Purpose

Updates which channels one attendance notification kind may use. Never creates a row — both are seeded by migration and this endpoint only edits them.

Source Evidence

attendance-settings.controller.ts:57-73; attendance-settings.service.ts:73-146; dto/attendance-settings.dto.ts:53-71.

Auth and Permissions

AttendanceSettings_UPDATE.

Request

Params: kind (attendance.first_period_reminder | attendance.student_absent). Body: UpdateAttendanceSettingsDto.

{ "enabled": true, "channels": ["push", "in_app", "sms", "email"], "expectedVersion": 0 }

Response

200, AttendanceSettingRowDto, re-read after the write.

Side Effects

Writes attendance_notification_settings. No downstream effect on any already-sent notification.

Error Cases

HTTP StatusError CodeCondition
404ATTENDANCE_SETTINGS_KIND_UNKNOWN:kind is not one of the two known values.
404ATTENDANCE_SETTINGS_NOT_FOUNDThe row is missing (operational fault).
422ATTENDANCE_CHANNEL_UNKNOWNA channel is not a known NOTIFICATION_CHANNEL, or repeats.
409ATTENDANCE_VERSION_CONFLICTStale expectedVersion.

Edge Cases

Enabling email/sms here does not notify any recipient who has never opened their own notification preferences — CATEGORY_DEFAULTS.attendance still applies afterwards, defaulting both off.

8.15 GET /api/mobile/teacher/attendance/classes

Purpose

Lists the classes the calling teacher is the class teacher of, with occupancy counts, for the teacher's class-picker screen.

Source Evidence

teacher-attendance-portal.controller.ts:41-51; teacher-attendance-portal.service.ts:42-83.

Auth and Permissions

  • Auth: JWT required.
  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: None — audience-asserted via assertTeacherStaffRow.
  • Guest support: No.

Response

200, TeacherClassSummaryDto[] — empty array if the caller teaches no active class in the current academic session.

Error Cases

HTTP StatusError CodeCondition
403PERMISSION_INSUFFICIENTNo active role, an active role that is guardian/student, or no live staff row for this user.

8.16 GET /api/mobile/teacher/attendance

Purpose

Reads one of the teacher's own classes' register for one date — identical response shape to the admin GET, scoped to a class they actually teach.

Source Evidence

teacher-attendance-portal.controller.ts:53-64; teacher-attendance-portal.service.ts:85-94.

Auth and Permissions

No @Permissions(); assertTeacherStaffRow plus assertOwnClass.

Request

Query: date, classPublicId (§6.19).

Response

200, StudentAttendanceStateDto (§6.9) — same shape as §8.1.

Error Cases

HTTP StatusError CodeCondition
403PERMISSION_INSUFFICIENTNot a staff session.
403ATTENDANCE_NOT_CLASS_TEACHERNot the class teacher of this class.
404CLASS_NOT_FOUNDclassPublicId unresolvable.
409ACADEMIC_CALENDAR_NOT_GENERATEDNo calendar row for this date.
422ATTENDANCE_DATE_OUTSIDE_SESSIONdate is outside the class's academic session range.

8.17 PUT /api/mobile/teacher/attendance

Purpose

Marks the teacher's own class's register — today only, and refused once completed, since correcting a closed day is the admin surface's job.

Source Evidence

teacher-attendance-portal.controller.ts:66-81; teacher-attendance-portal.service.ts:99-121; dto/teacher-attendance-portal.dto.ts:30-53.

Auth and Permissions

No @Permissions(); assertTeacherStaffRow plus assertOwnClass.

Request

Body: TeacherMarkAttendanceDto (§6.19) — identical to BulkMarkAttendanceDto minus force/forcedReason; sending either is a 400 via forbidNonWhitelisted.

Response

200, StudentAttendanceStateDto.

Side Effects

Identical to §8.2 — this route calls the exact same StudentAttendanceWriteService.bulkMark transaction with { refuseIfCompleted: true, requireToday: true }.

Error Cases

Same as §8.2, plus:

HTTP StatusError CodeCondition
403ATTENDANCE_NOT_CLASS_TEACHERNot this class's teacher.
422ATTENDANCE_DATE_NOT_TODAYdate is not today, school-local.
409ATTENDANCE_ALREADY_COMPLETEDThe register is already completed.
400(validation)force/forcedReason present in the payload.

Edge Cases

A teacher submitting exactly at the school-local midnight boundary is evaluated against SchoolClockService.now(), not the request's own clock.

8.18 POST /api/mobile/teacher/attendance/complete

Purpose

Completes the teacher's own class's register once every pupil is marked.

Source Evidence

teacher-attendance-portal.controller.ts:83-94; teacher-attendance-portal.service.ts:123-135.

Auth and Permissions

No @Permissions(); assertTeacherStaffRow plus assertOwnClass.

Request

Body: TeacherCompleteAttendanceDto (§6.19).

Response

200, StudentAttendanceStateDto with session.status: "completed".

Error Cases

Same as §8.4, plus 403 ATTENDANCE_NOT_CLASS_TEACHER.

8.19 GET /api/mobile/student/attendance

Purpose

A pupil's own attendance, date-ranged, for their own attendance-history screen.

Source Evidence

student-attendance-portal.controller.ts; student-attendance-portal.service.ts; dto/student-attendance-portal.dto.ts.

Auth and Permissions

  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: None — assertPortalAudience(actor, "student").
  • Takes no id parameter; the pupil is students.user_id = actor.id.

Request

Query: from, to (both required, @IsDateString).

Response

200, StudentAttendancePortalEntryDto[] (§6.19) — no markedBy, no staff identity.

{
  "success": true,
  "message": "Attendance fetched.",
  "data": [
    { "attendanceDate": "2026-09-13", "status": "present", "classLabel": "Grade 5 A (Morning)", "bsYear": 2083, "bsMonth": 5, "bsDay": 29, "note": null }
  ]
}

Error Cases

HTTP StatusError CodeCondition
403PERMISSION_INSUFFICIENTActive role's scopeKind is not student.
404STUDENT_NOT_FOUNDThe caller's own students row does not resolve.

Edge Cases

A date outside the Bikram Sambat converter's supported range (2000–2090 BS) returns bsYear/bsMonth/bsDay: 0/0/0 for that row rather than failing the page — unreachable in practice for any date attendance could have been recorded on.

8.20 GET /api/mobile/guardian/children/:studentId/attendance

Purpose

One child's attendance, date-ranged, as read by their guardian. Used by the guardian's per-child attendance screen.

Source Evidence

guardian-attendance-portal.controller.ts; guardian-attendance-portal.service.ts; dto/guardian-attendance-portal.dto.ts.

Auth and Permissions

  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: None — assertPortalAudience(actor, "guardian") plus a ward check.

Request

Params: studentId (uuid). Query: from, to.

Response

200, GuardianAttendancePortalEntryDto[] — same shape as §8.19, no markedBy.

Error Cases

HTTP StatusError CodeCondition
403PERMISSION_INSUFFICIENTActive role's scopeKind is not guardian.
404STUDENT_NOT_FOUNDstudentId is not this guardian's ward (identical response to a genuinely nonexistent id), or the child's graduate access has ended — an ended graduation answers identically to a non-ward, never a distinguishing status.

Edge Cases

A guardian with two children can never distinguish, from this route's response, whether a 404 means "wrong id" or "not your child" — that ambiguity is deliberate, so the id space cannot be probed as an enumeration oracle.

8.21 GET /api/mobile/student/attendance/summary

Purpose

A pupil's attendance totals for the current academic session, from the session start to today. Feeds the pupil dashboard.

Source Evidence

student-attendance-portal.controller.ts (mySummary); student-attendance-portal.service.ts (resolveOwnStudentId); portal/shared/portal-attendance-summary.service.ts; portal/shared/portal-attendance-summary.dto.ts; apps/api/src/modules/reports/portal/queries/portal-attendance.query.ts; apps/api/src/common/utils/cache-race.util.ts.

Auth and Permissions

  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: None — assertPortalAudience(actor, "student"). The handler key StudentAttendancePortalController.mySummary is in NO_PERMISSION_ADMIN_HANDLERS.
  • The route takes no id. The pupil is resolved from the token (students.user_id = actor.id, deleted_at only).

Request

No params, query or body.

Response

{
  "message": "Attendance summary fetched.",
  "data": {
    "academicSessionName": "2082/83",
    "from": "2026-04-14",
    "to": "2026-10-06",
    "expectedDays": 120,
    "marked": 118,
    "present": 104,
    "late": 6,
    "absent": 5,
    "excused": 3,
    "unmarked": 2,
    "ratePct": 93.22
  },
  "errorCode": null
}

Fields are described under Portal DTOs in §6.19.

Error Cases

HTTP StatusError CodeCondition
403PERMISSION_INSUFFICIENTActive role's scopeKind is not student.
404STUDENT_NOT_FOUNDThe caller has no live students row.

Edge Cases

  • The window ends at school-local today, never at the session's end date, so a required day that has not happened yet is not counted as expected. The end date is inclusive.
  • A pupil is expected only on days inside an enrolment span in the current session (enrolled_on <= day and ended_on null or after the day), so a mid-year transfer counts each class for its own days.
  • ratePct is null when marked is 0. unmarked counts expected days that have no mark yet, so a day the teacher has not registered is not treated as absent.
  • The totals are cached for 60 seconds per pupil and school-local date, so a mark made in the register can take up to 60 seconds to appear. See §11.

Example Requests

curl -X GET "$API_URL/api/mobile/student/attendance/summary" \
  -H "Authorization: Bearer TOKEN"

8.22 GET /api/mobile/guardian/children/:studentId/attendance/summary

Purpose

One child's attendance totals for the current academic session, as read by their guardian. Same payload as §8.21.

Source Evidence

guardian-attendance-portal.controller.ts (childSummary); guardian-attendance-portal.service.ts (resolveChildId, resolveWard); portal/shared/portal-attendance-summary.service.ts.

Auth and Permissions

  • Guard chain: JwtAuthGuard, RoleGuard.
  • Permission: None — assertPortalAudience(actor, "guardian") plus the ward check. The handler key GuardianAttendancePortalController.childSummary is in NO_PERMISSION_ADMIN_HANDLERS.
  • The ward check runs first; the cache is read only for an id that passed it.

Request

Params: studentId (uuid; a non-UUID value is a 400 from ParseUUIDPipe). No query or body.

Response

200, PortalAttendanceSummaryDto — the same shape as §8.21, with message: "Attendance summary fetched.".

Error Cases

HTTP StatusError CodeCondition
403PERMISSION_INSUFFICIENTActive role's scopeKind is not guardian.
404STUDENT_NOT_FOUNDstudentId is not this guardian's ward, does not exist, or the child's graduate access has ended. All three answer identically.

Edge Cases

  • The cache entry is keyed by pupil, not by caller, and is reachable only after identity is resolved, so the pupil route and the guardian route share one entry.
  • Windowing, expected-day and rate rules are those of §8.21.

Example Requests

curl -X GET "$API_URL/api/mobile/guardian/children/01922e2d-3c4d-7c3a-9d2e-1a2b3c4d5e6f/attendance/summary" \
  -H "Authorization: Bearer TOKEN"

9. Flow Diagrams

9.1 Route Ownership

9.2 Request Sequence — bulk mark (either surface)

9.3 Error Branch

EndpointPagination TypeDefault SizeMax SizeSort FieldsFiltersResult Cap
GET /attendance/students/:studentId/historyKeyset (cursor)20100changed_at DESC, id DESC (fixed)from, to (attendance date range)None beyond page size
GET /attendance/staff/:staffId/historyKeyset (cursor)20100changed_at DESC, id DESC (fixed)from, toNone beyond page size
GET /attendance/staffOffset (page/size)1/20100Fixed: name, then iddesignationIdPaginationUtil.MAX_PAGE_SIZE (100)

No endpoint in this module supports free-text search or client-chosen sort fields. The staff list computes its full roster and tally before slicing to a page — the tally therefore always reflects the whole roster, never just the visible page. Both history routes reject a malformed cursor with 400 PAGINATION_CURSOR_INVALID rather than silently restarting; a caller should treat that specific code as "start over from page one."

11. Caching, Jobs, and External Integrations

IntegrationUsed?DetailsSource
Redis cacheOnly the two portal summary routesportal:attendance-summary: keys, 60-second TTL, one entry per pupil and school-local date. The read goes through cacheGetFast, which races the Redis read against a 250 ms budget and treats a timeout, an error or an outage as a miss, so the route falls back to the database; the write is fire-and-forget (cacheSetFast). No writer invalidates the key: a register mark, a correction or a calendar edit is visible after at most 60 seconds. Every other route reads the database directly.portal/shared/portal-attendance-summary.service.ts; common/utils/cache-race.util.ts
BullMQYesQueueName.ATTENDANCE, two jobs (FIRST_PERIOD_REMINDER, ABSENCE_ALERT), one worker, ticked every minute by AttendanceScheduler. See backend doc §9.packages/jobs/src/index.ts:1029-1044, attendance-workers.module.ts
External APINoNotification delivery is internal to NotificationModule; no external provider is called directly by this module.—

13. Mandatory Deep API Documentation Pack

13.1 Route-by-Route Completeness Matrix

RouteController MethodDTOsService MethodGuardsPermissionsCacheJobsDB TouchesErrorsTestsDocumented?
GET /attendance/studentsStudentAttendanceController.getAttendanceGetStudentAttendanceQueryDto→StudentAttendanceStateDtoStudentAttendanceReadService.getStateJwt, RoleStudentAttendance_READN/AN/Asessions, records, roster tablesCLASS_NOT_FOUND, ATTENDANCE_DATE_OUTSIDE_SESSION, ACADEMIC_CALENDAR_NOT_GENERATEDstudent-attendance.integration.spec.tsYes
PUT /attendance/students.markBulkMarkAttendanceDto→StudentAttendanceStateDtoStudentAttendanceWriteService.bulkMarkJwt, RoleStudentAttendance_CREATE,_UPDATEN/AN/Asessions, records, log, runtimesee §8.2sameYes
PATCH /attendance/students/:recordId.correctPatchStudentAttendanceRecordDto→StudentAttendanceRecordDto.patchRecordJwt, RoleStudentAttendance_UPDATEN/AN/Arecords, logsee §8.3sameYes
POST /attendance/students/complete.completeCompleteAttendanceDto→StudentAttendanceStateDtoStudentAttendanceLifecycleService.completeJwt, RoleStudentAttendance_UPDATEN/AN/Asessionssee §8.4sameYes
POST /attendance/students/reopen.reopenReopenAttendanceDto→same.reopenJwt, RoleStudentAttendance_UPDATEN/AN/Asessionssee §8.5sameYes
POST /attendance/students/reset.resetResetAttendanceDto→same.resetJwt, RoleStudentAttendance_DELETEN/AN/Arecords, log, sessions, runtimesee §8.6sameYes
GET /attendance/students/:studentId/historyStudentAttendanceHistoryController.getHistoryStudentAttendanceHistoryQueryDto→AttendanceHistoryEntryDto[]AttendanceHistoryService.getHistoryJwt, RoleStudentAttendance_READN/AN/Achange logPAGINATION_CURSOR_INVALIDsameYes
GET /attendance/overviewAttendanceOverviewController.getOverviewAttendanceOverviewQueryDto→AttendanceOverviewRowDto[]AttendanceOverviewService.getOverviewJwt, RoleStudentAttendance_READN/AN/Aclasses, sessions, records, runtime, timetables, timeslotsnone specificsameYes
GET /attendance/staffStaffAttendanceController.listListStaffAttendanceQueryDto→StaffAttendanceListDtoStaffAttendanceService.listJwt, RoleStaffAttendance_READN/AN/Astaff, recordsnone specificstaff-attendance.integration.spec.tsYes
PUT /attendance/staff.bulkWriteBulkStaffAttendanceDto→list+tally.bulkWriteJwt, RoleStaffAttendance_CREATE,_UPDATEN/AN/Arecords, logsee §8.10sameYes
PATCH /attendance/staff/:recordId.patchPatchStaffAttendanceDto→StaffAttendanceRowDto.patchJwt, RoleStaffAttendance_UPDATEN/AN/Arecords, logsee §8.11sameYes
GET /attendance/staff/:staffId/historyStaffAttendanceHistoryController.historyStaffAttendanceHistoryQueryDto→entries.historyJwt, RoleStaffAttendance_READN/AN/Achange logPAGINATION_CURSOR_INVALIDsameYes
GET /attendance/settingsAttendanceSettingsController.listnone→AttendanceSettingsListDtoAttendanceSettingsService.listJwt, RoleAttendanceSettings_READN/AN/AsettingsATTENDANCE_SETTINGS_NOT_FOUNDattendance-settings.integration.spec.tsYes
PUT /attendance/settings/:kind.updateUpdateAttendanceSettingsDto→AttendanceSettingRowDto.updateJwt, RoleAttendanceSettings_UPDATEN/AN/Asettingssee §8.14sameYes
GET /mobile/teacher/attendance/classesTeacherAttendancePortalController.listMyClassesnone→TeacherClassSummaryDto[].listMyClassesJwt, Rolenone (audience)N/AN/Astaff, classesPERMISSION_INSUFFICIENTattendance-portal.integration.spec.tsYes
GET /mobile/teacher/attendance.getAttendanceTeacherAttendanceQueryDto→StudentAttendanceStateDto.getAttendanceJwt, Rolenone (audience)N/AN/A(delegates to admin read)ATTENDANCE_NOT_CLASS_TEACHERsameYes
PUT /mobile/teacher/attendance.markAttendanceTeacherMarkAttendanceDto→same.markAttendanceJwt, Rolenone (audience)N/AN/A(delegates to admin write)see §8.17sameYes
POST /mobile/teacher/attendance/complete.completeAttendanceTeacherCompleteAttendanceDto→same.completeAttendanceJwt, Rolenone (audience)N/AN/A(delegates to admin lifecycle)see §8.18sameYes
GET /mobile/student/attendanceStudentAttendancePortalController.myAttendanceStudentAttendancePortalQueryDto→entries.myAttendanceJwt, Rolenone (audience)N/AN/Astudents, recordsSTUDENT_NOT_FOUNDsameYes
GET /mobile/guardian/children/:studentId/attendanceGuardianAttendancePortalController.childAttendanceGuardianAttendancePortalQueryDto→entries.childAttendanceJwt, Rolenone (audience)N/AN/Aguardians, students, recordsSTUDENT_NOT_FOUNDsameYes
GET /mobile/student/attendance/summaryStudentAttendancePortalController.mySummaryPortalAttendanceSummaryDtoStudentAttendancePortalService.resolveOwnStudentId, PortalAttendanceSummaryService.forStudentJwt, Rolenone (audience)portal:attendance-summary: (60 s)N/Astudents, academic_sessions, academic_calendar_days, student_class_enrollments, student_attendance_sessions/recordsSTUDENT_NOT_FOUNDportal-attendance-summary specsYes
GET /mobile/guardian/children/:studentId/attendance/summaryGuardianAttendancePortalController.childSummaryPortalAttendanceSummaryDtoGuardianAttendancePortalService.resolveChildId, PortalAttendanceSummaryService.forStudentJwt, Rolenone (audience)portal:attendance-summary: (60 s)N/Aguardians, students, users, academic_sessions, academic_calendar_days, student_class_enrollments, student_attendance_sessions/recordsSTUDENT_NOT_FOUNDportal-attendance-summary specsYes

13.2 Request/Response Exhaustiveness

Covered per-endpoint in §8 above — every mutating route includes a minimal and (where the DTO has optional fields) a full example; every route documents its success response, its representative validation/domain errors, and (where applicable) its empty-result behavior.

13.3 API Diagram Pack

See §9 for the route-ownership graph, the bulk-mark sequence diagram, and the shared error decision tree — the same three shapes apply, with only the actor and the entry guard changing, across every mutating route in this module (admin PUT/PATCH/POST and the teacher portal's PUT/POST).

13.4 Consumer Integration Notes

ConsumerRequired KnowledgeFailure HandlingContract Stability
Admin panelexpectedVersion/submissionId on every mutating student route; force/forcedReason for holidays; StudentAttendance_DELETE is a distinct permission from _UPDATE for reset.Show ATTENDANCE_VERSION_CONFLICT as "someone else changed this — reload"; show ATTENDANCE_NOT_A_SCHOOL_DAY with a force-confirm dialog.Stable.
Teacher-facing web/mobile clientThe portal write is today-only and pre-completion-only; there is no force field to send.On ATTENDANCE_DATE_NOT_TODAY/ATTENDANCE_ALREADY_COMPLETED, direct the user to the admin surface rather than retrying.Stable.
Pupil/guardian-facing clientNo marked_by field exists in either portal response — do not design a UI element that assumes one.A 404 from the guardian route means either a bad id or a non-ward; treat both identically.Stable.
QABoth history routes require the id tiebreaker to test pagination correctly — construct a bulk mark of several pupils to reliably produce a changed_at tie.Reproduce the append-only trigger by attempting a direct UPDATE/DELETE on attendance_change_log and expecting SQLSTATE 2F002.Stable.
Internal service (workers)Both sweeps are idempotent per tick via a guarded UPDATE ... WHERE ... IS NULL; neither throws on a domain skip.A processor exception should be treated as retryable (BullMQ's own retry semantics apply); a logged skip is not a failure.Stable.

13.5 API Tradeoffs and Rationale

DecisionChosen BehaviorAlternatives ConsideredWhy This TradeoffRiskMitigation
Pagination style (history)Keyset (cursor)Offset (page/size)An offset page over a table that grows continuously (the change log) shifts under a concurrent write; keyset is stable across inserts.A client must persist the opaque cursor rather than compute page N directly.nextCursor is always returned; there is no "jump to page N" use case for a history feed.
Pagination style (staff list)Offset (page/size)KeysetThe staff roster for one date is bounded and effectively static within a request; offset is simpler and the tally needs the whole roster anyway.None material at staff-roster scale.—
Auth mode (admin vs. portal)Permission-gated admin routes; audience-asserted, permission-free portal routesGrant a scoped permission to teacher/student/guardian rolesThe seeded roles' scopeKind: "all" and TEACHER_PERMISSIONS = STAFF_PERMISSIONS reference make any such grant unscoped in practice.A future change to the seeded role shape could silently widen a portal-equivalent permission — mitigated by the module never granting one at all.The dual role.guard.ts/route-permissions.spec.ts allowlist.
Error shapeExisting envelope (success/message/data, errorCode inside the thrown exception body)A dedicated attendance error envelopeConsistency with every other module in this API.None.—
Idempotency (student bulk write)Client-generated submissionId, checked before the version checkRely on natural upsert idempotency aloneA naturally-idempotent upsert still bumps version, breaking a genuinely concurrent second edit's version check.None material — the extra column and read are cheap.—
Idempotency (staff bulk write)None — last-write-winsThe same submissionId mechanism as studentsStaff attendance is single-office, low-contention; the machinery was judged not worth it.A genuine double-submit from the same office silently reapplies (harmlessly, since the entries are identical) or, from two different offices, the later one wins.Both writes are individually logged for audit.
Backward compatibilityNot yet exercised — this is the module's first release————

13.6 API Change Impact

ChangeAffected ConsumersBackend ImpactData ImpactMigration Needed?Compatibility Plan
Adding a fifth attendance_status valueEvery DTO enumerating ATTENDANCE_STATUS, both notification templatesEnum migration, ATTENDANCE_STATUS const update in attendance.types.tsExisting rows unaffected; new status usable going forwardYes — Postgres enum ALTER TYPE ... ADD VALUEAdditive; no removal, so old clients simply never send the new value.
Adding a channel to a notification kind's settings rowUpdateAttendanceSettingsDto, both processorsUpdate attendance_notification_settings_channels_array CHECK if the max-4 cap needs raisingNone to existing rowsPossibly (CHECK bound)Document the new channel in the settings screen before enabling it by default.
Renaming a StudentAttendance_* permission codeAdmin panel's permission list, activity-log filter (ACTIVITY_MODULES in the frontend repo)Update permission-catalog.ts, re-run permissions:syncExisting role grants keyed on the old string name would need remappingYes — a data migration over role_permissionsRequires a coordinated frontend change; see the cross-repo activity-modules-parity rule in this repo's CLAUDE.md.

14. Zero-Omission API Checklist

  • Every controller route is documented (§4, §8).
  • Every parent route prefix and runtime URL is documented (§4).
  • Every DTO field, nested field, enum, default, and validator is documented (§6, §7).
  • Every response field, nullable field, and generated field is documented (§6.6–§6.19).
  • Every auth, guard, permission, and audience-assertion branch is documented (§5, §8).
  • Every success, validation, permission, not-found, conflict, and server-error branch is documented (§8's Error Cases tables).
  • Every database touch, and the absence of any cache/queue/notification side effect where true, is documented (§8's Side Effects, §11).
  • Every route has minimal/full-request and success/error examples where a body applies (§8).
  • Every endpoint family has route, sequence, and error diagrams (§9).
  • Every tradeoff is documented (§13.5).
  • The API doc links to backend and features/flows (below).

15. Integration Checklist

  • Every route from controllers is documented.
  • Every DTO field is documented.
  • Every enum value is documented.
  • Every response envelope is documented.
  • Every error code is documented.
  • Every auth guard and permission (or its deliberate absence) is documented.
  • Every cache key (none), queue job, and external call is documented.
  • Every diagram matches the current code.
  • The API doc links to backend and features/flows.

See Also

  • Backend doc: /docs/developer/attendance/backend
  • Features and flows doc: /docs/developer/attendance/feature

On this page

Attendance - API Reference1. Documentation Evidence2. Module Summary3. Concepts and Terminology4. API Surface Map5. Auth, Identity, and Permissions6. DTO and Model Reference6.1 MarkAttendanceEntryDto (nested request)6.2 BulkMarkAttendanceDto (request, admin PUT)6.3 GetStudentAttendanceQueryDto (query, GET .../students)6.4 PatchStudentAttendanceRecordDto (request, PATCH .../:recordId)6.5 CompleteAttendanceDto / ReopenAttendanceDto / ResetAttendanceDto (requests)6.6 StudentAttendanceRosterEntryDto (response, nested)6.7 AttendanceTallyDto (response, nested)6.8 StudentAttendanceSessionDto / AttendanceCalendarDayDto (response, nested)6.9 StudentAttendanceStateDto (response, the shared envelope)6.10 StudentAttendanceRecordDto (response, PATCH .../:recordId)6.11 StudentAttendanceHistoryQueryDto / AttendanceHistoryEntryDto6.12 AttendanceOverviewQueryDto / AttendanceOverviewRowDto6.13 ListStaffAttendanceQueryDto6.14 StaffAttendanceRowDto / StaffAttendanceTallyDto / StaffAttendanceListDto (response)6.15 StaffAttendanceEntryDto / BulkStaffAttendanceDto (request, PUT /attendance/staff)6.16 PatchStaffAttendanceDto (request, PATCH /attendance/staff/:recordId)6.17 StaffAttendanceHistoryQueryDto / StaffAttendanceHistoryEntryDto6.18 AttendanceSettingRowDto / AttendanceSettingsListDto / UpdateAttendanceSettingsDto6.19 Portal DTOs7. Enum Reference8. Endpoint Reference8.1 GET /api/attendance/studentsPurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge CasesExample Requests8.2 PUT /api/attendance/studentsPurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge CasesExample Requests8.3 PATCH /api/attendance/students/:recordIdPurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge CasesExample Requests8.4 POST /api/attendance/students/completePurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge Cases8.5 POST /api/attendance/students/reopenPurposeSource EvidenceAuth and PermissionsRequestResponseError Cases8.6 POST /api/attendance/students/resetPurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge Cases8.7 GET /api/attendance/students/:studentId/historyPurposeSource EvidenceAuth and PermissionsRequestResponseError CasesEdge Cases8.8 GET /api/attendance/overviewPurposeSource EvidenceAuth and PermissionsRequestResponseError CasesEdge Cases8.9 GET /api/attendance/staffPurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsEdge Cases8.10 PUT /api/attendance/staffPurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge Cases8.11 PATCH /api/attendance/staff/:recordIdPurposeSource EvidenceAuth and PermissionsRequestResponseError Cases8.12 GET /api/attendance/staff/:staffId/historyPurposeSource EvidenceAuth and PermissionsRequestResponseError Cases8.13 GET /api/attendance/settingsPurposeSource EvidenceAuth and PermissionsResponseError Cases8.14 PUT /api/attendance/settings/:kindPurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge Cases8.15 GET /api/mobile/teacher/attendance/classesPurposeSource EvidenceAuth and PermissionsResponseError Cases8.16 GET /api/mobile/teacher/attendancePurposeSource EvidenceAuth and PermissionsRequestResponseError Cases8.17 PUT /api/mobile/teacher/attendancePurposeSource EvidenceAuth and PermissionsRequestResponseSide EffectsError CasesEdge Cases8.18 POST /api/mobile/teacher/attendance/completePurposeSource EvidenceAuth and PermissionsRequestResponseError Cases8.19 GET /api/mobile/student/attendancePurposeSource EvidenceAuth and PermissionsRequestResponseError CasesEdge Cases8.20 GET /api/mobile/guardian/children/:studentId/attendancePurposeSource EvidenceAuth and PermissionsRequestResponseError CasesEdge Cases8.21 GET /api/mobile/student/attendance/summaryPurposeSource EvidenceAuth and PermissionsRequestResponseError CasesEdge CasesExample Requests8.22 GET /api/mobile/guardian/children/:studentId/attendance/summaryPurposeSource EvidenceAuth and PermissionsRequestResponseError CasesEdge CasesExample Requests9. Flow Diagrams9.1 Route Ownership9.2 Request Sequence — bulk mark (either surface)9.3 Error Branch10. Pagination, Sorting, Filtering, and Search11. Caching, Jobs, and External Integrations13. Mandatory Deep API Documentation Pack13.1 Route-by-Route Completeness Matrix13.2 Request/Response Exhaustiveness13.3 API Diagram Pack13.4 Consumer Integration Notes13.5 API Tradeoffs and Rationale13.6 API Change Impact14. Zero-Omission API Checklist15. Integration ChecklistSee Also