EduTrack Online - Public API

Version: 1.3 Date: July 16, 2026 Target Audience: Next.js Frontend Developers
Backend: Django REST Framework

This file documents endpoints that require NO authentication.


Base URL

https://apionline.edutrackeg.com

1. Authentication


POST /accounts/login/

Description: Authenticates a user with username, email, gmail, or student phone number + password. Returns user data in the JSON body and sets JWT tokens as HTTP-Only cookies.

Cookies set on success:

Both tokens contain role in their JWT payload. The frontend can decode the access token to read user_id and role without additional API calls.

Authentication: Public (no login required)

Content-Type: application/json

Request Body:

{ "username": "String (Required) — Username, email, gmail, or student phone number", "password": "String (Required)" }

Login Identifier Resolution: The username field accepts any of the following (resolved in order):

Input type Example Works for
Username "hazem123" All roles
Email "hazem@gmail.com" All roles (matches User.email)
Gmail "hazem@gmail.com" All roles (matches profile's gmail field)
Phone number "01012345678" Students only (matches StudentProfile.phone_number)

Success Response — 200 OK:

{ "role": "String (siteowner|teacher|assistant|student)", "name": "String", "is_active": "Boolean", "student_code": "String | null — Only for students. The 7-digit student identifier.", "status": "String | null — Only for students: pending|verified|declined|suspended_temporary|suspended_permanent" }

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 Permanently suspended student {"error": "Your account has been permanently suspended."}
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. Also refreshes the user-role cookie if present. 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" }

Cookie set on success:

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 64-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 (Required) — 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 Weak password {"error": "This password is too common."} or {"error": "This password is entirely numeric."}
400 OTP expired {"error": "OTP has expired"}
400 Missing reset token {"error": "Reset token is required. Please verify your OTP first."}
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": true, "email": "String - The verified email address", "verification_token": "String - Single-use token required for registration", "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"}

Business Rules:


4. Student Registration


POST /accounts/student/register/

Description: Register a new student account. Creates both a User and StudentProfile atomically. Email must be verified first (see verify-email flow above).

Authentication: Public

Content-Type: multipart/form-data

Prerequisite: Call POST /verify-email/confirm/ first to obtain a verification_token. Include it in this request.

Request Body:

{ "verification_token": "String (Required) Token from verify-email/confirm", "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 (Required)", "mother_number": "String (Required)", "school_type": "Integer (Required)", "grade": "Integer (Required)", "division": "Integer (Required)", "school_name": "String (Required)", "birth_date": "Date (YYYY-MM-DD) (Required)", "gender": "String (Required) male|female", "gmail": "String (Required) Must be globally unique, must match verified email", "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 Missing verification token {"verification_token": ["Email verification is required."]}
400 Invalid/expired token {"verification_token": ["Invalid or expired verification token..."]}
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 type or grade {"school_type": ["School type and grade are required."]}
400 Missing division {"division": ["Division is required."]}
400 Missing school name {"school_name": ["School name is required."]}
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" }, "subjects_detail": [ { "id": "Integer", "name": "String" } ], "grades_detail": [ { "id": "Integer", "name": "String" } ], "courses_count": "Integer — Number of active courses for this teacher" } ] }

GET /accounts/public/stats/

Description: Public platform stats for the landing page (SEO/marketing). Returns active teacher count, active course count, and subject count. Student counts are intentionally excluded — per-teacher/aggregate student data is private.

Authentication: Public

Success Response — 200 OK:

{ "teacher_count": 12, "course_count": 34, "subject_count": 40 }

GET /accounts/public/teachers/<id>/

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" }, "subjects_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. Public Course Catalog (SEO)

Public course browsing for anonymous users (SEO pages). No prices, no video data, no student data. These endpoints replaced the old anonymous GET /courses/?teacher=X path — course lists and details now require authentication, so the landing page uses these.

GET /courses/public/

Description: List active courses for the public catalog.

Authentication: Public

Query Parameters:

Parameter Type Description
subject Integer Filter by subject ID
teacher Integer Filter by teacher ID
grade Integer Filter by grade ID
search String Search by name, description, or teacher name

Success Response — 200 OK (paginated):

{ "count": "Integer", "next": "String (URL) | null", "previous": "String (URL) | null", "results": [ { "id": "Integer", "name": "String", "description": "String | null", "cover_picture": "String (URL) | null", "teacher": {"id": "Integer", "name": "String"}, "grade": {"id": "Integer", "name": "String"}, "subject": {"id": "Integer", "name": "String"}, "topic_count": "Integer", "total_lectures": "Integer", "is_active": "Boolean" } ] }

GET /courses/public/<id>/

Description: Public course detail — course info + topic/lecture names only. No prices, no video IDs, no Bunny data.

Authentication: Public

Success Response — 200 OK:

{ "id": "Integer", "name": "String", "description": "String | null", "cover_picture": "String (URL) | null", "teacher": {"id": "Integer", "name": "String"}, "grade": {"id": "Integer", "name": "String"}, "subject": {"id": "Integer", "name": "String"}, "topic_count": "Integer", "total_lectures": "Integer", "topics": [ { "id": "Integer", "name": "String", "description": "String | null", "order": "Integer", "picture": "String (URL) | null", "lectures": [ {"id": "Integer", "name": "String", "description": "String | null", "order": "Integer"} ] } ] }

Error Responses:

Status Condition Response Body
404 Course not found / inactive {"error": "Course not found"}

7. 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/<id>/

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/<id>/

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/<id>/

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/<id>/

Authentication: Public

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


8. 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/<id>/

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/<id>/

Authentication: Public

Success Response — 200 OK:

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

9. Public Key


GET /accounts/public-key/

Description: Returns the RSA public key used for JWT verification (RS256 algorithm). Frontends can use this to verify token signatures locally.

Authentication: None (public)

Success Response 200 OK:

{ "public_key": "String PEM-formatted RSA public key", "algorithm": "String Always 'RS256'" }

Error Responses:

Status Condition Response Body
500 Server has no public key configured {"error": "Public key not configured on the server."}

10. Field Availability Check


GET /accounts/check-field/

Description: Check if a field value is already taken. Used for real-time validation on registration and profile forms.

Authentication: None (public)

Query Parameters:

Parameter Type Description
field String username, gmail, or phone_number
value String The value to check

Success Response 200 OK:

{ "available": true, "message": "" }

When unavailable:

{ "available": false, "message": "This username is already taken." }

Error Responses:

Status Condition Response Body
400 Missing or invalid field {"error": "Both field and value are required."}

11. Video Security Options


GET /accounts/video-security/

Description: List video security options (platform fee per student). SiteOwner only — used when assigning security settings to teachers.

Authentication: SiteOwner

Query Parameters:

Parameter Type Description
search String Filter by name

Success Response — 200 OK:

{ "count": "Integer", "results": [ { "id": "Integer", "name": "String", "price_per_student": "String (Decimal)" } ] }

GET /accounts/video-security/<id>/

Description: Retrieve a single video security option.

Authentication: SiteOwner

Success Response — 200 OK:

{ "id": "Integer", "name": "String", "price_per_student": "String (Decimal)" }

Error Responses:

Status Condition Response Body
403 Not a SiteOwner {"detail": "You do not have permission to perform this action."}
404 Not found {"error": "Video security option not found"}