---
title: "Managers & Principals"
url: https://docs.growamal.com/en/guides/managers-and-principals/
language: en
---

# Managers & Principals

Managers and Principals are two distinct leadership roles at different levels.

| Role | Level | Scope |
|---|---|---|
| **Manager** | Organization | Scoped to specific schools via `scopedSchoolIds`; `[]` means org-wide |
| **Principal** | School | Leads **exactly one** school (`PrincipalProfile.schoolId`) |

## Two creation paths

1. **Create the user** with `POST /api/users` and `primaryPersona: MANAGER` (or `PRINCIPAL`), which
   creates the login account and the persona profile together.
2. **Assign the role/profile** to an existing user with `POST /api/managers` or `POST /api/principals`.

Use path 1 for a brand-new person; use path 2 to grant the role to a user that already exists.

## Create a Manager

```bash
curl -X POST https://localhost:3000/api/managers \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "userId": 1, "scopedSchoolIds": ["clx4z2k0u0000xyz1234abcde"] }'
```

Needs `MANAGE_MANAGERS` (Admin-only). An empty `scopedSchoolIds` makes the manager org-wide.

Returns `409` (`CONFLICT`, "User is already a Manager") if the user already holds the Manager role,
including when two concurrent requests race on the same `userId`.

## Create a Principal

```bash
curl -X POST https://localhost:3000/api/principals \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{ "userId": 1, "schoolId": "clx4z2k0u0000xyz1234abcde", "tier": "STANDARD" }'
```

Returns `201` `PrincipalDetailResponse`. `MANAGE_PRINCIPALS`: an Admin holds it at any scope; a
Manager holds it when the target `schoolId` is within their `scopedSchoolIds` (or they are org-wide).

> **Note:** **The principal ↔ school link is bidirectional.** Creating a PrincipalProfile with `schoolId=X` sets
> `School.principalUserId`; unassigning nulls it. Wave 1 allows **one principal per school**.

## Organization rollup (admin utility)

`GET /api/organizations/{id}/rollup` (`VIEW_ORG`, Admin or a Manager on their own org) returns the
school list and `totalStudents`. The `byMacroDomain` field is always an empty object `{}` in this
endpoint. Macro-domain data is served by the three dedicated manager routes described below.

## Manager org-level API

The manager dashboard is backed by three aggregate read-only routes, all gated on `VIEW_ORG`
(Manager or Admin). A Manager with a non-empty `scopedSchoolIds` sees only their scoped schools
(all-or-nothing enforcement). A cross-org `orgId` returns `404` without disclosing existence.

### Overview

`GET /api/orgs/{orgId}/overview` returns `OrgOverviewDTO`: per-school rollup cards containing
`schoolNameAr`, `totalStudents`, `classCount`, `needsSupportCount`, and `lastUpdatedAt`. This is the
manager home screen.

### Comparisons

`GET /api/orgs/{orgId}/comparisons` returns `OrgComparisonDTO`: status-count distributions across
all six macro-domain tiles for every school in the org, ordered alphabetically. There is no
preferential ranking between schools. Each entry is a status distribution, not a numeric average and
not a single overall percentage.

### Trends

`GET /api/orgs/{orgId}/trends` returns `OrgTrendsDTO`: a time-series per school per macro-domain
tile. The x-axis buckets snapshots by calendar month (`YYYY-MM` derived from `computedAt`) in
ascending order. This bucketing is by calendar month, not by BOY/MOY/EOY assessment windows.

For the school-level reads behind a principal's dashboard, see
[Standards & Benchmarks](https://docs.growamal.com/en/guides/standards-and-benchmarks/) (school overview + heatmap) and
[Reporting & Parent PDF](https://docs.growamal.com/en/guides/reporting-and-parent-pdf/).

## Approval authority

Tier-3 activation approval is admin-seated in Wave 1 (`APPROVE_TIER_3_ACTIVATION`), and the
reading-specialist is admin-seated via `specialistRole`. See [Core Concepts](https://docs.growamal.com/en/core-concepts/).
