GET /api/v1/cohorts/{cohortId}/cockpit
Returns the operating picture of one cohort: how many members are on time, late, at risk or unknown, the pending reviews and open blockers, the members without a recent intervention, and the next live sessions. Every aggregate is recomputed on read over the caller's own scope; nothing is stored.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed — the whole cohort |
ADMIN | Allowed — the whole cohort |
MEMBER | Conditional — only with a business role below |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | program_director scoped to the organization, or coach scoped to enrollments |
| Resource scope | Cohort of the organization attached to the API key |
| Participation | A coach sees only the enrollments of this cohort they are assigned to |
| Resource state | None — a cohort in any status can be read |
A coach with no assigned enrollment in this cohort receives 404, exactly like
an unknown cohort: the endpoint never confirms that a cohort exists.
| Parameter | Type | Required | Description |
|---|---|---|---|
cohortId | UUID | yes | Cohort visible from the key's organization |
curl "$COHORTE_API_BASE_URL/api/v1/cohorts/COHORT_ID/cockpit" \
-H "x-api-key: $COHORTE_API_KEY"200 OK — the cockpit, computed over the caller's scope.401 Unauthorized — the key is missing, invalid, or no longer attached to a member.404 Not Found — the cohort is unknown, belongs to another organization, or the bearer has no scope on it.| Field | Type | Nullable | Description |
|---|---|---|---|
success | boolean | no | true for a successful request |
data.cohort.name | string | no | Cohort name |
data.cohort.programName | string | yes | Parent program name |
data.cohort.status | string | no | draft, open, active, completed or archived |
data.cohort.timezone | string | yes | Operating timezone of the cohort (IANA) |
data.cohort.startDate | ISO 8601 string | yes | Start date, UTC, null when the cohort has none |
data.scope | string | no | tenant (whole cohort) or assigned (the caller's enrollments only) |
data.week | object | no | {measured: true, week, startDate, timezone} or {measured: false, reason} |
data.phase | object | no | Current named phase, or not_configured / not_positioned |
data.capacity | object | no | Limit and global occupancy when the caller has tenant-wide scope |
data.members | object | no | total, active, pending, completed over the caller's scope |
data.rhythm | object | no | Counts for on_time, late, at_risk and unknown; they sum to members.total |
data.reviews | object | no | {measured: true, total, highlighted} — highlighted counts overdue reviews |
data.blockers | object | no | {measured: true, total, highlighted} — highlighted counts urgent blockers |
data.absences | object | no | Measured counts, or {measured: false, reason: "no_past_live"} |
data.withoutIntervention | number | no | Members with no note and no coaching session for 14 days |
data.upcomingLives[] | array | no | Up to three scheduled sessions targeting this cohort |
data.provenance | object | no | Source and asOf timestamp of every aggregate |
data.unavailable[] | array | no | Sections with no value, each with its source and reason |
A signal that cannot be measured is never returned as 0: it carries
measured: false and a reason. startsAt is an absolute instant in UTC; the
display timezone is a separate field.
{
"success": true,
"data": {
"cohort": {
"id": "773021ba-0000-4000-8000-000000000000",
"name": "Autumn cohort",
"programName": "Closing High-Ticket",
"status": "active",
"timezone": "Europe/Paris",
"startDate": null
},
"scope": "tenant",
"week": {"measured": false, "reason": "start_date_missing"},
"phase": {"measured": true, "id": "…", "name": "Practice", "position": 2, "total": 3},
"capacity": {"limited": true, "limit": 24, "occupancy": 18, "available": 6, "overCapacity": false},
"members": {"total": 6, "active": 3, "pending": 2, "completed": 1},
"rhythm": {"on_time": 1, "late": 0, "at_risk": 0, "unknown": 5},
"reviews": {"measured": true, "total": 1, "highlighted": 0},
"blockers": {"measured": true, "total": 1, "highlighted": 0},
"absences": {"measured": false, "reason": "no_past_live"},
"withoutIntervention": 6,
"upcomingLives": [],
"provenance": {
"rhythm": {
"source": "rhythm_derivation",
"asOf": "2026-09-02T10:00:00.000Z"
}
},
"unavailable": [
{"key": "week", "source": "cohort", "reason": "start_date_missing"}
]
}
}at_risk wins over late,
which wins over unknown: a member who is both late and blocked is reported
once, as at_risk.data.scope says which
set the numbers cover, so a small number is never mistaken for a small cohort.