Skoolsewa - Ecommerce Docs
Developer ResourcesProfile

Profile — API

GET /api/profile and GET /api/mobile/portal/me — request, response shape, per-type block nullability, and error codes.

Profile — API

Endpoint

MethodPathSurfaceAuthPermission
GET/api/profileadmin panelBearer (JwtAuthGuard + RoleGuard)none declared — self-scoped by construction
GET/api/mobile/portal/meconsumer portalBearer (JwtAuthGuard + RoleGuard)none declared — self-scoped by construction

No parameters. No body. No write counterpart.

The caller is taken from the session; there is no id to supply and none is accepted.

Two paths, one service

Both routes call ProfileService.findForActor and return the identical ProfileDto. There is one service and one DTO; the second path exists because the consumer surface is mounted under /api/mobile by RouterModule, and registering ProfileModule there instead would have MOVED /api/profile to /api/mobile/profile and broken the admin panel.

The response is the same for every audience, and it returns every profile the caller holds regardless of which role the session is acting as — a teacher who is also a parent receives both the staff and the guardian block. That is deliberate: this route describes the person, not the session's current context.

Response

200 with the project envelope.

{
  "message": "Profile fetched.",
  "data": {
    "person": {
      "id": "01a078b4-010d-7309-aaee-52d88c912d3d",
      "firstName": "Deepa",
      "middleName": null,
      "lastName": "Rai",
      "fullName": "Deepa Rai",
      "email": "student@example.test",
      "emailVerified": true,
      "phone": null,
      "phoneVerified": false,
      "image": null,
      "dateOfBirth": "2012-05-04",
      "gender": "female",
      "bloodGroup": "O+",
      "maritalStatus": null,
      "disabilityType": null,
      "ethnicityId": 12,
      "ethnicityName": "Chhetri",
      "motherTongueId": 4,
      "motherTongueName": "Nepali",
      "permanentAddress": {
        "provinceName": "Bagmati",
        "districtName": "Kathmandu",
        "municipalityName": "Kathmandu",
        "wardNo": 5,
        "tole": "Baluwatar",
        "houseNo": "12"
      },
      "currentAddress": { "provinceName": null },
      "bio": null
    },
    "student": {
      "admissionNumber": "STU-2099-0001",
      "studentId": "SID-2099-0001",
      "admissionDate": "2026-04-15",
      "recordStatus": "active",
      "transportMode": "school_bus",
      "interestsHobbies": null,
      "guardians": [
        { "fullName": "Chandra Rai", "relationship": "mother", "isPrimary": true }
      ]
    },
    "staff": null,
    "guardian": null
  }
}

Every identifier above is synthetic.

Block nullability

person is always present. The other three are null unless the caller holds a live row in that table, and they are independent rather than exclusive — a teacher whose child attends the school receives both staff and guardian.

BlockPresent when
personalways
studenta live students row exists for actor.id
staffa live staff row exists
guardiana live guardians row exists

staff

{
  "employeeCode": "EMP-2099-0001",
  "joiningDate": "2026-04-15",
  "employmentStatus": "active",
  "qualification": "M.Ed",
  "experienceYears": 6,
  "department": { "publicId": "0198…", "name": "Science" },
  "designation": { "publicId": "0198…", "name": "Senior Teacher", "isTeaching": true }
}

department and designation are keyed by publicId, never the integer primary key, and each is independently nullable — a department without a designation is legal; the reverse is refused by staff_designation_needs_department.

guardian

{
  "kind": "person",
  "organizationName": null,
  "occupation": "Engineer",
  "children": [
    { "fullName": "Deepa Rai", "relationship": "mother", "isPrimary": true }
  ]
}

kind is person | organization. The father | mother | local_guardian values belong to relationship, which is a different enum on a different table (student_guardian.relationship). Conflating the two is easy and was caught in review.

Fields this endpoint never returns

AbsentRead it atPermission
salary, allowances, total, bank, branch, PAN, citizenship, SSF, CITGET /staff/:id/salaryStaffSalary_READ
medical conditions, allergies, special needsGET /students/:id/medicalStudentMedical_READ

These are omitted from the DTO entirely rather than nulled, so a consumer cannot render them by mistake. See the feature page for why this is not a self-only carve-out.

Errors

StatuserrorCodeMeaning
401No session
401AUTH_ACTIVE_ROLE_REQUIREDThe caller holds roles but the session has not been pointed at one. Thrown by @CurrentAdmin(); the panel redirects to /dashboard/select-role

There is no 404. The caller's own users row is not filtered on deleted_atjwt.strategy.ts already refuses a session whose user is soft-deleted, so filtering again would be the only way to produce a 404 for a caller who demonstrably exists. This also matters on the consumer side: adminApiGet calls Next's notFound() on any 404 GET, which would render the app's not-found page rather than an in-page error.