Exact wire contract for every screen → endpoint. Base /api/v1; envelope per
00-shared/07 . All endpoints from attendance.controller.ts, biometric.controller.ts,
reports.controller.ts, dashboard.controller.ts; business rules from the services.
Global guard chain: RateLimitGuard → JwtAuthGuard → RbacGuard (app.module.ts:129-131);
attendance controller adds no per-endpoint @Permissions (OQ-5).
Aspect Contract
Base https://api.<domain>/api/v1
Headers Authorization: Bearer <accessToken>; x-request-id; Content-Type: application/json; Idempotency-Key (UUID) on offline writes
Success {success,message,data,meta?,timestamp,requestId}
Error {success,message,error:{code,details?},timestamp,requestId}
Tenancy tenantId from JWT claim — never in body (00-shared/07 §6 )
Caching attendance lists: last-good cache, SWR, TTL 5 min (00-shared/06 §3.3 )
Offline reads cached; writes queued (this module defines an offline queue — 00-shared/06 §3.7 )
Retry backoff on 5xx/network; no auto-retry on 429
Pagination note (OQ-4): none of the attendance GETs paginate — data is a bare
array, no meta. Client strategy: treat as full lists; history views page client-side.
Endpoint POST /attendance (attendance.controller.ts:25-27)
Request MarkAttendanceDto: {studentId, classId, date('YYYY-MM-DD'), status, checkIn?, checkOut?, source?, remarks?} (mark-attendance.dto.ts:11-49)
Success 201 → data = attendance doc (_id, studentId,classId,date,status,source,checkIn?,checkOut?,remarks?,markedBy?,version,createdAt,updatedAt)
Business idempotent upsert by (tenantId, studentId, date) (attendance.repository.ts:17-37); emits AttendanceMarked {studentId,status,date} (attendance.service.ts:37-44); unique index attendance.schema.ts:59-62
Errors 400 VALIDATION_ERROR (invalid status/date/ids — e2e p1-operations.e2e-spec.ts:222-228); 404 upstream refs; 409 race; 429; 5xx
Source attendance.service.ts:24-46
Offline queue locally; flush with Idempotency-Key
Endpoint POST /attendance/bulk (attendance.controller.ts:28-30)
Request BulkMarkAttendanceDto {records: MarkAttendanceDto[]} (mark-attendance.dto.ts:51-54)
Success data = array of docs, order preserved; one AttendanceMarked per record (attendance.service.ts:48-54)
Errors 400 if any record invalid (sequential loop throws on first bad record — earlier records may already be saved, OQ-2); 409/429/5xx
Source attendance.service.ts:48-54; END_TO_END_USER_FLOWS.md:269-270,288
Offline flush in chunks ≤ 100 records
Endpoint GET /attendance/class/:classId?date=YYYY-MM-DD (attendance.controller.ts:31-36)
Success data = array of docs for that class+date (empty if none)
Errors 400 missing date; 404 cross-tenant class; 429
Source attendance.service.ts:56-61; attendance.repository.ts:39-44; e2e p1-operations.e2e-spec.ts:211-220
Note used per day; S4 month = 1 call/day (cache; OQ-4)
Endpoint GET /attendance/student/:studentId?startDate&endDate (both optional) (attendance.controller.ts:37-43)
Success data = array of docs, date-range filtered (unfiltered = full history)
Errors 404 cross-tenant; 429
Source attendance.service.ts:63-73; attendance.repository.ts:46-58; END_TO_END_USER_FLOWS.md:324-325,348,378-379,401
Note parent/student gating open (OQ-7)
Endpoint GET /attendance/summary?classId&startDate&endDate (all required) (attendance.controller.ts:44-50)
Success data = {total, summary:{status:count}} (attendance.service.ts:103-113)
Errors 400 missing params; 429
Source attendance.service.ts:103-113
Note in-memory aggregation (OQ-8)
Endpoint GET /attendance/:id (attendance.controller.ts:51-53)
Success data = doc
Errors 404 RESOURCE_NOT_FOUND "Attendance record not found." (attendance.service.ts:77)
Source attendance.service.ts:75-79
Endpoint PATCH /attendance/:id (attendance.controller.ts:54-59)
Request UpdateAttendanceDto {status?, checkIn?, checkOut?, remarks?} (update-attendance.dto.ts:4-22)
Success data = updated doc (version +1, base.repository.ts:57-66); emits AttendanceUpdated {attendanceId, studentId, changes:[keys]} (attendance.service.ts:88-99)
Errors 404 missing; 400; OQ-6 (no enum validation on status — client restricts); 429
Source attendance.service.ts:81-101
Note changes keys → UI "what changed" toast; audit trail via event map (event-queue-map.ts:18-21)
Endpoint POST /biometric/ingest (biometric.controller.ts:14-18)
Request CreateBiometricLogDto {studentId, deviceId, timestamp, mode?} (create-biometric-log.dto.ts:4-21)
Success data = raw log doc (biometric-log.schema.ts:7-27) — stored only, not applied to attendance (OQ-3)
Source biometric.service.ts:14-16
Note biometric-sync cron */15 (scheduler.service.ts:71-76) has no worker today; deviceId unvalidated against biometric_devices (biometric-device.schema.ts:15-31)
Endpoint POST /reports/generate (reports.controller.ts:14-18)
Request {type:'attendance_summary', classId?, startDate?, endDate?} (report-job.schema.ts:8-9)
Success data = {jobId, status:'queued'} (reports.service.ts:28-44)
Poll GET /reports/:jobId → `data = {type, status(queued
Errors 404 unknown job; 400 bad type; 429
Source reports.service.ts:110-137; report.worker.ts:7
Note teacher role lacks report.* (role.schema.ts:31) — admin-only today (OQ)
Endpoint GET /dashboard/overview (dashboard.service.ts:18-71)
KPI attendance: {total, summary, rate} — rate = round(present/total × 100) (dashboard.service.ts:57-64)
Tap deep link to S2 for today's class
Item Source
AttendanceMarked / AttendanceUpdated → queue attendance-process, job process-attendanceevent-queue-map.ts:14-21
attendance-process worker: absence watch (all absent docs ≥ 3 → log), bulk-import branchattendance.worker.ts:16,45-54,57-102
Repeatable attendance-report-daily 07:00 UTC → report-generate scheduler.service.ts:99-104; attendance-report.job.ts:13-24
Repeatable biometric-sync */15 → biometric-sync queue (no worker) scheduler.service.ts:71-76; queue.constants.ts:7
Permissions available attendance.mark, attendance.edit (permissions.constants.ts:29-30); report.generate, report.read (39-40); biometric.log.create/read, biometric.device.manage (41-43)
Code Attendance-specific UX
400 VALIDATION_ERROR field errors (§08_Form_Specifications.md §6 ); bulk → pre-validate client-side
401 UNAUTHENTICATED refresh once; else session expiry flow (00-shared/06 §5 )
403 PERMISSION_DENIED hide grid actions; batch bar disabled
404 RESOURCE_NOT_FOUND refresh roster; "record no longer exists" state
409 DUPLICATE_RESOURCE grid refresh (upsert race); offline flush collision preview
422 BUSINESS_RULE_VIOLATION context banner (not currently emitted by attendance service — future use)
429 RATE_LIMITED backoff, no auto-retry, countdown
5xx generic + requestId; retry offered