---
title: "Parent Portal API"
url: https://docs.growamal.com/en/guides/parent-portal-api/
language: en
---

# Parent Portal API

The parent web portal (WP-PWP-BE) exposes four read-only endpoints that surface a child's
growth to their linked parent. All four endpoints share the same permission gate and the same
two-belt no-leak posture.

## Permission gate

```
VIEW_OWN_CHILDREN | VIEW_PARENT_REPORTS | RUN_BACKOFFICE
```

A parent persona holds both `VIEW_OWN_CHILDREN` and `VIEW_PARENT_REPORTS`. A Teacher, Student,
or Principal token holds none of these and receives `403`. `RUN_BACKOFFICE` is reserved for
super-admin support access.

Permission alone is not sufficient. Each `:childId` route additionally verifies that the calling
parent is linked to that specific child (see IDOR closure below).

## IDOR closure

The parent's identity is derived from `ctx.userId` (the authenticated session). There is no
`:parentId` segment in any path, so it is structurally impossible to request another parent's
children by substituting an ID in the URL.

For the three `:childId` routes, the handler resolves the child's `StudentProfile` within the
caller's org and then calls `canReachStudent(..., { allowParent: true })` against the
`ParentStudent` junction:

| Outcome | HTTP status | When |
|---|---|---|
| Child not in caller's org (or does not exist) | `404` | Existence non-disclosure (cross-org) |
| Child in org but not linked to this parent | `403` | In-org but unlinked (FU-15) |
| Child linked | `200` | Proceed with response |

The `404` / `403` discrimination is intentional: returning `403` for a cross-org child would
confirm the child exists in the system.

## Two-belt no-leak posture

Every response passes two independent safety layers:

1. **Typed DTO allow-list** (`parent-portal.schemas.ts`): the response schema contains only
   parent-safe fields. θ scores, `SkillStatus` enum tokens, profile codes, internal IDs, and
   benchmark numbers cannot leak because they are not declared in the schema at all.
2. **`applyParentStricterFilter`** (canonical FR-LANG-6 belt from `modules/language-safety`):
   runs on every serialized string in the response. It blocks any internal identifier, numeric
   score, or clinical label that might otherwise slip through inside an allowed string field.

Both layers are required. Neither alone is sufficient.

## Endpoints

### List children

```
GET /api/parent/children
```

Returns the list of children linked to the authenticated parent via `ParentStudent`. Each entry
contains the child's first name, grade level, school name, and last-active timestamp. No student
IDs, class IDs, or score values appear.

An empty array is a valid response when the parent has no linked children yet.

### Diagnostic history

```
GET /api/parent/children/{childId}/diagnostics
```

Returns the completed diagnostic history for the specified child. Each entry carries a
status-label only (Arabic categorical description); no raw θ, no percentages, and no `SkillStatus`
enum tokens are included.

### Growth over time

```
GET /api/parent/children/{childId}/growth
```

Returns a macro-domain status trajectory showing how the child's status on each growth region
has changed across assessment windows. Values are Arabic categorical labels only. No numbers
appear at any level.

### At-home recommendations

```
GET /api/parent/children/{childId}/recommendations?window=BOY
```

Returns up to five at-home growth suggestions drawn from the pre-generated parent narrative. The
suggestions come from the SPOT profile template library and are never generated at runtime by an
LLM (V-5 no-LLM invariant).

The `window` query parameter (default `BOY`) selects the assessment window.

#### GL-005 halt payload

If the child has no active profile assignment (for example, the diagnostic is still in progress),
the endpoint returns an HTTP `200` with a safe GL-005 halt payload instead of fabricating
suggestions. The halt payload is a short, warm Arabic message that explains more data is needed.
It never falls back to a generic statement.

## Fail-closed behavior

All four endpoints return `503` if the language-safety rule set is unavailable. The server refuses
to ship a response without confirming that the parent-stricter filter can run. This is the same
posture used by the parent PDF endpoint (WP-09-BE).

> **Warning:** Never store or log the raw response bodies from these endpoints in a context visible to non-parent
> roles. The typed DTOs guarantee that no internal identifiers reach the wire, but server-side
> logging of full response payloads could still expose the child's name or school name to log viewers.

## Related

- [Reporting and Parent PDF](https://docs.growamal.com/en/guides/reporting-and-parent-pdf/): the parent growth PDF endpoint and its permission model
- [Parent Child Links](https://docs.growamal.com/en/guides/parent-child-links/): how to link a parent to a child (`ParentStudent` junction)
- [Language Safety](https://docs.growamal.com/en/guides/language-safety/): parent-stricter filter and growth-language rules
- [Core Concepts - RBAC](https://docs.growamal.com/en/core-concepts/): full permission table
