# REST API Documentation

Base URL: `https://yourdomain.com/api/`

All responses are JSON with this shape:
```json
{ "success": true|false, "message": "...", "data": { ... }, "meta": { ... } }
```
Paginated endpoints include `meta.pagination`:
```json
{ "current_page": 1, "per_page": 20, "total_items": 57, "total_pages": 3 }
```

## Authentication

All endpoints except `auth/*` require a Bearer token:
```
Authorization: Bearer <access_token>
```

Access tokens expire in 24 hours (`JWT_ACCESS_TOKEN_TTL`). Use the refresh
token (valid 30 days) to get a new one without re-prompting for a password.

---

## Auth

### `POST /api/auth/login.php`
```json
{ "role": "admin|teacher|parent", "identifier": "email or phone", "password": "..." }
```
Admin/teacher log in with **email**; parents log in with **phone**.
Returns `access_token`, `refresh_token`, `expires_in`, `user`, `role`, `school`.

Rate-limited: 5 failed attempts locks the identifier for 15 minutes.

### `POST /api/auth/refresh-token.php`
```json
{ "refresh_token": "..." }
```
Returns a fresh `access_token`.

### `POST /api/auth/logout.php` *(auth required)*
```json
{ "refresh_token": "..." }
```
Revokes the refresh token. Discard the access token client-side too.

### `POST /api/auth/forgot-password.php`
```json
{ "role": "admin|teacher|parent", "identifier": "..." }
```
Always returns a generic success message (prevents account enumeration).
Generates a 6-character reset code — delivery (SMS/email) must be wired up
server-side (see `INSTALLATION.md` §9).

### `POST /api/auth/reset-password.php`
```json
{ "role": "...", "identifier": "...", "token": "6-char code", "new_password": "..." }
```

---

## Profile

### `GET /api/profile/index.php` *(auth)*
Returns the caller's own profile.

### `POST /api/profile/update.php` *(auth, multipart for photo)*
Fields: `name`, `email` (parent) or `phone` (admin/teacher), `photo` (file).

### `POST /api/profile/change-password.php` *(auth)*
```json
{ "current_password": "...", "new_password": "..." }
```

---

## Dashboard

### `GET /api/dashboard/admin.php` *(admin/teacher)*
Total students/teachers, today's attendance breakdown, fees collected this
month, fees due, 5 most recent notices, 5 upcoming exams.

### `GET /api/dashboard/parent.php?student_id=` *(parent)*
`student_id` optional — defaults to first linked child. Returns the
child's profile, this month's attendance summary, pending homework, latest
published result, fee due amount + next due date, recent notices.

---

## Parent

### `GET /api/parent/children.php` *(parent)*
Lists every student linked to the logged-in parent (for a "switch child"
selector — one parent account may have multiple siblings enrolled).

---

## Students

### `GET /api/students/list.php` *(admin/teacher)*
Query: `class_id`, `section_id`, `status` (default `active`), `search`,
`page`, `limit`.

### `GET /api/students/get.php?id=` *(auth)*
Admin/teacher: any student in their school. Parent: only their own linked
children (403 otherwise).

---

## Attendance

### `POST /api/attendance/mark.php` *(admin/teacher)*
Bulk class marking:
```json
{
  "class_id": 1, "section_id": 2, "attendance_date": "2026-08-09",
  "marked_via": "manual",
  "records": [ { "student_id": 10, "status": "present", "remarks": "" } ]
}
```
Single-scan (QR/RFID hardware) shortcut:
```json
{ "rfid_card_number": "04A2B3C4", "status": "present", "marked_via": "rfid" }
```
`status` is one of `present|absent|late|half_day`. Notifications are queued
to parents **only** for `absent`/`late` (never `present`), per spec.
Every `student_id` is re-validated to belong to the caller's school **and**
the given class/section before writing — non-matching records are silently
skipped, not written.

### `GET /api/attendance/get.php?student_id=&month=YYYY-MM` *(auth)*
Parent: own child only. Returns `records[]` + a `summary` count by status.

### `GET /api/attendance/get.php?class_id=&section_id=&date=YYYY-MM-DD` *(admin/teacher)*
Class-wide view for a single day.

