{
    "openapi": "3.1.0",
    "info": {
        "title": "Acakoro School",
        "version": "1.0.0",
        "description": "Backend API for **Acakoro School's** CBC (Competency-Based Curriculum)\nexam and results management system.\n\nIt replaces the school's manual Excel marksheets \u2014 one workbook per\ngrade band (e.g. `Marksheet_G3-G6`, `Marksheet_G7-10`), one sheet per\ngrade per assessment (e.g. `G6_A1`, `G6_A2`, `G7_A1`) \u2014 with a single\nbackend that captures the same data and computes the same derived\nvalues, consistently, for every grade and every term.\n\n---\n\n## 1. Academic structure\n\nThe system is organized as:\n\n```\nTerm\n  \u2514\u2500\u2500 Assessment        (Opener \/ Mid Term \/ End Term)\n        \u2514\u2500\u2500 Grade        (3, 5, 6, 7, 8, 9, 10, ...)\n              \u2514\u2500\u2500 Learning Area   (subject, fixed per grade)\n                    \u2514\u2500\u2500 Student Mark   (by admission number)\n```\n\n**Terms & Assessments**\nA school year has terms (Term 1, 2, 3). Each term has exactly\n**3 assessments**, sat in this order:\n\n| Order | Assessment | Notes |\n|-------|------------|-------|\n| 1 | Opener | First assessment of the term |\n| 2 | Mid Term | Mid-term assessment |\n| 3 | End Term | Final assessment of the term |\n\n**Grades**\nGrades observed in the school's existing records: **3, 5, 6, 7, 8, 9, 10**.\n(Grade 4 sheets weren't present in the sampled files but the schema\nsupports any grade without code changes.)\n\n**Learning Areas (subjects) \u2014 fixed per grade**\nEach grade has its own subject list under CBC; lower grades and\nupper grades don't study the same subjects. From the source sheets:\n\n| Grade band | Learning Areas |\n|---|---|\n| Grade 3 | English, Mathematics, Integrated Activities, Kiswahili |\n| Grade 5 \u2013 6 | English, Mathematics, Kiswahili, Agriculture, Integrated Science, ICT, Social Studies, CRE, Creative Arts |\n| Grade 7 \u2013 9 | English, Mathematics, Kiswahili, Agriculture, Integrated Science, Social Studies, CRE, Pre-Technical Studies, Creative Arts |\n| Grade 10 | English, Mathematics, Kiswahili, Geography, CSL, Business Studies, Computer Studies |\n\nA Learning Area belongs to exactly one Grade in this system \u2014 the\nsame subject name (e.g. \"English\") appearing in two grades is two\nseparate Learning Area records, since the \"out of\" mark and the\nstudents sitting it differ per grade.\n\n---\n\n## 2. Marks capture workflow\n\nMirrors how staff currently fill the Excel sheets:\n\n1. Pick the **Assessment** (Opener \/ Mid Term \/ End Term) and **Term**.\n2. Pick the **Grade**.\n3. Pick the **Learning Area** (subject) for that grade.\n4. Set the **\"out of\"** \u2014 maximum obtainable mark for that\n   assessment + grade + learning area combination (this varied\n   per subject in the source sheets, e.g. out of 30, out of 40,\n   out of 50 \u2014 it is not fixed school-wide).\n5. Enter the **raw mark** for each student, matched by\n   **admission number** (not just name \u2014 the source sheets used\n   name only, which doesn't scale or disambiguate students\n   reliably; the backend uses admission number as the key).\n\n---\n\n## 3. Derived values (computed automatically, not entered manually)\n\n**Performance Level** \u2014 CBC 4-band scale, computed from the\n**percentage** scored (`mark \u00f7 out_of \u00d7 100`), not a hardcoded raw\nmark cutoff. This fixes an inconsistency in the original sheets,\nwhere each sheet hardcoded its own raw-mark thresholds per subject\n(e.g. `>= 23` out of 30) instead of deriving them from percentage \u2014\nmeaning the same percentage could land in different bands on\ndifferent sheets purely due to copy-paste drift.\n\n| Level | Meaning | Default cutoff |\n|---|---|---|\n| E.E. | Exceeding Expectation | \u2265 75% |\n| M.E. | Meeting Expectation | \u2265 50% and < 75% |\n| A.E. | Approaching Expectation | \u2265 30% and < 50% |\n| B.E. | Below Expectation | < 30% |\n\n*(Cutoffs above are carried over from the observed sheet logic \u2014\ne.g. Grade 6 out of 30 used `>=23 \/ >=16 \/ >=9`, which is `~76.6% \/\n~53.3% \/ ~30%`. Confirm\/adjust exact percentages with the school\nbefore locking these as policy.)*\n\n**Points** \u2014 numeric weight attached to the performance level,\nused for ranking and aggregate reporting:\n\n| Level | Points |\n|---|---|\n| E.E. | 4 |\n| M.E. | 3 |\n| A.E. | 2 |\n| B.E. | 1 |\n\n**Per-student, per-assessment aggregates**\n- **Total Marks** \u2014 sum of raw subject scores across all Learning\n  Areas the student sat for that grade\/assessment.\n- **Total Points** \u2014 sum of subject Points across the same set.\n- **Rank \/ Position** \u2014 student's position within their Grade for\n  that Assessment, ordered by Total Marks (configurable to order\n  by Total Points instead).\n- **Class Average** \u2014 per Learning Area, the mean raw mark across\n  all students in that grade\/assessment (present in the original\n  sheets as an \"AVERAGE\" row).\n\n**Per-student, per-term aggregates** (across Opener + Mid Term + End\nTerm)\n- **Term Total Points** \u2014 sum of Total Points across all 3\n  assessments.\n- **Term Average Mark** \u2014 per Learning Area, mean of the student's\n  marks across the 3 assessments.\n\n---\n\n## 4. Roles & access (role-based access control)\n\n| Role | Access |\n|------|--------|\n| **Admin** | Full control \u2014 manage terms, assessments, grades, learning areas, teacher & student accounts, assign teachers to grade\/subject combinations, view all results school-wide |\n| **Teacher** | Enter\/edit marks only for the grade(s) and subject(s) assigned to them; view class lists, mark sheets, and performance reports for their own classes only |\n\n\nAuthorization is enforced at the route\/policy level \u2014 e.g. a\nTeacher assigned to Grade 6 Mathematics cannot submit marks for\nGrade 7 English; a Parent can only view their own children's\nadmission numbers.\n\n---\n\n## 5. Conventions used across this API\n\n- **Auth**: Bearer token (see the Authorize button in this UI).\n- **Identifiers**: students are referenced by `admission_number`\n  in request\/response bodies wherever practical, in addition to\n  internal numeric IDs.\n- **Errors**: standard Laravel validation\/error JSON shape \u2014\n  `{ \"message\": \"...\", \"errors\": { \"field\": [\"...\"] } }` for 422,\n  `{ \"message\": \"...\" }` for 401\/403\/404\/500.\n- **Pagination**: list endpoints are paginated (`page`, `per_page`\n  query params), returning Laravel's standard paginator shape.\n- **Dates**: ISO 8601 (`YYYY-MM-DD`).\n\n---\n\nThis API is consumed by the school's internal results dashboard."
    },
    "servers": [
        {
            "url": "https:\/\/acakoro-api.apelisoltech.com\/api\/v1"
        }
    ],
    "tags": [
        {
            "name": "Authentication",
            "description": "Staff authentication for the Acakoro School CBC Exams API.\n\nCovers Admin and Teacher accounts only \u2014 students and parents are\nnot API users in this system (they're read via separate Student\/\nGuardian resources, not logged-in accounts). Administrators create\nstaff accounts directly; there is no self-registration endpoint.\n\nAll endpoints return a Sanctum bearer token on success, included\nas `Authorization: Bearer {token}` on every subsequent request.\n\n### Login\n\n| Endpoint | Auth required | Purpose |\n|---|---|---|\n| `POST \/auth\/login` | No | Authenticates with email + password. Returns a bearer token. |\n\n**Account lockout:** after 5 failed login attempts from the same\nemail + IP combination within a 15-minute window, further attempts\nare rejected until the window expires \u2014 regardless of whether the\ncredentials are correct. This protects staff accounts from\nbrute-force attempts without needing a CAPTCHA.\n\n**Deactivated accounts:** if an Administrator has set a staff\naccount's `is_active` to `false` (e.g. a teacher who has left the\nschool), login fails even with correct credentials.\n\n### Session & Token Management\n\nTokens are scoped by device name via the required `device_name`\nfield on `POST \/auth\/login` (e.g. `\"device_name\": \"office-pc\"` or\n`\"device_name\": \"teacher-app-android\"`). Logging in again from the\nsame `device_name` replaces that device's previous token \u2014 it does\nnot create unlimited tokens per device, but a user can hold one\nactive token per distinct device name simultaneously (e.g. laptop\n+ phone at the same time).\n\n| Endpoint | Auth required | Purpose |\n|---|---|---|\n| `GET \/auth\/profile` | Yes | Returns the current authenticated user with their assigned role(s) |\n| `POST \/auth\/logout` | Yes | Revokes only the token used for this request (this device) |\n| `POST \/auth\/logout-all` | Yes | Revokes every token for the user (all devices) |\n\n### Password Management\n\n| Endpoint | Auth required | Purpose |\n|---|---|---|\n| `POST \/auth\/forgot-password` | No | Sends a password reset link to the email, if registered. Always returns a generic success message \u2014 never reveals whether the email exists. |\n| `POST \/auth\/reset-password` | No | Validates the reset token and email, sets a new password. Revokes all existing sessions for that account. |\n\n> **All sessions revoked on reset:** after a successful password\n> reset, every bearer token previously issued to that account is\n> deleted. The user must log in again on every device.\n\n### Frontend Implementation Guide\n\n**Step 1 \u2014 On login**\nStore the returned bearer token securely. Include it as\n`Authorization: Bearer {token}` on every subsequent request.\n\n**Step 2 \u2014 Handle lockout responses**\nA `401` from `POST \/auth\/login` with a \"Too many login attempts\"\nmessage means the account\/IP is temporarily locked \u2014 surface the\nwait time to the user rather than retrying immediately.\n\n**Step 3 \u2014 Fetch profile**\nCall `GET \/auth\/profile` to load the user's role (Admin or\nTeacher) into app state, and use it to gate UI elements \u2014\ne.g. only Admins see grade\/subject management screens; Teachers\nonly see mark entry for their assigned classes."
        }
    ],
    "security": [
        {
            "http": []
        }
    ],
    "paths": {
        "\/auth\/login": {
            "post": {
                "operationId": "auth.login",
                "description": "Authenticates a staff account (Admin or Teacher) with email and\npassword. Staff accounts are created by an Administrator \u2014 there\nis no self-registration endpoint.\n\nReturns a Sanctum bearer token scoped to `device_name`, so the same\nuser can hold separate, independently revocable tokens per device\n(e.g. one for the office PC, one for a mobile app).\n\nLocked out after 5 failed attempts from the same email\/IP within\n15 minutes.",
                "summary": "Login",
                "tags": [
                    "Authentication"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/LoginRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "user": {
                                                    "$ref": "#\/components\/schemas\/UserResource"
                                                },
                                                "token": {
                                                    "type": "string"
                                                },
                                                "token_type": {
                                                    "type": "string",
                                                    "const": "Bearer"
                                                }
                                            },
                                            "required": [
                                                "user",
                                                "token",
                                                "token_type"
                                            ]
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "message": {
                                                    "type": "string",
                                                    "const": "Login successful."
                                                }
                                            },
                                            "required": [
                                                "message"
                                            ]
                                        },
                                        "errors": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            },
                                            "minItems": 0,
                                            "maxItems": 0,
                                            "additionalItems": false
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "errors"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "null"
                                        },
                                        "meta": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            },
                                            "minItems": 0,
                                            "maxItems": 0,
                                            "additionalItems": false
                                        },
                                        "errors": {
                                            "type": "string"
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "errors"
                                    ]
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                },
                "security": []
            }
        },
        "\/auth\/forgot-password": {
            "post": {
                "operationId": "auth.forgotPassword",
                "description": "Sends a password reset link to the given email address, if it\nbelongs to a registered staff account. Always returns a generic\nsuccess response regardless of whether the email exists, so the\nAPI never reveals which emails are registered.",
                "summary": "Request password reset link",
                "tags": [
                    "Authentication"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/EmailRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "null"
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "message": {
                                                    "type": "string",
                                                    "const": "If that email is registered, a password reset link has been sent."
                                                }
                                            },
                                            "required": [
                                                "message"
                                            ]
                                        },
                                        "errors": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            },
                                            "minItems": 0,
                                            "maxItems": 0,
                                            "additionalItems": false
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "errors"
                                    ]
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                },
                "security": []
            }
        },
        "\/auth\/reset-password": {
            "post": {
                "operationId": "auth.resetPassword",
                "description": "Sets a new password using the token from the reset link sent via\n`POST \/auth\/forgot-password`. On success, every existing session\nfor the account is revoked \u2014 the user must log in again on all\ndevices with the new password.",
                "summary": "Reset password",
                "tags": [
                    "Authentication"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/ResetPasswordRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "null"
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "message": {
                                                    "type": "string",
                                                    "const": "Password has been reset successfully. Please log in with your new password."
                                                }
                                            },
                                            "required": [
                                                "message"
                                            ]
                                        },
                                        "errors": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            },
                                            "minItems": 0,
                                            "maxItems": 0,
                                            "additionalItems": false
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "errors"
                                    ]
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                },
                "security": []
            }
        },
        "\/auth\/logout": {
            "post": {
                "operationId": "auth.logout",
                "description": "Revokes only the Sanctum token used to authenticate the current\nrequest, ending this device's session. Other devices the user is\nlogged in on remain active \u2014 use `POST \/auth\/logout-all` to revoke\nevery session at once.",
                "summary": "Logout",
                "tags": [
                    "Authentication"
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "null"
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "message": {
                                                    "type": "string",
                                                    "const": "Logged out successfully."
                                                }
                                            },
                                            "required": [
                                                "message"
                                            ]
                                        },
                                        "errors": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            },
                                            "minItems": 0,
                                            "maxItems": 0,
                                            "additionalItems": false
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "errors"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/auth\/logout-all": {
            "post": {
                "operationId": "auth.logoutAllDevices",
                "description": "Revokes every Sanctum token belonging to the authenticated user,\nending all active sessions across every device.",
                "summary": "Logout of all devices",
                "tags": [
                    "Authentication"
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "null"
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "message": {
                                                    "type": "string",
                                                    "const": "Logged out of all devices."
                                                }
                                            },
                                            "required": [
                                                "message"
                                            ]
                                        },
                                        "errors": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            },
                                            "minItems": 0,
                                            "maxItems": 0,
                                            "additionalItems": false
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "errors"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/auth\/profile": {
            "get": {
                "operationId": "auth.profile",
                "description": "Returns the full record for the currently authenticated staff\nuser, including their assigned role(s) (Admin \/ Teacher).",
                "summary": "Authenticated user profile",
                "tags": [
                    "Authentication"
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/UserResource"
                                        },
                                        "meta": {
                                            "type": "string"
                                        },
                                        "errors": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            },
                                            "minItems": 0,
                                            "maxItems": 0,
                                            "additionalItems": false
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "meta",
                                        "errors"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "http": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "token"
            }
        },
        "schemas": {
            "EmailRequest": {
                "type": "object",
                "properties": {
                    "email": {
                        "type": "string",
                        "format": "email"
                    }
                },
                "required": [
                    "email"
                ],
                "title": "EmailRequest"
            },
            "LoginRequest": {
                "type": "object",
                "properties": {
                    "email": {
                        "type": "string",
                        "format": "email"
                    },
                    "password": {
                        "type": "string"
                    },
                    "device_name": {
                        "type": "string",
                        "maxLength": 255
                    }
                },
                "required": [
                    "email",
                    "password",
                    "device_name"
                ],
                "title": "LoginRequest"
            },
            "ResetPasswordRequest": {
                "type": "object",
                "properties": {
                    "token": {
                        "type": "string"
                    },
                    "email": {
                        "type": "string",
                        "format": "email"
                    },
                    "password": {
                        "type": "string",
                        "minLength": 8
                    },
                    "password_confirmation": {
                        "type": "string",
                        "minLength": 8
                    }
                },
                "required": [
                    "token",
                    "email",
                    "password",
                    "password_confirmation"
                ],
                "title": "ResetPasswordRequest"
            },
            "UserResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "email": {
                        "type": "string"
                    },
                    "is_active": {
                        "type": "boolean"
                    },
                    "roles": {
                        "type": "object"
                    },
                    "email_verified_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "email",
                    "is_active",
                    "roles",
                    "email_verified_at",
                    "created_at"
                ],
                "title": "UserResource"
            }
        },
        "responses": {
            "ValidationException": {
                "description": "Validation error",
                "content": {
                    "application\/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Errors overview."
                                },
                                "errors": {
                                    "type": "object",
                                    "description": "A detailed description of each field that failed validation.",
                                    "additionalProperties": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "required": [
                                "message",
                                "errors"
                            ]
                        }
                    }
                }
            },
            "AuthenticationException": {
                "description": "Unauthenticated",
                "content": {
                    "application\/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Error overview."
                                }
                            },
                            "required": [
                                "message"
                            ]
                        }
                    }
                }
            }
        }
    }
}