EduTrack Online - Public API

Version: 1.1
Date: April 29, 2026
Target Audience: Next.js Frontend Developers
Backend: Django REST Framework

This file documents endpoints that require NO authentication.


Base URL

https://edutrackonline.savekiteg.com

All endpoints are prefixed with /api/.


1. Authentication


POST /accounts/login/

Description: Authenticates a user with username and password. Returns user data in the JSON body and sets JWT tokens as HTTP-Only cookies.

Authentication: Public (no login required)

Content-Type: application/json

Request Body:

{ "username": "String (Required)", "password": "String (Required)" }

Success Response — 200 OK:

{ "role": "String (siteowner|teacher|assistant|student)", "name": "String", "is_active": "Boolean" }

Note: access_token and refresh_token are set as HTTP-Only cookies and are NOT in the response body.

Error Responses:

Status Condition Response Body
400 Missing username or password {"error": "String"}
401 Invalid credentials {"error": "No active account found with the given credentials"}
401 Account inactive (teacher/assistant) {"error": "Your account is inactive. Please contact an administrator."}
401 Assistant's teacher inactive {"error": "Your assigned teacher's account is inactive..."}

Business Rules:


POST /accounts/logout/

Description: Blacklists the refresh token and clears both JWT cookies from the browser.

Authentication: Any authenticated user

Content-Type: application/json

Request Body: None

Success Response — 200 OK:

{ "message": "Successfully logged out" }

Error Responses:

Status Condition Response Body
401 Not authenticated {"detail": "Authentication credentials were not provided."}

Business Rules:


POST /accounts/token/refresh/

Description: Rotates the refresh token and issues new access and refresh tokens. Reads the refresh token from the cookie.

Authentication: Public (reads token from cookie)

Content-Type: application/json

Request Body: None

Success Response — 200 OK:

{ "role": "String (siteowner|teacher|assistant|student)", "name": "String", "is_active": "Boolean" }

Note: New access_token and refresh_token cookies are set.

Error Responses:

Status Condition Response Body
401 No refresh token in cookie {"error": "Refresh token not found"}
401 Invalid/expired refresh token {"error": "Invalid or expired refresh token"}
401 User not found {"error": "User not found"}
401 Inactive teacher/assistant {"error": "Your account is inactive..."}
401 Assistant's teacher inactive {"error": "Your assigned teacher's account is inactive..."}

Business Rules:


GET /accounts/me/

Description: Returns the current user's role, name, and active status. Used by the frontend to restore session state on page refresh.

Authentication: Any authenticated user

Content-Type: application/json

Request Body: None

Success Response — 200 OK:

{ "role": "String (siteowner|teacher|assistant|student)", "name": "String", "is_active": "Boolean" }

Error Responses:

Status Condition Response Body
401 Not authenticated {"detail": "Authentication credentials were not provided."}

2. Password Management


POST /accounts/forgot-password/