---

## Homework

### `GET /api/homework/list.php` *(auth)*
- Parent: **requires** `student_id`, returns that child's class/section
  homework.
- Teacher: their own created homework.
- Admin: school-wide, optional `class_id` filter.

### `GET /api/homework/get.php?id=` *(auth)*
Includes `attachments[]` with `url`.

### `POST /api/homework/create.php` *(admin/teacher, multipart)*
Fields: `class_id`, `section_id`, `subject_id` (optional), `title`,
`description`, `due_date`, `teacher_id` (**required if role=admin**),
`attachments[]` (files — images and/or PDFs, multiple allowed).
`class_id`/`section_id`/`subject_id`/`teacher_id` are all verified to
belong to the caller's school before insert.

---

## Exams & Results

### `GET /api/exams/list.php?class_id=&student_id=` *(auth)*
Parent must pass `student_id` (class is inferred from it). Admin/teacher
may pass `class_id` to filter, or omit for all exams in the school.

### `GET /api/results/get.php?exam_id=&student_id=` *(auth)*
Subject-wise marks + max/passing marks + overall `summary`
(percentage/grade/class_rank) once published. `published: false` until the
admin publishes results for that exam.

### `POST /api/results/enter.php` *(admin/teacher, multipart for marksheet)*
```json
{ "exam_id": 5, "subject_id": 3, "student_id": 10, "marks_obtained": 78, "remarks": "" }
```
Or attach a photographed/scanned marksheet via the `marksheet` file field.
Both `exam_id` (school-owned) and `student_id` (school-owned) are verified
before write.

---

## Fees

### `GET /api/fees/status.php?student_id=` *(auth)*
All fee line items for the student + `total_due`.

### `GET /api/fees/receipts.php?student_id=` *(auth)*
Payment history (receipts) for the student.

### `GET /api/fees/due-list.php` *(admin/teacher)*
School-wide pending/overdue fees, optional `class_id` filter, paginated.

> Online payment collection (Razorpay etc.) is not wired up in this
> version — fee collection happens via the School Admin panel (cash/UPI/
> bank/online recorded manually with a receipt). Add a payment-gateway
> endpoint here if/when online self-pay is needed.

---

## Notices

### `GET /api/notices/list.php` *(auth)*
Returns notices targeted at `all` plus the caller's own role audience
(`teachers` for teachers; `parents`/`students` for parents). Paginated.

### `GET /api/notices/get.php?id=` *(auth)*
Includes `attachments[]`. 403 if the notice isn't targeted at your role.

---

## Classes

### `GET /api/classes/list.php` *(auth)*
Every class in the school with nested `sections[]` and `subjects[]` —
used to populate dropdowns (e.g. homework creation) in the Flutter app.

---

## Institute

### `GET /api/institute/profile.php` *(auth)*
The school's public profile (name, logo, address, contact, principal,
academic year) for the "Institute Details" screen.

---

## Notifications

### `POST /api/notifications/register-token.php` *(auth)*
```json
{ "token": "fcm-device-token", "device_type": "android|ios|web" }
```
Call after login and whenever Firebase issues a refreshed token.

### `GET /api/notifications/list.php?page=&limit=` *(auth)*
In-app notification history for the logged-in user.

---

## Error codes

| Code | Meaning |
|---|---|
| 401 | Missing/invalid/expired token, or wrong credentials |
| 403 | Authenticated but not allowed to access this resource (wrong role or wrong tenant/child) |
| 404 | Resource not found (or, for tenant isolation, exists but not yours — same response, no leak) |
| 422 | Validation failed — see `data.errors` |
| 429 | Rate limited (login attempts) |
| 500 | Server error (message only, no stack trace unless `APP_DEBUG=true`) |

## Multi-tenancy & security

Every endpoint scopes queries by the `school_id` embedded in the JWT — it
is never trusted from the request body. Any foreign key referenced in a
write (student_id, class_id, teacher_id, exam_id, etc.) is re-validated to
belong to that same school before the write happens, so a compromised or
malicious client on School A's account cannot read or modify School B's
data even by guessing valid-looking IDs. See `SECURITY_NOTES.md` for the
audit trail.
