Provision Users
Every user after the bootstrap admin is created through POST /api/users, whose body is a
discriminated union keyed on primaryPersona. The discriminator (CreateUserBody) maps each
persona to its own body schema:
primaryPersona → body schema
STUDENT → CreateStudentUserBody
TEACHER → CreateTeacherUserBody
PARENT → CreateParentUserBody
PRINCIPAL → CreatePrincipalUserBody
MANAGER → CreateManagerUserBody
ADMIN → CreateAdminUserBodySet primaryPersona and the server validates the rest of the body against that persona’s schema.
Per-persona fields
| Persona | Required (beyond common) | Optional |
|---|---|---|
| common | email, password (8–72 chars), fullNameAr, primaryPersona | fullNameEn, usually organizationId |
STUDENT | gradeLevel (1–4) | homeDialect (MSA | LEV, default MSA), classId |
TEACHER | — | tier (STANDARD | SENIOR | HEAD), arabicLiteracyTraining |
PARENT | — | phoneE164, preferredLanguage (ar | en) |
PRINCIPAL | schoolId | tier (STANDARD | HEAD) |
MANAGER | — | scopedSchoolIds[] ([] = org-wide) |
ADMIN | scope (SUPER | ORG | SCHOOL) | specialistRole |
Example: create a Grade-2 student already enrolled in a class:
curl -X POST https://localhost:3000/api/users \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"primaryPersona": "STUDENT",
"email": "[email protected]",
"password": "student-strong-password",
"fullNameAr": "ليان حسن",
"organizationId": "clx4z2k0u0000xyz1234abcde",
"gradeLevel": 2,
"classId": "clx4z2k0u0000xyz1234abcde"
}'Enrolling a student into a class. Set classId on the student body at creation. There is no
separate enroll endpoint in 1.0.0-phase0; the student then appears in
GET /api/classes/{classId}/students. See Manage Classes & Rosters.
Who can call it
POST /api/users is gated by the MANAGE_* permissions for the target persona. For example, creating
a Principal needs MANAGE_PRINCIPALS. An Admin holds it at any scope; a Manager holds it when the
target school is within their scope.
What you get back
A 201 UserCreatedResponse (id, email, primaryPersona, organizationId, createdAt). The
matching persona profile row is created in the same transaction.
Read profiles and lists
GET /api/users/me/profile→ the caller’s persona-typedPersonaProfileResponse(discriminated).GET /api/users→ users in your scope, paginated with anextCursor.
Errors
| Status | When |
|---|---|
422 | Validation: short password, bad persona/field combo (VALIDATION_ERROR) |
403 | Missing the required MANAGE_* permission (PERMISSION_DENIED) |
409 | Conflict: e.g. a quickCode collision (auto-retried once, then surfaced) |
See Errors for the full catalog.