Description: Requests a password reset OTP. Always returns success (even if email doesn't exist) to prevent email enumeration attacks.

Authentication: Public

Content-Type: application/json

Request Body:

{ "email": "String (Required) — Email address associated with the account" }

Success Response — 200 OK:

{ "message": "If an account exists with this email, you will receive a reset code." }

Error Responses:

Status Condition Response Body
400 Missing email {"error": "Email is required"}

Business Rules:


POST /accounts/verify-otp/

Description: Verifies the OTP code sent to the user's email. Returns a reset_token that should be used in the subsequent password reset call.

Authentication: Public

Content-Type: application/json

Request Body:

{ "email": "String (Required)", "otp": "String (Required) — 6-digit code" }

Success Response — 200 OK:

{ "valid": "boolean - Whether the OTP is valid", "reset_token": "String — 32-character hex token", "message": "OTP verified successfully" }

Error Responses:

Status Condition Response Body
400 Missing email or OTP {"error": "Email and OTP are required"}
400 OTP expired {"valid": false, "error": "OTP has expired"}
400 Invalid OTP {"valid": false, "error": "Invalid OTP"}

POST /accounts/reset-password/

Description: Resets the user's password using the verified OTP and optional reset token. Invalidates all existing refresh tokens, forcing re-login on all devices.

Authentication: Public

Content-Type: application/json

Request Body:

{ "email": "String (Required)", "otp": "String (Required) — 6-digit code", "reset_token": "String (Optional) — From verify-otp response", "new_password": "String (Required) — Minimum 8 characters" }

Success Response — 200 OK:

{ "message": "Password reset successfully. Please log in again." }

Error Responses:

Status Condition Response Body
400 Missing required fields {"error": "Email, OTP, and new password are required"}
400 Password too short {"error": "Password must be at least 8 characters"}
400 OTP expired {"error": "OTP has expired"}
400 Invalid reset token {"error": "Invalid reset token"}
400 Invalid OTP {"error": "Invalid OTP"}

Business Rules:


POST /accounts/change-password/

Description: Allows an authenticated user to change their password. Requires the current password for verification. Invalidates all refresh tokens on success.

Authentication: Any authenticated user

Content-Type: application/json

Request Body:

{ "old_password": "String (Required) — Current password", "new_password": "String (Required) — Minimum 8 characters", "new_password_confirm": "String (Required) — Must match new_password" }

Success Response — 200 OK:

{ "message": "Password changed successfully. Please log in again." }

Error Responses:

Status Condition Response Body
400 Missing fields {"error": "All password fields are required"}
400 Passwords don't match {"error": "New passwords do not match"}
400 Password too short {"error": "Password must be at least 8 characters"}
400 Wrong old password {"error": "Current password is incorrect"}
401 Not authenticated {"detail": "Authentication credentials were not provided."}

Business Rules:


3. Email Verification


POST /accounts/verify-email/request/

Description: Requests an email verification OTP.

Authentication: Public

Content-Type: application/json

Request Body:

{ "email": "String (Required) — Email address to verify" }

Success Response — 200 OK:

{ "message": "Verification code sent to your email." }

Error Responses:

Status Condition Response Body
400 Missing email {"error": "Email is required"}

Business Rules:


POST /accounts/verify-email/confirm/

Description: Confirms email ownership by verifying the OTP code.

Authentication: Public

Content-Type: application/json

Request Body:

{ "email": "String (Required)", "otp": "String (Required) — 6-digit code" }

Success Response — 200 OK:

{ "verified": "boolean - Whether the email is verified", "message": "Email verified successfully" }

Error Responses:

Status Condition Response Body
400 Missing fields {"error": "Email and OTP are required"}
400 OTP expired {"verified": false, "error": "OTP has expired"}
400 Invalid OTP {"verified": false, "error": "Invalid OTP"}

4. Student Registration


POST /accounts/student/register/

Description: Register a new student account. Creates both a User and StudentProfile atomically.

Authentication: Public

Content-Type: multipart/form-data

Request Body:

{ "username": "String (Required) — Unique, case-insensitive", "password": "String (Required) — Minimum 8 characters", "password_confirm": "String (Required) — Must match password", "name_ar": "String (Required) — Arabic name", "name_en": "String (Required) — English name", "phone_number": "String (Required) — Egyptian format", "father_number": "String (Optional) — Required for school students", "mother_number": "String (Optional) — Required for school students", "father_job": "String (Required)", "educational_state": "String (Required) — school", "school_type": "Integer (Optional) — Required for school students", "grade": "Integer (Optional) — Required for school students", "division": "Integer (Optional) — Required for school students", "school_name": "String (Optional) — Required for school students", "national_id": "String (Required) — 14 digits", "birth_date": "Date (YYYY-MM-DD) (Required)", "gender": "String (Required) — male|female", "gmail": "String (Required) — Must be globally unique", "facebook_link": "String (Optional)", "governorate": "Integer (Required)", "area": "Integer (Required) — Must belong to the selected governorate" }

Success Response — 201 Created:

{ "message": "String - Success message", "student_code": "String - Auto-generated 7-digit code", "username": "String - Student's username", "status": "String - Always 'pending'" }

Error Responses:

Status Condition Response Body
400 Username exists {"username": ["Username already exists."]}
400 Email exists {"gmail": ["This email is already associated with an account."]}
400 Passwords don't match {"password_confirm": ["Passwords do not match."]}
400 Missing school fields {"educational_state": ["School type and grade are required..."]}
400 Missing division {"division": ["Division is required for school students."]}
400 Missing school name {"school_name": ["School name is required for school students."]}
400 Missing parent phones {"father_number": ["Father phone number is required..."]}
400 Area doesn't match governorate {"area": ["The selected area does not belong to the governorate..."]}

Business Rules:


5. Public Teacher Listings


GET /accounts/public/teachers/

Description: List all active teachers. Supports filtering by subject and grade.

Authentication: Public

Query Parameters:

Parameter Type Description
subject Integer Filter by subject ID
grades Integer Filter by grade ID
search String Search by name or subject name
all String Set to true to bypass pagination

Success Response — 200 OK:

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String", "profile_picture": "String (URL) | null", "subject_detail": { "id": "Integer", "name": "String" }, "grades_detail": [ { "id": "Integer", "name": "String" } ] } ] }

GET /accounts/public/teachers//

Description: Get detailed public profile for a single teacher.

Authentication: Public

Success Response — 200 OK:

{ "id": "Integer", "name": "String", "profile_picture": "String (URL) | null", "biography": "String | null", "facebook": "String | null", "subject_detail": { "id": "Integer", "name": "String" }, "grades_detail": [ { "id": "Integer", "name": "String" } ] }

Error Responses:

Status Condition Response Body
404 Teacher not found {"error": "Teacher not found"}

6. Educational Structure (Read-Only GET)


Grades

GET /accounts/grades/

Description: List all grades. Supports search and pagination bypass.

Authentication: Public

Query Parameters:

Parameter Type Description
search String Filter by name (case-insensitive)
all String Set to true to bypass pagination
page Integer Page number
page_size Integer Items per page

Success Response — 200 OK:

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String" } ] }

GET /accounts/grades//

Authentication: Public

Success Response — 200 OK:

{ "id": "Integer", "name": "String" }

Error Responses:

Status Condition Response Body
404 Grade not found {"error": "Grade not found"}

School Types

GET /accounts/school-types/

Description: List all school types. Same pattern as grades.

Authentication: Public

Query Parameters: Same as grades (search, all, page, page_size)

Success Response — 200 OK:

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String" } ] }

GET /accounts/school-types//

Authentication: Public

Success Response — 200 OK:

{ "id": "Integer", "name": "String" }

Divisions

GET /accounts/divisions/

Description: List all divisions with nested grades and school types.

Authentication: Public

Query Parameters:

Parameter Type Description
school_type Integer Filter by school type ID
grade Integer Filter by grade ID
search String Filter by name
all String Set to true to bypass pagination

Success Response — 200 OK:

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String", "grades_detail": [ { "id": "Integer", "name": "String" } ], "school_types_detail": [ { "id": "Integer", "name": "String" } ], "grade_ids": "Array[Integer] - Grade IDs", "school_type_ids": "Array[Integer] - School type IDs" } ] }

GET /accounts/divisions//

Authentication: Public

Success Response — 200 OK: Same structure as list item.


Subjects

GET /accounts/subjects/

Description: List all subjects with nested grades, divisions, and school types. This is the public catalog that unauthenticated users can browse.

Authentication: Public

Query Parameters:

Parameter Type Description
school_type Integer Filter by school type ID
grade Integer Filter by grade ID
division Integer Filter by division ID
search String Filter by name
all String Set to true to bypass pagination

Success Response — 200 OK:

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String", "grade_ids": "Array[Integer] - Grade IDs", "grades_detail": [ { "id": "Integer", "name": "String" } ], "division_ids": "Array[Integer] - Division IDs", "divisions_detail": [ { "id": "Integer", "name": "String" } ], "school_type_ids": "Array[Integer] - School type IDs", "school_types_detail": [ { "id": "Integer", "name": "String" } ], } ] }

GET /accounts/subjects//

Authentication: Public

Success Response — 200 OK: Same structure as list item.


7. Location Lookups (Read-Only GET)


Governorates

GET /accounts/governorates/

Description: List all governorates.

Authentication: Public

Success Response — 200 OK:

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String" } ] }

GET /accounts/governorates//

Authentication: Public

Success Response — 200 OK:

{ "id": "Integer", "name": "String" }

Areas

GET /accounts/areas/

Description: List all areas (filterable by governorate).

Authentication: Public

Query Parameters:

Parameter Type Description
governorate Integer Filter by governorate ID

Success Response — 200 OK:

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String", "governorate_id": "Integer - Governorate ID", "governorate_name": "String - Governorate name" } ] }

GET /accounts/areas//

Authentication: Public

Success Response — 200 OK:

{ "id": "Integer", "name": "String", "governorate_id": "Integer - Governorate ID", "governorate_name": "String - Governorate name" }

8. Curriculum (Read-Only GET)


Chapters

GET /curriculum/chapters/

Description: List all chapters. Filter by subject and/or grade.

Authentication: None (public read)

Query Parameters:

Success Response — 200 OK:

{ "count": "integer - Number of items", "results": [ { "id": "integer - Unique identifier", "subject": "integer - Subject ID", "subject_name": "string - Subject name", "grade": "integer - Grade ID", "grade_name": "string - Grade name", "name": "string - Chapter name", "order": "integer - Sort order", "lesson_count": "integer - Number of lessons" } ] }

GET /curriculum/chapters//

Description: Retrieve a single chapter with nested lessons.

Authentication: None (public read)

Success Response — 200 OK:

{ "id": "integer - Unique identifier", "subject": "integer - Subject ID", "subject_name": "string - Subject name", "grade": "integer - Grade ID", "grade_name": "string - Grade name", "name": "string - Chapter name", "order": "integer - Sort order", "lessons": [ {"id": "integer - Unique identifier", "name": "string - Lesson name", "order": "integer - Sort order"}, {"id": "integer - Unique identifier", "name": "string - Lesson name", "order": "integer - Sort order"} ], "created_at": "DateTime (ISO 8601)", "updated_at": "DateTime (ISO 8601)" }

Lessons

GET /curriculum/lessons/

Description: List all lessons. Filter by chapter.

Authentication: None (public read)

Query Parameters:


GET /curriculum/lessons//

Description: Retrieve a single lesson.

Authentication: None (public read)


Curriculum by Subject & Grade

GET /curriculum/by-subject-grade/?subject=&grade=

Description: Get all chapters with nested lessons for a specific subject + grade combination.

Authentication: None (public read)

Query Parameters:

Success Response — 200 OK:

[ { "id": "integer - Unique identifier", "subject": "integer - Subject ID", "subject_name": "string - Subject name", "grade": "integer - Grade ID", "grade_name": "string - Grade name", "name": "string - Chapter name", "order": "integer - Sort order", "lessons": [ {"id": "integer - Unique identifier", "name": "string - Lesson name", "order": "integer - Sort order"}, {"id": "integer - Unique identifier", "name": "string - Lesson name", "order": "integer - Sort order"} ] } ]