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.
https://edutrackonline.savekiteg.com
All endpoints are prefixed with /api/.
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_tokenandrefresh_tokenare 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:
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:
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_tokenandrefresh_tokencookies 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:
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."} |
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:
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"} |
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:
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:
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:
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"} |
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:
school_type, grade, division, school_name, father_number, mother_number.area must belong to the selected governorate.student_code is auto-generated and guaranteed unique.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"
}
]
}
]
}
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"} |
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"
}
]
}
Authentication: Public
Success Response — 200 OK:
{
"id": "Integer",
"name": "String"
}
Error Responses:
| Status | Condition | Response Body |
|---|---|---|
404 |
Grade not found | {"error": "Grade not found"} |
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"
}
]
}
Authentication: Public
Success Response — 200 OK:
{
"id": "Integer",
"name": "String"
}
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"
}
]
}
Authentication: Public
Success Response — 200 OK: Same structure as list item.
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"
}
],
}
]
}
Authentication: Public
Success Response — 200 OK: Same structure as list item.
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"
}
]
}
Authentication: Public
Success Response — 200 OK:
{
"id": "Integer",
"name": "String"
}
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"
}
]
}
Authentication: Public
Success Response — 200 OK:
{
"id": "Integer",
"name": "String",
"governorate_id": "Integer - Governorate ID",
"governorate_name": "String - Governorate name"
}
Description: List all chapters. Filter by subject and/or grade.
Authentication: None (public read)
Query Parameters:
subject — Filter by subject IDgrade — Filter by grade IDSuccess 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"
}
]
}
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)"
}
Description: List all lessons. Filter by chapter.
Authentication: None (public read)
Query Parameters:
chapter — Filter by chapter IDDescription: Retrieve a single lesson.
Authentication: None (public read)
Description: Get all chapters with nested lessons for a specific subject + grade combination.
Authentication: None (public read)
Query Parameters:
subject (required) — Subject IDgrade (required) — Grade IDSuccess 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"}
]
}
]