The Academy
docs/academy/.System Atlas
Teacher Journey — 50 Endpoints
Complete API reference grouped by domain. Every endpoint with real request/response examples.
Register Account
Create a teacher account. Returns tokens immediately but email must be verified before login.
Request / Response
{
"fullName": "Ahmed Hassan",
"email": "ahmed@gmail.com",
"username": "ahmed_h",
"password": "Teacher@123",
"phoneNumber": "+201012345678",
"dateOfBirth": "1990-05-15T00:00:00",
"role": "Teacher"
}
{
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJl...",
"expiresAt": "2026-09-12T12:15:00Z",
"role": "Teacher",
"userId": "b0000000-0000-00:00:00-000000000001"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 10 requests/minute.
Auth: None (public).
Response Fields: nationalId, subscriptionPlan (enum string: "Free"/"Pro"/"Premium"), subscriptionEnd (nullable DateTime), bio (nullable), rating (byte 0-5), subjectName (from linked Subject).
Errors: 401 (no/invalid token), 403 (wrong role).
Enums:
·
role: "Teacher" | "Student" | "Parent" — Admin cannot self-register.Validation:
·
fullName: required, max 100 chars.·
email: required, valid format, must be unique.·
username: required, 3-50 chars, must be unique.·
password: required, min 8 chars.·
phoneNumber: required, E.164 format regex ^\+?\d{7,15}$.·
dateOfBirth: required, must be in the past.Errors: 400 (validation), 409 (duplicate email/username).
Verify Email
Enter the 6-digit numeric code sent to your email. Code expires in 2 minutes.
Request / Response
{ "email": "ahmed@gmail.com", "code": "482916" }
{ "data": { "message": "success.emailVerified" }, "meta": { "timestamp": "...", "requestId": "..." } }
Rate Limit: 10 requests/minute per IP.
Validation:
·
email: required, valid format, must match a pending verification.·
code: required, exactly 6 numeric digits (^[0-9]{6}$).Errors: 400 (validation), 401 (expired/invalid code — code expires in 2 minutes).
Resend: Call POST /auth/resend-verification with { "email": "..." } to get a new code.
Login
Authenticate with email + password. Returns JWT access (15min) + refresh (7 days) tokens.
Request / Response
{ "email": "ahmed@gmail.com", "password": "Teacher@123" }
{
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJl...",
"expiresAt": "2026-09-12T12:15:00Z",
"role": "Teacher",
"userId": "b0000000-0000-0000-0000-000000000001"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 10 requests/minute per IP.
Validation:
·
email: required, valid format.·
password: required, min 6 chars (note: registration requires 8, login only 6 for backward compat).Errors: 400 (validation), 401 (bad credentials OR unverified email).
Token Lifecycle: Access token = 15 minutes. Refresh token = 7 days. Use Bearer token in Authorization header for all subsequent requests. Refresh via POST /auth/refresh-token when access expires.
Get My Profile
View your full teacher profile including subscription status.
Request / Response
{
"data": {
"id": "b0000000-0000-0000-0000-000000000001",
"fullName": "Ahmed Hassan",
"email": "ahmed@gmail.com",
"username": "ahmed_h",
"phoneNumber": "+201012345678",
"dateOfBirth": "1990-05-15T00:00:00",
"nationalId": "29005151234567",
"subscriptionPlan": "Premium",
"subscriptionEnd": "2027-09-12T00:00:00Z",
"bio": "Biology teacher with 10 years experience",
"rating": 5,
"subjectName": "Biology",
"createdAt": "2026-01-15T00:00:00Z"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Query: search (optional, matches name/email), filter ("my-students" default | "available"), page (default 1), pageSize (default 20, max 100).
Behavior: the default "my-students" lists pending + active memberships in your groups (never platform-wide students). "available" lists students with no membership in your groups. Pending students report
isEnrolled: false and zeroed metrics.Errors: 401 (no token), 403 (wrong role), 400 (invalid filter/paging).
Update My Profile
Update your name, phone, bio, and subscription plan.
Request / Response
{
"fullName": "Ahmed Hassan",
"phoneNumber": "+201012345678",
"bio": "Biology teacher — 10 years experience",
"subscriptionPlan": "Premium"
}
Rate Limit: 30 req/min.
Validation: fullName (required, max 100), phoneNumber (required, E.164 regex). Email change triggers re-verification (old email stays active until new one is verified).
Errors: 400 (validation), 401 (no token), 403 (wrong role), 409 (duplicate email if changed).
Teacher Dashboard
Full dashboard: profile, overview stats, week-over-week trends, top/bottom groups, recent uploads, top students, platform comparison, students at risk. Fixed-size summary endpoint — no pagination. Cached 2 minutes.
Request / Response
{
"data": {
"profile": { "fullName": "Ahmed Hassan", "email": "ahmed@gmail.com", "subject": "Biology", "subscriptionPlan": "Premium" },
"overview": {
"totalGroups": 8, "activeGroups": 6, "totalStudents": 142,
"totalSessionsThisMonth": 18, "pendingPaymentsEGP": 3500.00,
"totalUploadsThisMonth": 12, "scoreTrend": 4.5,
"topPerformingGroup": "Biology - Secondary 2", "topPerformingGroupScore": "88.5%",
"studentsAtRiskCount": 5, "uploadStreakWeeks": 6,
"classAverageScore": 76.3, "totalSessionsAllTime": 234
},
"trends": {
"scoreChangePercent": 5.2, "attendanceChangePercent": 3.1,
"studentsChange": 8, "scoreTrend": "up", "attendanceTrend": "up"
},
"topGroups": {
"top": [
{ "groupId": "...", "name": "Biology - Secondary 2", "subject": "Biology", "gradeLevel": "SecondaryTwo", "period": "First Term", "studentCount": 45, "averageScore": 88.5, "isActive": true, "attendanceRate": 94.0, "trend": "up" }
],
"bottom": [
{ "groupId": "...", "name": "Math - Secondary 1", "subject": "Mathematics", "gradeLevel": "SecondaryOne", "period": "First Term", "studentCount": 22, "averageScore": 54.2, "isActive": true, "attendanceRate": 72.0, "trend": "down" }
]
},
"recentUploads": [
{ "uploadId": "...", "fileName": "session_2026-09-10.xlsx", "groupName": "Biology - Secondary 2", "status": "Completed", "rowsProcessed": 45, "uploadedAt": "2026-09-12T14:30:00Z" }
],
"topStudents": [
{ "gradeLevel": "SecondaryTwo", "groupName": "Biology - Secondary 2", "students": [
{ "studentId": "...", "fullName": "Ali Hassan", "studentCode": "STU-003", "rank": 1, "avgScore": 95.5, "attendanceRate": 100.0 }
]}
],
"unreadNotifications": 3,
"platformComparison": { "yourAverageScore": 76.3, "schoolAverageScore": 71.0, "yourRank": 3, "totalTeachers": 15, "performanceLabel": "Above Average" }
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Caching: IMemoryCache, 2 minutes TTL. Cache key:
teacher_home_{userId}.Response Fields: profile, overview (incl. studentsAtRiskCount), trends (week-over-week), topGroups (top 2 + bottom 2 by average score), recentUploads (last 3 uploads with status), topStudents (grouped by grade level, top 3 per grade from best group), unreadNotifications, platformComparison (nullable).
No pagination — this is a fixed-size summary endpoint.
Errors: 401 (no token), 403 (wrong role).
Teacher Analytics & Charts Dashboard
Dedicated analytics endpoint returning rich datasets for Flutter charts: score timelines (line/area), attendance timelines (stacked bar), score distribution brackets (histogram/donut), student health status (gauge/pie), group performance comparisons (multi-bar), assessment category breakdowns (polar), grade level distribution, and platform benchmarks with multi-criteria filtering.
Request / Response
?groupId=...&gradeLevel=SecondaryTwo&period=First%20Term&timeRange=30d&search=Ali
{
"data": {
"filterApplied": {
"groupId": null, "groupName": null, "gradeLevel": null,
"period": "First Term", "timeRange": "30d",
"dateFrom": "2026-08-20T00:00:00Z", "dateTo": "2026-09-19T23:59:59Z", "search": null
},
"overview": {
"totalGroups": 6, "activeGroups": 5, "totalStudents": 145,
"totalSessionsInPeriod": 24, "averageClassScore": 78.4,
"overallAttendanceRate": 89.6, "pendingPaymentsEGP": 3850.00,
"studentsAtRiskCount": 4, "uploadStreakWeeks": 7
},
"trends": { "scoreChangePercent": 4.8, "attendanceChangePercent": 2.1, "studentsChange": 12, "scoreTrend": "up", "attendanceTrend": "up" },
"scoreTimelineChart": [
{ "date": "2026-08-25T10:00:00Z", "label": "Aug 25", "sessionTitle": "Session 1", "groupName": "Physics - Sec 2", "averageScore": 75.5, "highestScore": 98.0, "lowestScore": 45.0, "attendanceRate": 92.0 }
],
"attendanceTimelineChart": [
{ "date": "2026-08-25T10:00:00Z", "label": "Aug 25", "sessionTitle": "Session 1", "groupName": "Physics - Sec 2", "attendedCount": 46, "absentCount": 4, "totalStudents": 50, "attendanceRate": 92.0 }
],
"scoreDistribution": {
"excellent": { "count": 35, "percentage": 24.1, "range": "85% - 100%" },
"veryGood": { "count": 52, "percentage": 35.9, "range": "75% - 84.9%" },
"good": { "count": 38, "percentage": 26.2, "range": "65% - 74.9%" },
"pass": { "count": 16, "percentage": 11.0, "range": "50% - 64.9%" },
"struggling": { "count": 4, "percentage": 2.8, "range": "< 50%" },
"totalEvaluated": 145
},
"studentHealthStatus": {
"excelling": { "count": 35, "percentage": 24.1, "label": "Excelling" },
"onTrack": { "count": 82, "percentage": 56.6, "label": "On Track" },
"needsPush": { "count": 24, "percentage": 16.5, "label": "Needs Push" },
"atRisk": { "count": 4, "percentage": 2.8, "label": "At Risk" }
},
"groupPerformanceComparison": [
{ "groupId": "...", "groupName": "Physics - Sec 2 (A)", "gradeLevel": "SecondaryTwo", "studentCount": 48, "averageScore": 84.2, "attendanceRate": 94.5, "sessionsCount": 8, "trend": "up" }
],
"assessmentCategoryBreakdown": [
{ "category": "Exams", "averageScore": 79.5, "totalAssessments": 145 }
],
"gradeLevelDistribution": [
{ "gradeLevel": "SecondaryTwo", "groupsCount": 3, "studentsCount": 90, "averageScore": 81.0 }
],
"platformComparison": { "yourAverageScore": 78.4, "schoolAverageScore": 72.0, "yourRank": 3, "totalTeachers": 18, "performanceLabel": "Above Average" },
"topStudents": { "items": [], "page": 1, "pageSize": 10, "totalCount": 0 },
"recentUploads": []
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Caching: IMemoryCache, 2 minutes TTL. Cache key:
dashboard:teacher:{userId}:{filterHash}.Filters:
groupId, gradeLevel, period, timeRange (7d, 30d, 90d, term, all, custom), dateFrom, dateTo, search, studentsPage, studentsPageSize (paginate the topStudents list; charts stay fixed).Flutter Visuals: Formatted for LineChart, BarChart, Donut/Pie, Radar, and Gauge widgets.
Students Needing Attention
At-risk students (score < 50% or attendance < 60%) with failure reasons, missed sessions, and parent contact. Opened from the risk badge on Home / Dashboard.
Request / Response
?groupId=...&gradeLevel=SecondaryTwo&reason=low_score&search=Omar&page=1&pageSize=20
{
"data": {
"items": [
{ "studentId": "...", "fullName": "Omar Khaled", "studentCode": "STU-042", "groupId": "...", "groupName": "Physics - Sec 2 (A)", "gradeLevel": "SecondaryTwo", "reason": "Low score & Low attendance", "reasonCode": "low_score_low_attendance", "currentScore": 42.5, "attendanceRate": 0.4, "missedSessionsCount": 3, "parentName": "Khaled Hassan", "parentPhoneNumber": "+201012345678" }
],
"page": 1, "pageSize": 20, "totalCount": 4
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Filters: groupId, gradeLevel, reason (low_score | low_attendance | low_score_low_attendance, case-insensitive — unknown values and display labels return 400), search; sorted worst-first (lowest score, then attendance).
Validation: page ≥ 1, pageSize 1–100 — violations return 400 ProblemDetails.
Flutter: render parentPhoneNumber as a tel: action button; lists arrive pre-sorted, don't re-sort client-side. Display
reason (localized), but send back reasonCode as the filter — the display text is not a valid filter value.
Create Group
Define group name, grade level, subject, period, and optional weekly schedules.
Request / Response
{
"name": "Biology - Secondary 2",
"academicYear": "2025-2026",
"gradeLevel": "SecondaryTwo",
"period": "First Term",
"subjectId": "a1b2c3d4-5678-9abc-def0-1234567890ab",
"schedules": [
{ "dayOfWeek": "Monday", "startTime": "14:00", "endTime": "16:00" },
{ "dayOfWeek": "Wednesday", "startTime": "14:00", "endTime": "16:00" }
]
}
{
"data": {
"id": "c5d6e7f8-9abc-def0-1234-567890abcdef",
"name": "Biology - Secondary 2",
"academicYear": "2025-2026",
"gradeLevel": "SecondaryTwo",
"period": "First Term",
"isActive": true,
"teacherId": "b0000000-0000-0000-0000-000000000001",
"subjectName": "Biology",
"createdAt": "2026-09-12T10:00:00Z",
"studentCount": 0, "averageScore": 0, "attendanceRate": 0, "activeSessions": 0,
"schedules": [
{ "id": "...", "dayOfWeek": "Monday", "startTime": "14:00", "endTime": "16:00", "isActive": true }
]
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Enums:
·
gradeLevel: 1=SecondaryOne, 2=SecondaryTwo, 3=SecondaryThree. Must be integer, not string.·
period: "First Term" | "Second Term". Restricted by CHECK constraint in DB.·
dayOfWeek (in schedules): "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday".Validation: name (required, max 200 chars), academicYear (required, e.g. "2025-2026"), subjectId (required, must be valid GUID).
Errors: 400 (validation), 401 (no token), 403 (wrong role), 404 (subject not found).
List My Groups
Paginated list of all your groups with stats.
Request / Response
gradeLevel: string? (filter by grade) page: int = 1 pageSize: int = 20
{
"data": {
"items": [
{
"id": "c5d6e7f8-...", "name": "Biology - Secondary 2",
"academicYear": "2025-2026", "gradeLevel": "SecondaryTwo",
"period": "First Term", "isActive": true,
"subjectName": "Biology", "studentCount": 50,
"averageScore": 88.5, "attendanceRate": 94.0,
"activeSessions": 3, "lastUploadDate": "2026-09-12T14:30:00Z",
"trend": "up",
"schedules": [
{ "id": "...", "dayOfWeek": "Monday", "startTime": "14:00", "endTime": "16:00", "isActive": true }
]
}
],
"page": 1, "pageSize": 20, "totalCount": 5
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Get Group Detail
Single group with full details including schedules.
Request / Response
Rate Limit: 30 req/min.
Errors: 401 (no token), 403 (not your group), 404 (group not found).
Update Group
Update group name, grade and period. Subject is set at creation and cannot be changed. Schedules are managed separately.
Request / Response
{
"name": "Biology - Secondary 2 (Updated)",
"academicYear": "2025-2026",
"gradeLevel": "SecondaryTwo",
"period": "Second Term"
}
Delete Group
Soft-delete: sets IsActive = false. Preserves all data.
Request / Response
{ "data": { "message": "Group deleted successfully" }, "meta": { "timestamp": "...", "requestId": "..." } }
Rate Limit: 30 req/min.
Behavior: Soft-delete — sets IsActive = false. All sessions, students, and data are preserved. Group disappears from default list queries but can still be accessed by ID.
Errors: 401 (no token), 403 (not your group), 404 (group not found).
Duplicate Group
Clone a group with its schedules. Students are NOT copied.
Request / Response
{ "name": "Biology - Secondary 2 (Copy)" }
{
"data": {
"id": "...", "name": "Biology - Secondary 2 (Copy)",
"academicYear": "2025-2026", "gradeLevel": "SecondaryTwo",
"period": "First Term", "subjectId": "a1b2c3d4-..."
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Behavior: Clones group metadata + schedules. Does NOT copy students, sessions, or upload logs. New group is independent.
Errors: 400 (validation), 401 (no token), 403 (not your group), 404 (source group not found).
Add Schedules to Group
Add weekly time slots to an existing group.
Request / Response
[
{ "dayOfWeek": "Tuesday", "startTime": "15:00", "endTime": "17:00" },
{ "dayOfWeek": "Thursday", "startTime": "15:00", "endTime": "17:00" }
]
{
"data": [
{ "id": "...", "dayOfWeek": "Tuesday", "startTime": "15:00", "endTime": "17:00", "isActive": true },
{ "id": "...", "dayOfWeek": "Thursday", "startTime": "15:00", "endTime": "17:00", "isActive": true }
],
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Enums: dayOfWeek: "Monday"|"Tuesday"|"Wednesday"|"Thursday"|"Friday"|"Saturday"|"Sunday". startTime/endTime: "HH:mm" format (24h).
Errors: 400 (validation), 401 (no token), 403 (not your group), 404 (group not found).
Update Schedule
Change day or time of an existing schedule slot.
Request / Response
{ "dayOfWeek": "Monday", "startTime": "16:00", "endTime": "18:00" }
Rate Limit: 30 req/min.
Enums:
dayOfWeek is System.DayOfWeek serialized as its English name: Sunday…Saturday.Errors: 400 (validation), 401 (no token), 403 (not your schedule), 404 (schedule not found).
Delete Schedule
Remove a weekly time slot from a group.
Request / Response
Rate Limit: 30 req/min.
Behavior: Permanently deletes the schedule slot. Cannot be undone.
Errors: 401 (no token), 403 (not your schedule), 404 (schedule not found).
List Students
Browse students visible to you. Filter by "my-students" or "available".
Request / Response
search: string? (search by name/email)
filter: string? ("my-students" default = pending + active | "available")
page: int = 1
pageSize: int = 20 (max 100)
{
"data": {
"items": [
{
"studentId": "d4e5f6a7-89ab-cdef-0123-456789abcdef",
"fullName": "Sara Mohamed",
"email": "sara@student.com",
"studentCode": "STU-001",
"isEnrolled": false,
"enrolledGroups": [
{ "groupId": "c5d6e7f8-...", "groupName": "Biology - Secondary 2", "status": "Pending" }
],
"stats": { "averageScore": 0, "attendanceRate": 0, "totalSessions": 0, "attendedSessions": 0, "assessmentCount": 0, "lastActivityDate": null, "scoreTrend": "stable", "pendingApprovalCount": 1 }
}
],
"page": 1, "pageSize": 20, "totalCount": 45
},
"meta": { "timestamp": "...", "requestId": "..." }
}
my-students so the list never leaks platform-wide students. Pending memberships report zeroed metrics; they are not counted as absences.
Look Up Student by Code
Find a specific student using their unique student code.
Request / Response
{
"data": {
"studentId": "d4e5f6a7-89ab-cdef-0123-456789abcdef",
"fullName": "Sara Mohamed",
"studentCode": "STU-001",
"alreadyEnrolledWithTeacher": false
},
"meta": { "timestamp": "...", "requestId": "..." }
}
alreadyEnrolledWithTeacher tells you whether the code already belongs to one of your groups.Privacy: email and contact data are deliberately excluded.
Errors: 401 (no token), 404 (unknown code).
Enroll Student by ID
Add a student to a group by their GUID.
Request / Response
{ "studentId": "d4e5f6a7-89ab-cdef-0123-456789abcdef" }
{
"data": {
"studentId": "d4e5f6a7-...",
"fullName": "Sara Mohamed",
"email": "sara@student.com",
"studentCode": "STU-001",
"isEnrolled": false,
"enrolledGroups": [{ "groupId": "c5d6e7f8-...", "groupName": "Biology - Secondary 2", "status": "Pending" }],
"stats": { "averageScore": 0, "attendanceRate": 0, "totalSessions": 0, "attendedSessions": 0, "assessmentCount": 0, "lastActivityDate": null, "scoreTrend": "stable", "pendingApprovalCount": 1 }
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Behavior: Creates a Pending membership (
StudentsGroups.Status = Pending). The student is excluded from rosters, sessions, templates, and uploads until approved. studentCode is generated once if missing and never overwritten.Errors: 400 (validation), 401 (no token), 403 (not your group), 404 (student not found), 409 (already enrolled).
Enroll Student by Code
Add a student to a group using their student code. No request body needed.
Request / Response
status: "Pending"Get Student Progress
Detailed progress: attendance, scores, per-group breakdown — scoped to your groups.
Request / Response
{
"data": {
"studentId": "d4e5f6a7-...",
"fullName": "Sara Mohamed",
"overallAttendancePercentage": 92.0,
"overallAverageWrittenScore": 85.5,
"overallAverageAssignmentScore": 88.0,
"overallProgressPercentage": 87.0,
"totalSessionsAcrossAllGroups": 25,
"perGroupProgress": [
{
"studentId": "d4e5f6a7-...",
"studentName": "Sara Mohamed",
"studentCode": "STU-001",
"groupId": "c5d6e7f8-...",
"groupName": "Biology - Secondary 2",
"enrollmentStatus": "Active",
"totalSessions": 12,
"attendedSessions": 11,
"attendancePercentage": 91.7,
"averageWrittenScore": 86.0,
"averageAssignmentScore": 89.0,
"overallProgressPercentage": 87.5,
"recentAssessments": [
{
"assessmentId": "e5f6a7b8-...",
"examName": "Unit 3 Written",
"topic": "Mitosis",
"kind": "Written",
"rawScore": 18,
"maxScore": 20,
"scorePercentage": 90,
"teacherComment": "Excellent",
"sessionDate": "2026-09-12T14:00:00"
}
]
}
]
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Metrics: attendance counts recorded attendance sessions inside the membership window; a missing row counts as absent.
Get Student's Parent Contact
Retrieve parent name, email, and phone for a student.
Request / Response
{
"data": {
"parentId": "f1a2b3c4-...",
"fullName": "Mohamed Ali",
"email": "mohamed@parent.com",
"phoneNumber": "+201055512345"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Unenroll Student
Remove a student from a group. Preserves historical data.
Request / Response
{ "data": { "message": "Student unenrolled successfully" }, "meta": { "timestamp": "...", "requestId": "..." } }
Rate Limit: 30 req/min.
Behavior: Soft-unenroll — the membership row becomes
Removed with removedAt set. Historical sessions and grades remain readable by the student and parent; the account itself is untouched. Re-enrolling later reactivates the same row as Pending and keeps the existing student code.Errors: 401 (no token), 403 (not your group), 404 (student not in group).
Approve Group Enrollment
Activate a pending membership in a specific group.
Request / Response
{ "data": { "message": "Student approved successfully" }, "meta": { "timestamp": "...", "requestId": "..." } }
Rate Limit: 30 req/min.
Behavior: Sets the membership
Status = Active and stamps approvedAt. Approval is group-scoped and does not touch User.IsActive. Approving an already-active membership is idempotent.Errors: 401 (no token), 403 (not your group), 404 (membership not found).
Export Students as Excel
Download a spreadsheet of all students in a group with their stats.
Request / Response
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename="students_c5d6e7f8.xlsx"
List All Sessions
All sessions across your groups, paginated.
Request / Response
{
"data": {
"items": [
{
"sessionId": "e7f8a9b0-...",
"studentId": "d4e5f6a7-...",
"studentName": "Sara Mohamed",
"studentCode": "STU-001",
"groupName": "Biology - Secondary 2",
"sessionDate": "2026-09-12T14:00:00",
"sessionType": "Quiz",
"attended": true,
"score": 45,
"maxScore": 50,
"scorePercentage": 90.0,
"comment": null
}
],
"page": 1, "pageSize": 20, "totalCount": 156
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Query: page (default 1), pageSize (default 20, max 100).
Behavior: One row per student per session across all groups owned by this teacher (not one row per session). Sorted by session date descending, then student name. Teacher rows carry studentId/studentName/studentCode; group rows by sessionId client-side.
Errors: 401 (no token), 403 (wrong role).
Create Session
Create a session with optional Excel header definitions. Headers define which columns your upload Excel will have.
Request / Response
{
"sessionDate": "2026-09-12T14:00:00",
"sessionType": "Quiz",
"topic": "Cell Division - Mitosis",
"description": "Chapter 3 review",
"headers": [
{ "key": "attendance", "label": "Attendance", "labelArabic": "الحضور", "columnType": "Attendance", "sortOrder": 0, "isRequired": true },
{ "key": "written_exam", "label": "Written Exam", "labelArabic": "الامتحان الكتابي", "columnType": "Score", "maxScore": 50, "sortOrder": 1, "isRequired": true },
{ "key": "oral_quiz", "label": "Oral Quiz", "labelArabic": "الامتحان الشفهي", "columnType": "Score", "maxScore": 20, "sortOrder": 2, "isRequired": false },
{ "key": "comment", "label": "Comment", "labelArabic": "ملاحظة", "columnType": "Comment", "sortOrder": 3, "isRequired": false }
]
}
{
"data": {
"id": "e7f8a9b0-1234-5678-9abc-def012345678",
"sessionDate": "2026-09-12T14:00:00",
"sessionType": "Quiz",
"topic": "Cell Division - Mitosis",
"description": "Chapter 3 review",
"createdAt": "2026-09-12T10:30:00Z",
"headers": [
{ "id": "...", "key": "attendance", "label": "Attendance", "labelArabic": "الحضور", "columnType": "Attendance", "maxScore": null, "sortOrder": 0, "isRequired": true },
{ "id": "...", "key": "written_exam", "label": "Written Exam", "labelArabic": "الامتحان الكتابي", "columnType": "Score", "maxScore": 50, "sortOrder": 1, "isRequired": true },
{ "id": "...", "key": "oral_quiz", "label": "Oral Quiz", "labelArabic": "الامتحان الشفهي", "columnType": "Score", "maxScore": 20, "sortOrder": 2, "isRequired": false },
{ "id": "...", "key": "comment", "label": "Comment", "labelArabic": "ملاحظة", "columnType": "Comment", "maxScore": null, "sortOrder": 3, "isRequired": false }
]
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Enums:
·
sessionType: 0=Exam, 1=Quiz, 2=Homework, 3=Oral, 4=Attendance. Integer, not string.·
columnType (in headers): 0=Attendance, 1=Score, 2=Comment. Integer, not string.Validation: sessionDate (required), topic (required), description (optional). Headers array items: key (required, unique per session), label (required), columnType (required: Attendance | Score | Comment — strings), sortOrder (required, 0-based), isRequired (default false). maxScore required when columnType is Score, and must be 1-1000.
Behavior: If headers omitted, defaults to Attendance + Written 60 + Assignment 20 + Comment. Use copyHeadersFromSessionId to reuse headers from a previous session (headers param is ignored if this is set).
Errors: 400 (validation), 401 (no token), 403 (wrong role), 404 (group not found).
Get Session Detail
Full session with all student sessions and assessment breakdowns.
Request / Response
{
"data": {
"sessionId": "e7f8a9b0-...",
"sessionDate": "2026-09-12T14:00:00",
"sessionType": "Quiz",
"topic": "Cell Division - Mitosis",
"description": "Chapter 3 review",
"groupName": "Biology - Secondary 2",
"subjectName": "Biology",
"teacherName": "Ahmed Hassan",
"studentSessions": [
{
"studentSessionId": "...",
"studentId": "d4e5f6a7-...",
"studentName": "Sara Mohamed",
"studentCode": "STU-001",
"attended": true,
"assessments": [
{ "assessmentId": "...", "examName": "written_exam", "topic": "Mitosis", "kind": "Written", "rawScore": 45, "maxScore": 50, "scorePercentage": 90, "teacherComment": "Good work" },
{ "assessmentId": "...", "examName": "oral_quiz", "topic": "Mitosis", "kind": "Oral", "rawScore": 18, "maxScore": 20, "scorePercentage": 90, "teacherComment": "Excellent" }
]
}
]
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Response: Full session with all StudentSessions and their Assessments. Includes group name, subject name, teacher name.
Errors: 401 (no token), 403 (not your session), 404 (session not found).
Update Session
Change session date, topic, description, or type.
Request / Response
{
"sessionDate": "2026-09-13T14:00:00",
"topic": "Cell Division - Meiosis",
"description": "Updated topic",
"sessionType": "Exam"
}
{
"data": {
"id": "e7f8a9b0-...",
"sessionDate": "2026-09-13T14:00:00",
"sessionType": "Exam",
"topic": "Cell Division - Meiosis",
"description": "Updated topic",
"createdAt": "2026-09-12T10:30:00Z"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Validation: All fields optional. Only provided fields are updated (partial update). sessionType uses same enum as create (0-4).
Errors: 400 (validation), 401 (no token), 403 (not your session), 404 (session not found).
Delete Session
Permanently delete a session and all its assessments.
Request / Response
Mark Attendance
Bulk mark attendance for all students in a session.
Request / Response
{
"attendances": [
{ "studentId": "d4e5f6a7-...", "attended": true },
{ "studentId": "a1b2c3d4-...", "attended": false },
{ "studentId": "e5f6a7b8-...", "attended": true }
]
}
Rate Limit: 30 req/min.
Behavior: Bulk operation. Creates or updates StudentSession records with the attended flag. Idempotent — calling again overwrites previous attendance.
Errors: 400 (validation), 401 (no token), 403 (not your session), 404 (session/student not found).
Get Session Headers
View the Excel column configuration for a session.
Request / Response
{
"data": [
{ "id": "...", "key": "attendance", "label": "Attendance", "labelArabic": "الحضور", "columnType": "Attendance", "maxScore": null, "sortOrder": 0, "isRequired": true },
{ "id": "...", "key": "written_exam", "label": "Written Exam", "labelArabic": "الامتحان الكتابي", "columnType": "Score", "maxScore": 50, "sortOrder": 1, "isRequired": true },
{ "id": "...", "key": "oral_quiz", "label": "Oral Quiz", "labelArabic": "الامتحان الشفهي", "columnType": "Score", "maxScore": 20, "sortOrder": 2, "isRequired": false },
{ "id": "...", "key": "comment", "label": "Comment", "labelArabic": "ملاحظة", "columnType": "Comment", "maxScore": null, "sortOrder": 3, "isRequired": false }
],
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Response: Array of SessionHeaderDefinitionDto (not wrapped in pagination). Sorted by sortOrder ascending.
Errors: 401 (no token), 403 (not your session), 404 (session not found).
Update Session Headers
Change the Excel column configuration. Locked (409) once scores are uploaded.
Request / Response
{
"headers": [
{ "key": "attendance", "label": "Attendance", "columnType": "Attendance", "sortOrder": 0, "isRequired": true },
{ "key": "written_exam", "label": "Written Exam", "columnType": "Score", "maxScore": 50, "sortOrder": 1, "isRequired": true },
{ "key": "bonus", "label": "Bonus Points", "columnType": "Score", "maxScore": 10, "sortOrder": 2, "isRequired": false }
],
"copyHeadersFromSessionId": null
}
{
"data": {
"sessionId": "e7f8a9b0-...",
"headers": [
{ "id": "...", "key": "attendance", "label": "Attendance", "columnType": "Attendance", "maxScore": null, "sortOrder": 0, "isRequired": true },
{ "id": "...", "key": "written_exam", "label": "Written Exam", "columnType": "Score", "maxScore": 50, "sortOrder": 1, "isRequired": true },
{ "id": "...", "key": "bonus", "label": "Bonus Points", "columnType": "Score", "maxScore": 10, "sortOrder": 2, "isRequired": false }
]
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Lock Condition: Returns 409 Conflict if scores already exist for this session. Delete existing scores first or create a new session.
Alternative: Use copyHeadersFromSessionId in the request body to copy headers from another session instead of defining them manually.
Errors: 400 (validation), 401 (no token), 403 (wrong role), 404 (session not found), 409 (scores exist — headers locked).
Add Assessment
Add a score record for a student in a session. Creates or updates the assessment.
Request / Response
{
"studentId": "d4e5f6a7-89ab-cdef-0123-456789abcdef",
"examName": "written_exam",
"rawScore": 45,
"maxScore": 50,
"kind": "Written",
"teacherComment": "Good understanding of mitosis phases"
}
{
"data": {
"id": "...",
"examName": "written_exam",
"topic": null,
"kind": "Written",
"rawScore": 45,
"maxScore": 50,
"scorePercentage": 90,
"teacherComment": "Good understanding of mitosis phases"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Validation: studentId (required, valid GUID), examName (required, must match a header key), scorePercentage (required, 0-100), maxScore (required, > 0).
Behavior: scorePercentage is stored as percentage (0-100), not raw score. The system calculates: rawScore = (scorePercentage / 100) * maxScore. Creates a new Assessment record linked to the StudentSession.
Errors: 400 (validation), 401 (no token), 403 (wrong role), 404 (session/student not found).
Update Assessment
Change score or comment on an existing assessment.
Request / Response
{ "scorePercentage": 48, "teacherComment": "Updated: excellent progress" }
{
"data": {
"id": "...", "examName": "written_exam", "topic": "",
"scorePercentage": 48, "teacherComment": "Updated: excellent progress"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Validation: Both fields optional (partial update). scorePercentage: 0-100.
Errors: 400 (validation), 401 (no token), 403 (not your assessment), 404 (assessment not found).
Get Average Score
Quick summary: average, min, max scores and attendance for a session.
Request / Response
{
"data": {
"sessionId": "e7f8a9b0-...",
"sessionType": "Quiz",
"sessionDate": "2026-09-12T14:00:00",
"groupName": "Biology - Secondary 2",
"totalStudents": 50,
"attendedStudents": 46,
"attendanceRate": 92.0,
"averageScore": 82.8,
"minScore": 35,
"maxScore": 100,
"totalAssessments": 138
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Response: Lightweight summary — faster than /report. TotalAssessments = total assessment records (one per score column per student).
Errors: 401 (no token), 403 (not your session), 404 (session not found).
Download Template
Get an Excel template pre-filled with enrolled student names/codes AND your custom header columns.
Request / Response
groupId: uuid? (legacy mode: populates student names) sessionId: uuid? (session-bound mode: headers match session config)
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename="template_20260912.xlsx"
Rate Limit: 5 req/min (stricter — file generation is expensive).
Modes:
· Session-bound (recommended): Pass sessionId. Template columns match session headers exactly. Student names/codes auto-filled from enrolled students.
· Legacy: Pass groupId only. Uses default headers (Attendance, Written 60, Assignment 20, Comment).
Errors: 401 (no token), 403 (wrong role), 404 (session/group not found).
Upload Excel
Fill the template with attendance + scores, then upload. Hangfire processes it async. Strategy: replace (overwrite), skip (keep existing), or error (reject if scores exist).
Request / Response
file: (binary .xlsx, max 10MB) groupId: c5d6e7f8-9abc-def0-1234-567890abcdef sessionId: e7f8a9b0-1234-5678-9abc-def012345678 (optional) assessmentDate: 2026-09-12T14:00:00 (optional) strategy: replace (error | replace | skip)
{
"data": {
"uploadId": "f9a0b1c2-3456-789a-bcde-f01234567890",
"fileName": "template_20260912.xlsx",
"status": "Queued",
"uploadedAt": "2026-09-12T14:30:00Z"
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 5 req/min (stricter — file upload is expensive).
Enums:
·
strategy: "error" | "replace" | "skip". Query parameter, not body.-
"error": Returns 409 if scores already exist for this session.-
"replace": Deletes all existing StudentSessions + Assessments, creates fresh from Excel.-
"skip": Keeps students who already have scores, only adds new students.Behavior: File is saved to disk at
uploads/excel/{yyyy}/{MM}/{dd}/{guid}_{filename}. Processing is async via Hangfire (ProcessExcelJob on "uploads" queue). Always returns status="Queued". Poll GET /upload-logs to check completion.Errors: 400 (invalid file), 401 (no token), 403 (wrong role), 409 (strategy=error and scores exist), 413 (file too large).
List Upload Logs
Paginated upload history with status, row counts, and group info.
Request / Response
uploadStatuses: UploadStatus[]? (repeatable: 0=Queued, 1=Processing, 2=Completed, 3=Failed; omit for all) page: int = 1 pageSize: int = 20 (max 100)
{
"data": {
"items": [
{
"uploadId": "f9a0b1c2-...",
"fileName": "template_20260912.xlsx",
"fileSize": 24576,
"status": "Completed",
"uploadedAt": "2026-09-12T14:30:00Z",
"completedAt": "2026-09-12T14:30:15Z",
"rawProcessed": 50,
"rawFailed": 0,
"groupId": "c5d6e7f8-...",
"groupName": "Biology - Secondary 2"
}
],
"page": 1, "pageSize": 10, "totalCount": 8
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Enums:
·
status: Queued, Processing, Completed, Failed (serialized as strings).Filters: repeatable
?uploadStatuses= (e.g. Queued, Failed); omit or empty for all. Filter applies before totalCount, so counts match the filtered list.Polling: After upload, poll this endpoint every 2-3 seconds. Check if status left Queued/Processing.
Errors: 401 (no token), 403 (wrong role).
Get Upload Log Detail
Single upload log with full details.
Request / Response
Retry Failed Upload
Re-queue a failed upload for processing.
Request / Response
Rate Limit: 30 req/min.
Behavior: Re-queues the same file for processing with the same strategy. Creates a new Hangfire job. The upload status resets to Queued (0).
Errors: 401 (no token), 403 (wrong role), 404 (upload log not found), 409 (status is not Failed).
Session Report
Professional report: column breakdown, highlights, chart series, needs-attention, comparison with previous session. Cached 1 minute.
Request / Response
{
"data": {
"session": {
"sessionId": "e7f8a9b0-...", "sessionDate": "2026-09-12T14:00:00",
"sessionType": "Quiz", "topic": "Cell Division - Mitosis",
"groupName": "Biology - Secondary 2", "subjectName": "Biology"
},
"analytics": {
"totalStudents": 50, "attendedStudents": 46, "attendanceRate": 92.0,
"averageScore": 82.8, "minScore": 35, "maxScore": 100, "medianScore": 85.0,
"passCount": 42, "failCount": 4, "passRate": 87.5,
"gradeDistribution": { "A": 31, "B": 33, "C": 21, "D": 7 }
},
"columnBreakdown": [
{ "key": "written_exam", "label": "Written Exam", "maxScore": 50, "average": 38.5, "min": 15, "max": 50, "passRate": 90.0, "assessedCount": 46 },
{ "key": "oral_quiz", "label": "Oral Quiz", "maxScore": 20, "average": 16.2, "min": 8, "max": 20, "passRate": 85.0, "assessedCount": 46 }
],
"highlights": {
"topStudents": [{ "studentId": "...", "studentName": "Ali Hassan", "studentCode": "STU-003", "average": 98.5, "delta": 3.2 }],
"atRisk": [{ "studentId": "...", "studentName": "Omar Ali", "studentCode": "STU-012", "average": 35.0, "delta": -5.0 }],
"mostImproved": [{ "studentId": "...", "studentName": "Fatma SA", "studentCode": "STU-008", "average": 88.0, "delta": 12.5 }],
"biggestDrop": []
},
"chartSeries": {
"histogram": [{ "bucket": "90-100", "count": 18 }, { "bucket": "80-89", "count": 15 }, { "bucket": "70-79", "count": 8 }, { "bucket": "60-69", "count": 5 }, { "bucket": "0-59", "count": 4 }],
"byColumn": [{ "label": "Written Exam", "average": 82.8 }, { "label": "Oral Quiz", "average": 81.0 }]
},
"needsAttention": [
{ "type": "low_attendance", "message": "4 students absent", "count": 4 },
{ "type": "failing_scores", "message": "4 students below 60%", "count": 4 }
],
"comparison": {
"previousAvgScore": 78.2, "currentAvgScore": 82.8, "scoreChange": 4.6,
"previousAttendanceRate": 88.0, "currentAttendanceRate": 92.0, "attendanceChange": 4.0,
"perColumnDelta": [
{ "key": "written_exam", "label": "Written Exam", "previousAverage": 35.0, "currentAverage": 38.5, "delta": 3.5 }
]
}
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Caching: IMemoryCache, 1 minute TTL. Cache key:
session_report_{sessionId}. Data is stale for up to 60s after new uploads.Response Fields: session (info), analytics (11 stats + gradeDistribution), columnBreakdown[] (per-column avg/min/max/passRate), highlights (topStudents/atRisk/mostImproved/biggestDrop — max 5 each), chartSeries (histogram 5 buckets + byColumn), needsAttention[] (max 5 items), comparison (nullable — null if no previous session for this group).
GradeDistribution Keys: "A" (90-100), "B" (80-89), "C" (70-79), "D" (60-69), "F" (below 60). Based on average score percentage.
Errors: 401 (no token), 403 (wrong role), 404 (session not found).
Export Session Report
Download session report as an Excel file.
Request / Response
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename="report_e7f8a9b0.xlsx"
Rate Limit: 30 req/min.
Behavior: Generates Excel with attendance, scores per column, grade distribution. File is generated on-the-fly (not cached).
Errors: 401 (no token), 403 (not your session), 404 (session not found).
Export Students
Download all students in a group as Excel with their stats.
Request / Response
Rate Limit: 30 req/min.
Behavior: Generates Excel with student names, codes, enrollment dates, attendance rates, average scores. One row per student.
Errors: 401 (no token), 403 (not your group), 404 (group not found).
Group Leaderboard
Rankings for a specific group. Recomputed hourly by Hangfire.
Request / Response
{
"data": [
{
"rank": 1, "studentId": "d4e5f6a7-...",
"studentName": "Ali Hassan", "studentCode": "STU-003",
"weightedScore": 95.5, "avgScore": 92.0, "attendanceRate": 100.0, "participationScore": 10.0,
"context": {
"totalStudents": 50, "scoreDifferenceToTop": 0,
"averageScore": 82.8, "topScore": 95.5,
"isTopPerformer": true, "rankLabel": "#1"
}
},
{
"rank": 2, "studentId": "...",
"studentName": "Sara Mohamed", "studentCode": "STU-001",
"weightedScore": 91.2, "avgScore": 88.0, "attendanceRate": 95.0, "participationScore": 9.0,
"context": { "totalStudents": 50, "scoreDifferenceToTop": 4.3, "averageScore": 82.8, "topScore": 95.5, "isTopPerformer": false, "rankLabel": "#2" }
}
],
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Enums:
·
period: "daily" | "weekly" | "monthly" | "alltime". Default: "weekly".Response: Array (not paginated). Sorted by rank ascending. WeightedScore = avgScore * 0.6 + attendanceRate * 0.3 + participationScore * 0.1.
Errors: 401 (no token), 403 (not your group), 404 (group not found).
Teacher-Wide Leaderboard
Combined rankings across all your groups. Paginated.
Request / Response
Rate Limit: 30 req/min.
Query: period (default "alltime"), gradeLevel (optional filter), page (default 1), pageSize (default 20, max 100).
Behavior: Combines rankings across ALL groups owned by this teacher. A student appears once with their best score across groups.
Errors: 401 (no token), 403 (wrong role).
List Notifications
Your notifications: badge awards, payment confirmations, system alerts.
Request / Response
{
"data": {
"items": [
{
"id": "...", "userId": "b0000000-...",
"title": "Payment Confirmed", "body": "Sara Mohamed's payment of 1500 EGP confirmed",
"type": "PaymentConfirmed", "isRead": false,
"payload": null, "sentAt": "2026-09-12T10:00:00Z"
}
],
"page": 1, "pageSize": 20, "totalCount": 12
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Query: page (default 1), pageSize (default 20, max 100).
Response Fields: id, userId, title, body, type (string like "PaymentConfirmed"), isRead (bool), payload (nullable JSON string), sentAt (nullable DateTime).
Errors: 401 (no token), 403 (wrong role).
Unread Count
Quick badge count for the notification bell.
Request / Response
{ "data": 3, "meta": { "timestamp": "...", "requestId": "..." } }
Mark Read / Read All
Mark a single notification as read, or mark all as read.
Request / Response
{ "data": {}, "meta": { "timestamp": "...", "requestId": "..." } }
Rate Limit: 30 req/min.
Behavior: mark-read sets isRead=true on one notification. read-all sets isRead=true on ALL unread notifications for this user.
Errors: 401 (no token), 403 (wrong role), 404 (notification not found for mark-read).
Send Notification to Parent
Send a notification to a specific student's parent.
Request / Response
{
"studentId": "d4e5f6a7-89ab-cdef-0123-456789abcdef",
"title": "Exam Results",
"body": "Sara scored 90% in the Cell Division quiz",
"type": "NewGrade"
}
Rate Limit: 30 req/min.
Enums:
·
type: NewGrade, RankChange, BadgeEarned, PaymentConfirmed, SystemAlert, LeaderboardResult (serialized as strings).Behavior: Creates a single Notification record for the student's parent. Bilingual EN/AR — title and body are stored as-is (caller provides translated version if needed).
Errors: 400 (validation), 401 (no token), 403 (wrong role), 404 (student not found or student has no parent).
Bulk Notify Parents
Send a notification to all parents in a group. Returns count of notifications sent.
Request / Response
{
"title": "Exam Results Available",
"body": "The results for the Cell Division quiz are now available. Class average: 82.8%",
"type": "SystemAlert"
}
{ "data": 15, "meta": { "timestamp": "...", "requestId": "..." } }
Rate Limit: 30 req/min.
Enums:
·
type: strings — NewGrade, RankChange, BadgeEarned, PaymentConfirmed, SystemAlert, LeaderboardResult.Behavior: Creates one Notification per parent (not per student). If a parent has multiple children in the group, they still get only one notification. Response data = count of notifications sent.
Errors: 400 (validation), 401 (no token), 403 (wrong role), 404 (group not found).
Look Up Subject by Name
Find a subject's ID by its English or Arabic name. Useful for creating groups.
Request / Response
{
"data": {
"id": "a1b2c3d4-5678-9abc-def0-1234567890ab",
"name": "Biology",
"nameArabic": "أحياء",
"isActive": true,
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": null
},
"meta": { "timestamp": "...", "requestId": "..." }
}
Rate Limit: 30 req/min.
Behavior: Case-insensitive name search. Use the returned
id as subjectId when creating groups (POST /teachers/groups).Related Endpoints: GET /subjects (list all, paginated, ?searchName= filter), POST /subjects (admin only, create new).
Errors: 401 (no token), 403 (wrong role), 404 (subject not found by name).
StudentsGroupsPending → Active → Removed with ApprovedAt/RemovedAt. Approval moved to PUT /teachers/groups/{groupId}/students/{studentId}/approve; unenroll is a soft delete that keeps history readable; StudentCode is generated once and never overwritten.SessionType.Attendance inside the membership window with at least one recorded row; a missing student row counts as absent; sessions with no attendance data are excluded; pending memberships never contribute absences.Assessment.RawScore and AssessmentKind added; every score response now exposes rawScore, maxScore, scorePercentage. Raw wins when supplied; percentage-only input derives the raw score via ScoreMath.LeaderboardEntryMapper.GET /students/badges returns the whole catalog with isEarned/awardedAt; paging params and the PaginatedResult envelope are gone, and metadata is now metaData.T2I_SQLSERVER_TESTS=1) creates a throw-away LocalDB database, applies every migration, and exercises each student-module repository. Migration 20261027084034_StudentModuleContracts backfills memberships and assessments.main.github/workflows/deploy.yml builds, tests, and deploys on push to main; deploy-pages.yml publishes this Atlas. Pull requests get no automated build or test run.Codebase Guide
✗ Application NEVER depends on Infrastructure
✗ API NEVER depends on Infrastructure directly (only via Application interfaces)
All wiring, middleware pipeline, DI
MediatR + validator registration
DbContext + repos + services registration
17 DbSets, TPT config
SaveChanges with error handling
25+ exception → HTTP mappings
Guid.CreateVersion7() — time-ordered UUIDs. Generated in handlers, NOT in entities. Entities have { get; set; } only._context.AddAsync() or LINQ queries. Handlers call _unitOfWork.SaveChangesAsync() explicitly..AsNoTracking() — faster, no change tracking overhead. Only use tracking for update scenarios.TrackToImprove.Infrastructure. Startup: TrackToImprove.API.Static factory methods for creating valid test entities. Thread-safe counter for unique values.
Generates JWT tokens for test users with known secret. Used in integration tests.
Seeds deterministic test data with fixed GUIDs. Same data every test run.
InMemory EF Core + FakeEmailService + FakeJobService + FakeFileStorage. Sets ASPNETCORE_ENVIRONMENT=Testing.
FakePasswordHasher (no BCrypt), FakeEmailService (logs to console), FakeJobService (no Hangfire), FakeFileStorage (no disk).
Wrapper for testing LoggingBehavior with non-seekable streams (prevents Stream.ReadTimeout).
| Field | Description |
|---|---|
| fullName | Full display name shown in profiles and conversation headers. E.g. "Ahmed Hassan" |
| Login identifier. Must be unique. Used for all auth flows. E.g. "ahmed@gmail.com" | |
| username | Unique username, 3-50 characters. Shown in profiles. E.g. "ahmed_h" |
| password | Minimum 8 characters. Hashed immediately on the server. E.g. "Teacher@123" |
| phoneNumber | Contact phone in E.164 format. E.g. "+201234567890" |
| dateOfBirth | Must be a past date. Used for age verification and profile display. |
| role | One of: "Teacher", "Student", "Parent". Determines permissions and which dashboard the user sees. |
| Property | What It Means & How to Display |
|---|---|
| accessToken | JWT bearer token. Send this in the Authorization: Bearer {token} header for all authenticated requests. Store securely in Flutter (e.g. flutter_secure_storage). Expires after the time shown in expiresAt. |
| refreshToken | Long-lived token used to get a new accessToken without re-entering credentials. Store securely. Send to /auth/refresh-token when the access token expires. |
| expiresAt | UTC timestamp when the accessToken stops working. Use this to schedule an automatic refresh before it expires. Display as relative time if needed: "Expires in 2 hours". |
| role | Determines which dashboard to show and which API endpoints are accessible. Route the user to the correct home screen: Teacher → TeacherHomeScreen, Student → StudentHomeScreen, Parent → ParentHomeScreen. |
| userId | Unique ID of the authenticated user. Useful for client-side operations like showing profile, filtering local cache, or WebSocket subscriptions. |
| Field | Description |
|---|---|
| Registered email address | |
| password | Plain-text password (verified against stored hash) |
| Field | Description |
|---|---|
| accessToken | The expired or soon-to-expire access token |
| refreshToken | The current valid refresh token |
| Field | Description |
|---|---|
| refreshToken | The refresh token to invalidate |
| Field | Description |
|---|---|
| The registered email address to send the reset code to |
| Field | Description |
|---|---|
| The email address the code was sent to | |
| otp | The 6-digit code from the email |
| newPassword | New password, minimum 8 characters |
| Field | Description |
|---|---|
| The email address to verify | |
| code | The 6-digit verification code from the email |
| Field | Description |
|---|---|
| The email address to resend the code to |
| Field | Description |
|---|---|
| currentPassword | The user's current password for verification |
| newPassword | New password, minimum 8 characters |
| Property | What It Means & How to Display |
|---|---|
| profile — Teacher's identity and subscription | |
| profile.fullName | The teacher's display name. Show as the main title on the dashboard header. E.g. "Good morning, Ahmed" |
| profile.email | Contact email. Show in profile section or settings. |
| profile.subject | The primary subject this teacher teaches. Show as a subtitle or chip. E.g. "Mathematics" |
| profile.subscriptionPlan | Current plan: "Free", "Pro", or "Premium". Show as a badge/chip. Controls which features are accessible. |
| profile.subscriptionEnd | When the subscription expires. Show as "Expires Mar 2026" or countdown. If null, the teacher is on the free plan. |
| overview — Key metrics at a glance | |
| overview.totalGroups | Total groups this teacher has ever created (including inactive). Show as a stat card: "5 Groups" |
| overview.activeGroups | Groups that are currently active (not soft-deleted). The number that matters for daily use. Show alongside total: "4 active" |
| overview.totalStudents | Total students across all active groups. Sum of all enrolled students. Show as stat card: "120 Students" |
| overview.totalSessionsThisMonth | How many sessions the teacher conducted this calendar month. Show as stat card. Compare to last month if possible. |
| overview.pendingPaymentsEGP | Total money in Egyptian Pounds that parents owe but haven't confirmed yet. Show with warning color if > 0: "3,500 EGP pending" |
| overview.totalUploadsThisMonth | Number of Excel files uploaded this month. Shows how active the teacher is with data entry. |
| overview.scoreTrend | Absolute change in average score vs previous month. Positive = improvement. E.g. 5.2 means scores went up 5.2 points. Display as "+5.2 pts" with green color. |
| overview.topPerformingGroup | Name of the group with the highest average score. Show as a highlight: "Your best: Math 101 - A" |
| overview.topPerformingGroupScore | The average score of that top group. Show alongside the name: "87.5% avg" |
| overview.studentsAtRiskCount | Students scoring below 50% or with attendance below 60%. This is a critical alert. Show as red badge: "3 students need attention". Tap to open the dedicated needs-attention screen (GET /teachers/students/needs-attention). |
| overview.uploadStreakWeeks | Consecutive weeks the teacher uploaded at least one score sheet. Gamification metric. Show with fire/trophy icon: "6-week streak!" |
| overview.classAverageScore | Average score across ALL students in ALL the teacher's groups (0-100). The single most important number. Display as large circular progress indicator. Color: green ≥80, yellow 60-79, red <60. |
| overview.totalSessionsAllTime | Total sessions ever conducted. Shows experience/tenure. Display as subtle stat: "156 total sessions" |
| trends — Week-over-week comparison | |
| trends.scoreChangePercent | Percentage change in average score from last week to this week. E.g. 5.2 means scores improved 5.2% this week. Display as "+5.2%" in green with ↑ arrow, or "-3.1%" in red with ↓ arrow. |
| trends.attendanceChangePercent | Percentage change in attendance rate from last week to this week. Same display logic as scoreChangePercent. |
| trends.studentsChange | Net change in unique students who attended sessions this week vs last week. E.g. 8 means 8 more students showed up this week. Display as "+8 students" badge. |
| trends.scoreTrend | Direction of scores over the last week. Values: "up", "down", "stable". Display as colored arrow: ↑ green, ↓ red, → gray. |
| trends.attendanceTrend | Same as scoreTrend but for attendance. Same arrow treatment. |
| topGroups — Best and worst performing groups | |
| topGroups.top | Top 2 groups by average score (only groups with students). Show as a "Top Performers" list with rank, name, and score. |
| topGroups.bottom | Bottom 2 groups by average score. Show as "Needs Improvement" list. Use this to prompt the teacher to focus on struggling groups. |
| topGroups.*.averageScore | Group's average score (0-100). Display as progress bar. Color: green ≥80, yellow 60-79, red <60. |
| topGroups.*.trend | Score direction: "up", "down", "stable". Show as arrow icon next to the score. |
| topGroups.*.attendanceRate | What % of students attended sessions in this group. Show as percentage. Below 70% should be flagged. |
| recentUploads — Latest file uploads | |
| recentUploads | Last 3 uploaded Excel files. Show as a list with file name, group name, and status badge. Tap to view upload details. |
| recentUploads.*.status | Processing status: "Queued", "Processing", "Completed", or "Failed". Show as colored badge: green=Completed, yellow=Processing, red=Failed. |
| recentUploads.*.rowsProcessed | How many student rows were successfully processed. Show as "30/30 students" or "28/30 (2 failed)". |
| topStudents — Best students per grade | |
| topStudents | For each grade level, the top 3 students from the highest-performing group in that grade. Show as expandable sections by grade. Each student shows rank, name, score, and attendance. |
| topStudents.*.rank | Student's rank in their group (1-based). Display as rank badge: #1 gold, #2 silver, #3 bronze. |
| Other fields | |
| unreadNotifications | Count of unread notifications. Show as red badge on the notification bell icon. Tap to open notifications screen. |
| needsAttention — moved | Removed from /home. Use the dedicated paginated endpoint GET /teachers/students/needs-attention: richer detail (studentCode, groupId, gradeLevel, missedSessionsCount, parent contact), filters (groupId, gradeLevel, reason=low_score|low_attendance|low_score_low_attendance, search), 20/page. |
| platformComparison | This teacher's ranking compared to all other active teachers. Null if computation fails. Show as a comparison card: "You rank #12 out of 85 teachers". |
| platformComparison.performanceLabel | Human-readable label: "Above Average" (≥110% of school avg), "Average" (90-110%), or "Below Average" (<90%). Display as colored badge. |
overview.studentsAtRiskCount badge on /home). Returns a paginated list of students with average score < 50% or attendance rate < 60% across the teacher's active groups. Each entry includes the failure reason, current score, attendance rate, missed session count, and parent contact (name + phone) for one-tap calls.| Parameter | Type | Default | Description & Flutter Usage |
|---|---|---|---|
| groupId | Guid? | null | Limit to one group. Pass from a group dropdown on the attention screen. |
| gradeLevel | string? | null | Filter by grade: SecondaryOne, SecondaryTwo, SecondaryThree. |
| reason | string? | null | low_score, low_attendance, or low_score_low_attendance (case-insensitive). Map to filter chips — use the row's reasonCode as the chip value, never the display text. Unknown values (incl. localized labels like Low score & Low attendance) return 400. |
| search | string? | null | Filters by student name, student code, or group name (case-insensitive). |
| page / pageSize | int | 1 / 20 | Pagination. Max pageSize is 100. |
| Property | What It Means & How to Display |
|---|---|
| items[].fullName | Student's name. Show as card title with a red alert icon. |
| items[].studentCode | Student's unique code. Show as subtitle chip for identification. |
| items[].groupName | Group the student belongs to. Show as a secondary chip. |
| items[].reason | Localized display-only failure text (EN/AR): "Low score", "Low attendance", or "Low score & Low attendance". Display as alert subtitle in red. Never send back as the reason filter — it returns 400. |
| items[].reasonCode | Machine token matching the row: low_score, low_attendance, or low_score_low_attendance. Use as the filter-chip value and as the ?reason= query param when re-fetching. |
| items[].currentScore | Current average (0-100). Show as progress bar; always < 50 for low-score entries. |
| items[].attendanceRate | Attendance as a 0-1 fraction rounded to 1 decimal (0.4 = 40%). Low-attendance entries are always < 0.60. |
| items[].missedSessionsCount | Sessions the student missed. Show as "3 missed sessions" badge; 0 means attendance is the issue, not absences. |
| items[].parentName / parentPhoneNumber | Parent contact for follow-up. Render parentPhoneNumber as a tel: action button. Null if the student has no linked parent. |
| items are sorted | Lowest score first, then lowest attendance — worst cases at the top. Don't re-sort client-side. |
/teachers/home provides a fast lightweight summary for the initial tab, this endpoint returns deep structured datasets designed specifically for Flutter charting widgets (fl_chart, syncfusion_flutter_charts). It supports flexible multi-criteria filtering by group, grade level, academic period, time range, custom date intervals, and student search. Caching is key-aware per filter selection with a 2-minute TTL. Zero N+1 queries.| Parameter | Type | Default | Description & Flutter Usage |
|---|---|---|---|
| groupId | Guid? | null | Filter all chart data and rankings to a single group. Pass from group dropdown selector. |
| gradeLevel | string? | null | Filter by grade level: SecondaryOne, SecondaryTwo, or SecondaryThree. |
| period | string? | null | Filter by term: First Term or Second Term. |
| timeRange | string? | "30d" | Preset interval: 7d (last 7 days), 30d (last 30 days), 90d (last quarter), term (current 4-month term), all (all-time), or custom. Map to time range filter chips. |
| dateFrom | DateTime? | null | Start date in ISO-8601 (e.g. 2026-08-01T00:00:00Z). Required if timeRange=custom. |
| dateTo | DateTime? | null | End date in ISO-8601. Used with custom date range picker. |
| search | string? | null | Search query to filter student names and student codes in the topStudents list. Does not affect overview counts — studentsAtRiskCount is search-independent by design. |
| studentsPage | int | 1 | Page number for the paginated topStudents list. Chart sections are never paginated. |
| studentsPageSize | int | 10 | Items per page for topStudents (max 100, clamped server-side). Keep filters identical between pages — the 2-minute cache is shared per filter combination, so paging is as cheap as the first call. |
| Property | Business Meaning & Flutter Display Guidance |
|---|---|
| filterApplied — Active Filter Echo & State Sync | |
| filterApplied.groupId | Selected group filter ID (null if all groups). Use to highlight the active group in your DropdownButton. |
| filterApplied.groupName | Display name of the filtered group (null if all groups). Use as the header sub-title. |
| filterApplied.gradeLevel | Selected grade level filter string (SecondaryOne, SecondaryTwo, SecondaryThree, or null). |
| filterApplied.period | Selected academic term filter string (e.g. "First Term"). |
| filterApplied.timeRange | Normalized time range: "7d", "30d", "90d", "term", "all", "custom". Use to activate the matching FilterChip. |
| filterApplied.dateFrom / dateTo | Effective UTC start and end bounds computed by the server for all time-series charts. |
| filterApplied.search | Active search string applied to the topStudents list. Echo into the SearchBar. Overview counts remain unfiltered. |
| overview & trends — KPI Cards & Delta Badges | |
| overview.averageClassScore | Weighted average score percentage (0-100) across all filtered sessions. Display inside a CircularPercentIndicator or large KPI card. |
| overview.overallAttendanceRate | Overall attendance rate percentage across all filtered groups. Display with an attendance icon and progress bar. |
| overview.totalSessionsInPeriod | Total number of class sessions conducted in the active date window. |
| overview.totalStudents / activeGroups | Active students and groups enrolled under this teacher. |
| overview.pendingPaymentsEGP | Total unconfirmed payments in EGP. Show in amber badge if > 0 with a quick navigation button to Payments. |
| overview.studentsAtRiskCount | Count of students with score < 50% or attendance < 60%. Search-independent by design. Display with a red pulsing badge; tap to open the paginated GET /teachers/students/needs-attention screen. |
| overview.uploadStreakWeeks | Consecutive weekly uploads count. Render with a flame/streak badge (e.g. "7-week streak 🔥"). |
| trends.scoreChangePercent | Week-over-week score delta percentage. Render as "+4.8%" in green (up) or "-2.1%" in red (down). |
| trends.attendanceChangePercent | Week-over-week attendance delta percentage with arrow indicators. |
| scoreTimelineChart — Score Progression (Line / Area Chart) | |
| scoreTimelineChart | Ordered list of session points for fl_chart.LineChart. Map index/date to X-axis and score to Y-axis. |
| scoreTimelineChart.*.date | Session date. Use for X-axis timestamps or tooltip dates. |
| scoreTimelineChart.*.label | Pre-formatted date label (e.g. "Aug 25"). Perfect for AxisTitles.bottomTitles. |
| scoreTimelineChart.*.sessionTitle | Session topic or exam title for interactive touch tooltips (LineTouchTooltipData). |
| scoreTimelineChart.*.groupName | Group name where the session took place. Useful for multi-group legend. |
| scoreTimelineChart.*.averageScore | Primary Y-axis plot line (FlSpot(x, averageScore)). Value ranges 0-100. |
| scoreTimelineChart.*.highestScore | Upper bound score in this session. Render as secondary light dotted line or shaded range area. |
| scoreTimelineChart.*.lowestScore | Lowest score in this session. Highlights score variance. |
| attendanceTimelineChart — Attendance Breakdown (Stacked Bar Chart) | |
| attendanceTimelineChart | Ordered list of session attendance for fl_chart.BarChart. |
| attendanceTimelineChart.*.attendedCount | Number of students present. Render as bottom bar segment in green. |
| attendanceTimelineChart.*.absentCount | Number of students absent. Render as stacked top bar segment in red/gray. |
| attendanceTimelineChart.*.attendanceRate | Percentage attendance (0-100%). Display in bar tooltip. |
| scoreDistribution — Grade Brackets (Histogram / Donut Chart) | |
| scoreDistribution | 5 standard score brackets: excellent (85-100%), veryGood (75-84.9%), good (65-74.9%), pass (50-64.9%), and struggling (<50%). |
| scoreDistribution.*.count | Number of assessment grades in this bracket. Map to histogram bar heights. |
| scoreDistribution.*.percentage | Percentage of total evaluated scores in this bracket. Map to PieChartSectionData for donut charts. |
| scoreDistribution.*.range | Legend range label string (e.g. "85% - 100%"). |
| studentHealthStatus — Student Intervention Gauges (Donut / Status Pie) | |
| studentHealthStatus | Categorizes enrolled students into 4 health tiers: excelling (score ≥ 85 & att ≥ 90), onTrack (score ≥ 65 & att ≥ 75), needsPush (50-65%), and atRisk (< 50% score or < 60% att). |
| studentHealthStatus.*.count / percentage | Count and percentage for status donut chart or horizontal segment progress bar. |
| groupPerformanceComparison — Group Benchmark (Multi-Bar / Radar) | |
| groupPerformanceComparison | List of teacher groups with aggregated metrics. Map to horizontal bar chart or comparison table. |
| groupPerformanceComparison.*.averageScore | Group's average score across all sessions in period. |
| groupPerformanceComparison.*.attendanceRate | Group's overall attendance rate %. |
| groupPerformanceComparison.*.trend | Localized trend label: "up", "stable", or "down". Display with directional arrow. |
| assessmentCategoryBreakdown — Exam vs Quiz vs Homework (Polar / Bar) | |
| assessmentCategoryBreakdown | Performance averages grouped by session type (Exams, Quizzes, Homework, Other). Render as a Radar/Spider chart or category bar breakdown. |
| gradeLevelDistribution — Secondary 1 / 2 / 3 Breakdown | |
| gradeLevelDistribution | Distribution of students, groups, and average score across SecondaryOne, SecondaryTwo, and SecondaryThree. |
| platformComparison — School Benchmark Radar | |
| platformComparison | Compares this teacher against school-wide averages. Shows yourAverageScore vs schoolAverageScore, yourRank, totalTeachers, and performanceLabel ("Above Average", "Average", "Below Average"). |
| topStudents — Actionable List (paginated) | |
| topStudents.items | Top ranked students filtered/searched. Shows rank (#1 gold, #2 silver, #3 bronze), full name, student code, score, and attendance. |
| topStudents.totalCount / page / pageSize | Drive infinite-scroll or page buttons via studentsPage / studentsPageSize query params (pageSize max 100). Chart sections are never paginated. |
| overview.studentsAtRiskCount | At-risk student count (score < 50% or attendance < 60%), independent of search. Tap to open the paginated GET /teachers/students/needs-attention screen for the full detail list. |
| recentUploads | Last 5 Excel score uploads with status, file name, rows processed, and timestamp. For full history use the paginated GET /teachers/upload-logs. |
| Property | What It Means & How to Display |
|---|---|
| id | Teacher's unique identifier. Use for API calls that require a teacher ID. Not typically displayed. |
| fullName | Display name shown across the app — in headers, conversation, student views. Show as the main profile name. |
| Login email. Show in profile settings. Used for notifications and password reset. | |
| username | Unique handle. Could be shown as @ahmed_h in profiles. |
| phoneNumber | Contact phone in E.164 format. Show in contact section. Parents may use this to reach the teacher. |
| dateOfBirth | Date of birth. Format as "Jun 15, 1985" or compute age: "40 years old". |
| nationalId | National ID number. Mask in UI: "2850****5678". Only visible to the teacher themselves. |
| subscriptionPlan | Current plan: "Free", "Pro", or "Premium". Show as colored chip. Controls feature access — e.g. Free plan may limit group count. |
| subscriptionEnd | When subscription expires. Show as "Expires Mar 1, 2026" or "Renews in 5 months". Null for free plan. |
| bio | Short biography. Show as multi-line text in profile. Parents and students can see this. |
| rating | Teacher rating on 0-5 scale. Display as 5 stars (filled/empty). Use for sorting in teacher discovery. |
| subjectName | Primary subject taught. Show as chip/badge. E.g. "Mathematics" |
| createdAt | When the account was created. Format as "Member since Jan 2024". Shows tenure/experience. |
| Field | Description |
|---|---|
| fullName | Updated display name (required) |
| phoneNumber | Updated phone number (required) |
| New email address (optional). If changed, triggers re-verification flow. | |
| bio | Updated biography (optional, teacher-only field) |
| Property | What It Means & How to Display |
|---|---|
| id | Group's unique identifier. Use for all group-related API calls (sessions, students, uploads). |
| name | Display name shown to students and parents. E.g. "Math 101 - Section A". Show as the group card title. |
| academicYear | Academic year string. E.g. "2025-2026". Show as chip/badge on group cards. |
| gradeLevel | Egyptian secondary school year. Values: "SecondaryOne" (1st year), "SecondaryTwo" (2nd year), "SecondaryThree" (3rd year). Show as chip. Used for filtering and grouping. |
| period | Teaching period. Values: "First Term" or "Second Term". Show as chip. Used for organizing sessions within an academic year. |
| isActive | Whether the group is active. Inactive groups are soft-deleted and hidden from student-facing lists. Show inactive groups grayed out with an "Archived" badge. |
| subjectName | The subject this group teaches. E.g. "Mathematics". Show as chip. Used for filtering. |
| studentCount | Number of currently enrolled students. Show as stat: "30 students". Tap to see the student list. |
| averageScore | Average score across all students in this group (0-100). Display as progress bar. Color: green ≥80, yellow 60-79, red <60. |
| attendanceRate | What % of students attended sessions in this group. Display as percentage. Below 70% should be flagged in red. |
| activeSessions | Number of sessions conducted in this group. Shows activity level. |
| lastUploadDate | When the last score sheet was uploaded. Show as relative time: "2 days ago". Null if never uploaded. |
| trend | Score direction: "up" (improving), "down" (declining), "stable". Show as arrow icon. Determined by comparing current avg score to threshold: ≥70 = up, ≥50 = stable, <50 = down. |
| schedules | Weekly schedule entries for this group. Shows which days/times the group meets. Null if no schedules configured. Show as day chips: "Mon 8:00-10:00". |
| Field | Description |
|---|---|
| name | Group display name, max 200 characters. E.g. "Math 101 - Section A" |
| academicYear | Academic year string. E.g. "2025-2026" |
| gradeLevel | Enum value: "SecondaryOne", "SecondaryTwo", or "SecondaryThree" |
| period | Must be "First Term" or "Second Term" (enforced by database CHECK constraint) |
| subjectId | UUID of an existing subject. Must match a subject from GET /subjects |
| schedules | Optional list of schedule entries. Each has: dayOfWeek (e.g. "Monday"), startTime (e.g. "08:00"), endTime (e.g. "10:00") |
| Field | Description |
|---|---|
| name | New group name (optional) |
| academicYear | New academic year (optional) |
| gradeLevel | New grade level (optional) |
| period | New period (optional) |
| subjectId | New subject (optional) |
| isActive | Set to false to soft-delete/archive the group (optional) |
| Field | Description |
|---|---|
| newName | Name for the cloned group. E.g. "Math 101 - Section B" |
| Field | Description |
|---|---|
| dayOfWeek | Day name: "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" |
| startTime | Start time in HH:mm format. E.g. "08:00" |
| endTime | End time in HH:mm format. Must be after startTime. E.g. "10:00" |
| Property | What It Means & How to Display |
|---|---|
| studentId | Student's unique ID. Use for enrollment, assessment, and notification calls. |
| studentName | List item title. |
| studentCode | Monospace subtitle for identification. |
| enrollmentStatus | Pending amber chip + "Approve" action; Active green; removed rows never appear here. |
| attendancePercentage | 0–100. Show a ring/percentage; flag <70% red. |
| averageWrittenScore / averageAssignmentScore | 0–100. Two compact stat tiles. |
| overallProgressPercentage | 0–100. Primary progress bar. |
| recentAssessments[].rawScore / maxScore | Render as 18/20; scorePercentage as 90%. |
my-students filter includes pending and active memberships; available lists students without a membership in this teacher's groups. Pending students report isEnrolled: false and zeroed metrics. Use this for the "Find Students" or "My Students" screen.| Param | Description |
|---|---|
| search | Case-insensitive contains on the student's full name or email (NOT the student code) |
| filter | my-students (default) = pending + active in my groups, available = no membership in my groups. Any other value (or empty) falls back to my-students |
| page | Page number (default: 1) |
| pageSize | Items per page (default: 20, max: 100) |
| Field | Display guidance |
|---|---|
| isEnrolled | true only with an Active membership. Show a "Pending approval" chip when false but enrolledGroups[].status == "Pending" |
| enrolledGroups[].status | Pending amber, Active green, Removed grey |
| stats.pendingApprovalCount | Badge count on the "Approve" action |
| stats.averageScore / attendanceRate | Both 0–100; hidden or "—" when stats.totalSessions == 0 |
| stats.lastActivityDate | Relative time ("3 days ago"); null for pending students |
alreadyEnrolledWithTeacher tells you whether the code is already in one of your groups. The response is the minimal StudentLookupResponseDto — email and contact data are deliberately excluded. Unknown code returns 404.| Field | Display guidance |
|---|---|
| studentId | Pass to POST /teachers/groups/{groupId}/students |
| fullName | Confirmation card title |
| studentCode | Monospace subtitle |
| alreadyEnrolledWithTeacher | true → disable enroll button and show "Already in your groups" |
| Param | Description |
|---|---|
| groupId | Filter progress to one of the teacher's groups (optional). Must be a group owned by the caller. |
| Field | Display guidance |
|---|---|
| perGroupProgress[].enrollmentStatus | Same color mapping as elsewhere (Pending/Active/Removed) |
| perGroupProgress[].attendancePercentage | 0–100; show a progress ring |
| perGroupProgress[].recentAssessments[].rawScore / maxScore | Render as 18/20 |
| perGroupProgress[].recentAssessments[].kind | Written / Assignment / Quiz / Oral / Other badge |
| perGroupProgress[].recentAssessments[].scorePercentage | Render as 90%; color by band |
| Field | Description |
|---|---|
| parentId | Parent user ID |
| fullName | Parent's full name |
| Parent's email address | |
| phoneNumber | Parent's phone number |
studentCode (generated once, never overwritten). Approve via PUT /teachers/groups/{groupId}/students/{studentId}/approve.| Field | Description |
|---|---|
| studentId | UUID of the student (required) |
| Field | Display guidance |
|---|---|
| studentCode | Show in a confirmation sheet so the student can share it |
| isEnrolled | false until approval — label the result "Pending approval" |
| enrolledGroups[].status | Pending at this point |
| Param | Description |
|---|---|
| groupId | UUID of the group to enroll into (required) |
Removed with removedAt, while every past session and grade stays readable by the student and parent. Only live metrics and rosters drop the student. Re-enrolling later reactivates the same row as Pending and keeps the existing code.Pending membership in a specific group (Status → Active, stamps approvedAt). This is a group-scoped workflow and does not toggle User.IsActive. Approving an already-active membership is idempotent.| Field | Display guidance |
|---|---|
| groupId | From the selected group row — approval is per group, not per student |
| studentId | From the pending student row |
| message | Show a snackbar, then refresh the roster |
PaginatedResult<StudentSessionResponseDto> — the same item shape the student sees in their own history. Supports only page and pageSize; there is no groupId, topic, date, or type filter. Group by sessionId client-side to rebuild a session list.| Param | Description |
|---|---|
| page / pageSize | Paging (default 1 / 20, max 100) |
| Field | Description |
|---|---|
| items[].sessionId | Session identifier — group rows by this to build a session list |
| items[].studentId / studentName / studentCode | Student identity for the row; null when the row is not student-scoped |
| items[].groupName / sessionDate / sessionType | Session metadata |
| items[].attended | Attendance for this student in this session |
| items[].score / maxScore / scorePercentage | Raw score, max, and percentage of this student's best assessment in the session (null when no assessment) |
| items[].assessments[] | Individual assessments with assessmentId, kind, rawScore, maxScore, scorePercentage |
| Field | Description |
|---|---|
| sessionDate | When the session took place. UTC date. E.g. "2025-09-28" |
| sessionType | Type of session. Values: "Exam" (formal test), "Quiz" (short pop quiz), "Homework" (assignment), "Oral" (verbal exam), "Attendance" (no grade, just tracking). Determines which icons/labels to show in the UI. |
| topic | What was covered. E.g. "Algebra Basics - Chapter 1". Show as the session title. |
| description | Optional additional details about the session content. |
| headers | Optional list of custom assessment columns for the Excel upload. Each header has: key (internal ID), label (display name), labelArabic (Arabic name), columnType (Score/Attendance/Comment), maxScore, sortOrder, isRequired. |
| copyHeadersFromSessionId | UUID of another session to copy headers from. Useful for repeating the same assessment structure across sessions. |
| Property | What It Means & How to Display |
|---|---|
| sessionId | Session identifier — required for update, delete, report, and export calls. |
| sessionType | Determines the icon and label. Exam = clipboard icon, Quiz = lightning bolt, Homework = pencil, Oral = microphone, Attendance = calendar check. |
| studentSessions[].studentSessionId | Identifier of the student-session join row. Use it when updating attendance rows. |
| studentSessions[].attended | Whether the student was present. Show as checkmark (green) or X (red) in the attendance column. |
| studentSessions[].assessments[].assessmentId | Identifier for PUT /teachers/sessions/assessments/{assessmentId}. |
| studentSessions[].assessments[].kind | Written | Assignment | Quiz | Oral | Other. |
| studentSessions[].assessments[].rawScore | Score in the scale of maxScore. Render "95 / 100", not a percentage. |
| studentSessions[].assessments[].scorePercentage | Score as a percentage (0-100). Display as colored number: green ≥80, yellow 60-79, red <60. |
| studentSessions[].assessments[].teacherComment | Teacher's feedback on the student's performance. Display as tooltip or subtitle under the score. |
| Field | Description |
|---|---|
| sessionDate | New date (optional) |
| topic | New topic (optional) |
| description | New description (optional) |
| sessionType | New type (optional) |
| Field | Description |
|---|---|
| studentId | UUID of the student being assessed |
| examName | Name of the assessment. E.g. "Midterm", "Quiz 1", "Homework Chapter 1" |
| scorePercentage | Optional percentage (0-100). Ignored when rawScore is supplied. |
| rawScore | Optional raw score in the scale of maxScore. When supplied, the percentage is derived server-side and wins over scorePercentage. |
| maxScore | Maximum possible score (1-1000). E.g. 100. |
| kind | Written (default) | Assignment | Quiz | Oral | Other. |
| teacherComment | Feedback text. E.g. "Excellent work!" or "Needs to practice more". Show to the student/parent. |
| Field | Description |
|---|---|
| scorePercentage | New percentage (optional). Ignored when rawScore is supplied. |
| rawScore | New raw score (optional). Converted to a percentage with the assessment's existing maxScore; wins over scorePercentage. |
| teacherComment | New comment (optional). E.g. "Updated: Good improvement" |
SessionAvgScoreResponseDto object.| Property | What It Means & How to Display |
|---|---|
| key | Internal identifier for this column. Used in Excel files and API calls. E.g. "quiz1" |
| label | Display name in English. Show as column header. E.g. "Quiz 1" |
| labelArabic | Display name in Arabic. Show when the app language is set to Arabic. |
| columnType | What kind of data this column holds: "Score" (numeric), "Attendance" (boolean), "Comment" (text). Determines the input widget. |
| maxScore | Maximum possible score for this column. Null for Attendance and Comment types. Show as "/20" next to the score input. |
| isRequired | Whether this column must be filled. Show as asterisk (*) next to the label if required. |
{ "headers": [...] } with 1–8 entries, at least one Score column, at most 5 score columns, and at most 1 attendance / 1 comment column. Returns 409 Conflict if scores already exist for this session (to prevent breaking existing data). There is no copyHeadersFromSessionId field on this endpoint — fetch another session's headers and resend them.| Field | Description |
|---|---|
| file | The Excel file. Accepted formats: .xlsx, .xls, .csv. Max 10MB. Path traversal in filenames is rejected. |
| groupId | UUID of the group this upload is for. The teacher must own this group. |
| Param | Description |
|---|---|
| groupId | UUID of the group (required). Determines student list in the template. |
| sessionId | UUID of the session (optional). If provided, enables session-bound mode with custom column headers. |
| Param | Description |
|---|---|
| uploadStatuses | Repeatable status filter: Queued, Processing, Completed, Failed. Omit to show all. Use it for "Failed only" views and status badge counts. |
| page | Page number, starting at 1. Default 1. |
| pageSize | Items per page. Default 20, max 100. |
| Property | What It Means & How to Display |
|---|---|
| status | Processing state: "Queued" (waiting), "Processing" (in progress), "Completed" (done), "Failed" (error). Show as colored badge: gray=Queued, yellow=Processing, green=Completed, red=Failed. |
| rawProcessed | Number of student rows successfully processed. Show as "30 students imported". |
| rawFailed | Number of rows that failed. If > 0, show warning: "2 rows failed". Tap to see error details. |
| uploadedAt | When the file was uploaded. Show as relative time: "2 hours ago". |
| completedAt | When processing finished. Null if still processing. Show as duration: "Processed in 5s". |
| Property | What It Means & How to Display |
|---|---|
| type | Notification category. Values: "NewGrade" (score published), "RankChange" (leaderboard position changed), "BadgeEarned" (achievement unlocked), "PaymentConfirmed" (payment verified), "SystemAlert" (platform announcement). Use to determine the icon and tap action. |
| isRead | Whether the teacher has seen this notification. Show unread ones with bold title and blue dot indicator. |
| payload | JSON string with context data (e.g. sessionId, studentId). Parse this to determine what to open when the notification is tapped. |
| sentAt | When the notification was sent. Show as relative time: "2 hours ago". |
| Field | Description |
|---|---|
| studentId | UUID of the student (the notification goes to their parent) |
| title | Notification title. E.g. "Great Job, Sara!" |
| body | Notification body. E.g. "Sara scored 95% on the Algebra midterm." |
| type | Enum: "NewGrade", "RankChange", "BadgeEarned", "PaymentConfirmed", "SystemAlert" |
| Field | Description |
|---|---|
| title | Notification title. E.g. "Progress Update" |
| body | Notification body. E.g. "Mid-term results for Math 101 are now available." |
LeaderboardEntryDto[] array — there is no page/pageSize and no items wrapper. Each entry includes the student's rank, scores, and contextual info like gap to top. The group is checked for existence only (404 if missing); the endpoint does not verify that the caller owns the group. Results are cached for 1 minute.| Param | Description |
|---|---|
| period | daily | weekly | monthly | alltime (default weekly) |
| Property | What It Means & How to Display |
|---|---|
| rank | Position on the leaderboard (1 = best). Display as rank badge: #1 gold, #2 silver, #3 bronze. |
| weightedScore | Combined score used for ranking. Weighted combination of avgScore, attendanceRate, and participationScore. Higher = better. |
| avgScore | Average assessment score (0-100). The raw academic performance number. |
| attendanceRate | What % of sessions the student attended (0-100). High attendance boosts ranking. |
| participationScore | Score based on session activity and engagement. Computed from session participation patterns. |
| context.totalStudents | Total students on this leaderboard. Show as "out of 30" next to the rank. |
| context.scoreDifferenceToTop | How far behind the top student this student is. E.g. 5.2 means 5.2 points behind #1. Show as "Gap: -5.2" for motivation. |
| context.rankLabel | Human-readable tier: "Gold" (top 10%), "Silver" (top 25%), "Top 10%", "Developing". Display as colored badge. |
| context.isTopPerformer | True if in the top 10%. Use to highlight top performers with special styling. |
{ items, page, pageSize, totalCount }. Optional filters: period (default alltime), gradeLevel, page, pageSize (max 100). Use this for the "Overall Rankings" screen.groupId defaults to the student's primary active group. A group the student never belonged to returns 403; a group with no computed board returns 200 with an empty page. Use this for the "My Rankings" screen.| Param | Description |
|---|---|
| groupId | Group to show (optional; defaults to the primary active group) |
| period | daily | weekly | monthly | alltime (default alltime) |
| page / pageSize | Paging for the full board (default 1 / 20, max 100) |
| Field | Display guidance |
|---|---|
| items[].rank | Rank badge: #1 gold, #2 silver, #3 bronze, rest gray |
| items[].attendanceRate | 0–100 (already multiplied) — never treat as a fraction |
| items[].context | Use isTopPerformer for a highlight row |
| Property | What It Means & How to Display |
|---|---|
| profile — Student identity | |
| profile.studentCode | Unique code the teacher uses to enroll this student. Show in profile header: "Code: STU001". Students can share this code with teachers. |
| overview — Key metrics | |
| overallAverageScore | Average score across ALL groups (0-100). The student's most important number. Display as large circular progress indicator. Color: green ≥80, yellow 60-79, red <60. |
| overallAttendancePercentage | What % of all sessions the student attended. Show as percentage bar. Below 70% = red warning. |
| totalBadges | Total achievement badges earned. Show with trophy icon: "5 badges earned". |
| currentRank | Best rank across all enrolled groups. 0 if not ranked. Show as "Rank #3 in your best group". |
| scoreTrend | Direction of scores. Values: "up", "down", "stable". Computed by splitting scores in half and comparing recent vs older half. Show as arrow icon. |
| attendanceStreak | Consecutive sessions attended. Gamification metric. Show with fire icon: "8-session streak!" |
| bestSubject | Subject with the highest average score. E.g. "Mathematics". Show as highlight: "Your best: Mathematics" |
| weakestSubject | Subject with the lowest average score. E.g. "Science". Show as improvement suggestion: "Focus on: Science" |
| improvementAreas | Auto-generated suggestions: "Improve Science scores", "Increase attendance", "Focus on exams". Show as actionable tips list. |
| predictedFinalGrade | Predicted grade based on current average. Values: "A" (≥90), "B+" (≥80), "B" (≥70), "C" (≥60), "D" (≥50), "F" (<50). Show as goal indicator. |
| subjectAnalysis — Per-subject breakdown | |
| subjectAnalysis.subjects | List of subjects with average scores and session counts. Display as bar chart or radar chart. Each bar shows subject name and score. |
| subjectAnalysis.bestSubject | Same as overview.bestSubject. Reinforces strength. |
| subjectAnalysis.weakestSubject | Same as overview.weakestSubject. Reinforces improvement area. |
| classPosition — Leaderboard position | |
| classPosition.rank | Student's rank in their best-performing group. Show as "You're #3 out of 30 in Math 101 - A". |
| classPosition.percentile | Percentile position (0-100). 90 means better than 90% of the class. Show as "Top 10%". |
| Other fields | |
| enrolledGroups | List of groups the student belongs to. Show as group cards with teacher name and subject. |
| rankings | Leaderboard position in each enrolled group. Show as rank badges on group cards. |
| recentSessions | Last 5 sessions with attendance and scores. Show as a timeline list. Each item shows group, date, type, score, and attended status. |
| recentBadges | Last 3 badges earned. Show as horizontal scroll of badge icons with names and dates. |
PaginatedResult<StudentSessionResponseDto>. A recorded attendance session with no student row counts as absent; sessions where nobody recorded attendance are excluded. Each row includes raw score, max score, percentage, and every assessment for that session. There is no summary object — compute totals client-side or read them from /students/home.| Property | What It Means & How to Display |
|---|---|
| page / pageSize / totalCount | Pagination envelope (default 1 / 20, max 100). Build the pager from totalCount. |
| items[].attended | Attendance for this session. Missing row in a recorded session is returned as false. |
| items[].score / maxScore | Raw score of the best assessment in the session — render as 19/20. Null when ungraded. |
| items[].assessments[] | All assessments for that session; render one card per assessment with kind badge and rawScore/maxScore. |
| items[].scorePercentage | 0–100. Colored number; null when ungraded. |
| items[].studentId / studentName / studentCode | Null on the student endpoint; populated on the teacher-facing list. |
isEarned and awardedAt, plus overall progress. The catalog is fixed, so this endpoint is never paginated — render it as a grid and grey out unearned badges.| Field | Display guidance |
|---|---|
| badges[].isEarned | Earned → full color + date; not earned → 40% opacity lock icon |
| badges[].metaData | Arabic/English metadata label (previously metadata — update the model) |
| badges[].awardedAt | Relative date, null when unearned |
| progress.completionPercentage | 0–100 ring at the top of the grid |
| progress.nextBadgeToEarn | Call-to-action card; null when all badges are earned |
enrollmentStatus, approvedAt, removedAt, and per-group stats. Removed memberships still appear so the student can open past work; only pending/active memberships appear in live rosters and new sessions.fullName and phoneNumber. Omitted fields stay unchanged.| Property | What It Means & How to Display |
|---|---|
| overview.totalChildren | Number of children linked to this parent account. Show as stat: "2 children" |
| overview.averageAttendance | Average attendance across all children (0-100). Show as aggregate stat card. |
| overview.averageScore | Average score across all children (0-100). The parent's overall view of academic performance. |
| overview.totalPaidEGP | Total confirmed payments in Egyptian Pounds. Show as "15,000 EGP paid". |
| overview.pendingPaymentsEGP | Total pending payments. Show with warning color if > 0: "3,500 EGP pending". |
| children.*.attendancePercentage | This child's attendance rate (0-100). Show as percentage on the child card. |
| children.*.averageScore | This child's average score (0-100). Show as progress bar on the child card. |
| children.*.currentRank | Best rank across groups. Null if not ranked. Show as "Rank #3". |
| children.*.scoreTrend | Score direction: "up", "down", "stable". Show as arrow icon on child card. |
| children.*.bestSubject | Subject with highest score. Show as "Best: Mathematics". |
| recentPayments | Last 3 payment records. Show as payment history list with status badges. |
| recentPayments.*.status | "Pending", "Confirmed", or "Disputed". Show as colored badge. |
enrollmentStatus) and stats computed in SQL. Attendance counts only recorded attendance sessions — a missing row counts as absent. Use this for the "My Children" screen.| Field | Display guidance |
|---|---|
| groups[].enrollmentStatus | Pending amber, Active green, Removed grey — removed groups stay visible for history |
| stats.attendanceRate | 0–100; flag <70% amber, <60% red |
| stats.scoreTrend | "up" / "down" / "stable" arrow next to the average |
| stats.bestSubject | Highlight chip on the child card |
assessments[] array (raw score, max score, percentage). A recorded attendance session with no student row counts as absent. Same data the student sees, scoped to this parent.?groupId=). A group the child never belonged to returns 403; no computed board returns 200 with an empty page. attendanceRate is 0–100. Use this for the "Child Rankings" screen.| Field | Description |
|---|---|
| teacherId | UUID of the teacher being paid (required) |
| studentId | UUID of the student this payment is for (required) |
| amountEGP | Payment amount in Egyptian Pounds, > 0 (required). E.g. 1500.00 |
| periodMonth | Month this payment covers. E.g. "2025-09-01". Null if not period-specific. |
| notes | Optional description. E.g. "September tutoring fee" |
{ payments: PaginatedResult<PaymentResponseDto>, summary: PaymentSummaryDto }. The list is nested under payments.items — there is no top-level items. pageSize defaults to 10 (max 100). summary holds aggregate totals and a monthly breakdown for trend charts. Use this for the "Payment History" screen.{ payments: PaginatedResult<PaymentResponseDto>, summary: PaymentSummaryDto } envelope. pageSize defaults to 10 (max 100). Shows which students/parents have paid, pending, or disputed. Use this for the teacher's "Payments" screen.| Field | Description |
|---|---|
| confirmed | true = confirm receipt, false = dispute the payment |
| notes | Optional note. E.g. "Received cash" or "Amount doesn't match agreed fee" |
| Property | What It Means & How to Display |
|---|---|
| totalUsers | Total registered users across all roles. Show as main stat card. |
| totalTeachers / totalStudents / totalParents | User counts by role. Show as breakdown cards or pie chart. |
| activeUsers / inactiveUsers | Active vs deactivated accounts. Show as toggle stat or bar chart. |
| verifiedUsers / unVerifiedUsers | Email verification status. High unverified count may indicate onboarding issues. |
| usersThisWeek | New registrations in the last 7 days. Show as growth metric. |
| growthPercent | Week-over-week registration growth. Show as "+12.5%" with arrow. |
| registrationTrend | Daily registration counts for the last 30 days. Show as line chart. |
| totalRevenueEGP | Total confirmed revenue in EGP. Show as main revenue card. |
| pendingRevenueEGP | Revenue awaiting teacher confirmation. Show as secondary card. |
| platformAvgScore | Average score across all sessions platform-wide. Show as performance indicator. |
| platformAttendanceRate | Average attendance rate platform-wide. Show as health metric. |
| recentUploads | Last 5 file uploads across all teachers. Show as activity feed. |
| topStudents | Top 5 students by score. Show as leaderboard card. |
| topTeachers | Top 5 teachers by student scores. Show as teacher rankings. |
{ items, page, pageSize, totalCount } envelope. Each subject has English and Arabic names. Use this to populate subject dropdowns when creating or editing groups. Teachers and admins can see all subjects. Pass ?searchName= to filter by name (case-insensitive contains); blank or omitted means no filtering.| Param | Description |
|---|---|
| searchName | Case-insensitive contains filter on the English name. Omit or blank for all. Use it for searchable subject dropdowns. |
| includeInactive | Admins only: true includes deactivated subjects. Ignored for non-admin roles. |
| page | Page number, starting at 1. Default 1. |
| pageSize | Items per page. Default 20, max 100. |
| Property | What It Means & How to Display |
|---|---|
| id | Subject UUID. Use when creating a group (subjectId field in CreateGroupRequestDto). |
| name | English name. E.g. "Mathematics". Show in English-language UI. |
| nameArabic | Arabic name. E.g. "الرياضيات". Show when app language is set to Arabic. |
| isActive | Whether the subject is available. Inactive subjects are hidden from dropdowns. |
| Field | Description |
|---|---|
| name | English name, max 100 characters. E.g. "Mathematics" |
| nameArabic | Arabic name, max 100 characters. E.g. "الرياضيات" |
role accepts teacher (default), student, parent, admin. Registered only in Development and returns 404 elsewhere. Requires the ComprehensiveSeeder to have run.Healthy whenever the process is running. No authentication and no database check. Use it for uptime monitors and container liveness probes.ready-tagged checks (database connectivity and Hangfire server availability). Returns 200 when healthy and 503 otherwise, with a JSON status payload.