POST /api/v1/enrollments/{enrollmentId}/coaching-sessions
Records one coaching session on one enrollment, with its notes, decisions and commitments. At most one commitment may be promoted to the member's next action; the promotion replaces the current one. Everything is append-only: nothing recorded here is ever updated or deleted.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Conditional — only with a business role below |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | coach scoped to this enrollment, or program_director scoped to the organization |
| Resource scope | Enrollment of the organization attached to the API key |
| Participation | The coach must be assigned to this enrollment |
| Resource state | The enrollment must be active |
A platform-wide role grants nothing here. An enrollment of another organization, or one outside the caller's scope, is refused without revealing whether it exists.
| Parameter | Type | Required | Description |
|---|---|---|---|
enrollmentId | UUID | yes | Enrollment visible from the key's organization |
| Header | Required | Description |
|---|---|---|
x-api-key | yes | API key created for this organization |
Idempotency-Key | yes | Stable key for this recording intention |
X-Correlation-Id | no | Caller correlation id, returned or generated |
| Field | Type | Required | Nullable | Description |
|---|---|---|---|---|
heldAt | ISO 8601 string | yes | no | When the session is held; a future instant schedules it |
entries | array | yes | no | At least one entry |
entries[].kind | string | yes | no | note, decision or commitment |
entries[].visibility | string | yes | no | coach_only or shared_with_member |
entries[].body | string | yes | no | 1 to 4000 characters; the title for a commitment |
entries[].ownerType | string | commitments only | yes | member or coach |
entries[].ownerUserId | UUID | coach owner only | yes | Must be a coach assigned to this enrollment |
entries[].dueAt | ISO 8601 string | commitments only | yes | Must be in the future |
entries[].promote | boolean | no | no | At most one entry in the request may set it |
{
"heldAt": "2026-09-01T14:30:00.000Z",
"entries": [
{
"kind": "note",
"visibility": "coach_only",
"body": "Still hesitating between industrial SMEs and consultancies."
},
{
"kind": "decision",
"visibility": "shared_with_member",
"body": "Focus on industrial SMEs until the end of the month."
},
{
"kind": "commitment",
"visibility": "shared_with_member",
"body": "Build a list of 50 qualified industrial SMEs",
"ownerType": "member",
"dueAt": "2026-09-08T21:59:00.000Z",
"promote": true
}
]
}curl -X POST "$COHORTE_API_BASE_URL/api/v1/enrollments/ENROLLMENT_ID/coaching-sessions" \
-H "x-api-key: $COHORTE_API_KEY" \
-H "Idempotency-Key: coaching-session-2026-09-01" \
-H 'Content-Type: application/json' \
-d '{"heldAt":"2026-09-01T14:30:00.000Z","entries":[{"kind":"note","visibility":"coach_only","body":"Short debrief."}]}'201 Created — the session and its entries were recorded.200 OK — the same Idempotency-Key replayed the original recording.400 Bad Request — missing Idempotency-Key, invalid JSON, invalid entry, a past deadline, two promoted commitments, an inactive enrollment, or a blocker escalation holding the current action.401 Unauthorized — the key is missing, invalid, or no longer attached to a member.403 Forbidden — the bearer cannot document this enrollment.404 Not Found — the enrollment is unknown or belongs to another organization.409 Conflict — the current action changed while the session was being recorded.| Field | Type | Nullable | Description |
|---|---|---|---|
success | boolean | no | true for a successful request |
replayed | boolean | no | true when the idempotency key replayed a recording |
data.id | UUID | no | Coaching session identifier |
data.enrollmentId | UUID | no | Enrollment the session belongs to |
data.heldAt | ISO 8601 string | no | When the session is held, in UTC |
data.recordedBy | UUID | no | Account that recorded the session |
data.recordedByName | string | yes | Display name of that account |
data.recordedAt | ISO 8601 string | no | Recording time in UTC |
data.promotedNextActionId | UUID | yes | Next action created or replaced by the promotion |
data.entries[].id | UUID | no | Entry identifier |
data.entries[].kind | string | no | note, decision or commitment |
data.entries[].visibility | string | no | coach_only or shared_with_member |
data.entries[].body | string | no | Entry content |
data.entries[].ownerType | string | yes | member or coach on a commitment |
data.entries[].ownerUserId | UUID | yes | Owning coach on a commitment |
data.entries[].dueAt | ISO 8601 string | yes | Commitment deadline in UTC |
data.entries[].promotedNextActionId | UUID | yes | Set on the promoted commitment only |
data.entries[].supersedesEntryId | UUID | yes | Entry this one replaces |
data.entries[].addedAt | ISO 8601 string | no | When the entry was appended, in UTC |
correlationId | UUID | no | Correlation id for support |
{
"success": true,
"replayed": false,
"data": {
"id": "9f2c0a1e-0000-4000-8000-000000000000",
"enrollmentId": "4af4cd8d-0000-4000-8000-000000000000",
"heldAt": "2026-09-01T14:30:00.000Z",
"recordedBy": "1b7d2f30-0000-4000-8000-000000000000",
"recordedByName": "Karim Haddad",
"recordedAt": "2026-09-01T14:32:00.000Z",
"promotedNextActionId": "6d41ab55-0000-4000-8000-000000000000",
"entries": [
{
"id": "3ac91b70-0000-4000-8000-000000000000",
"kind": "commitment",
"visibility": "shared_with_member",
"body": "Build a list of 50 qualified industrial SMEs",
"ownerType": "member",
"ownerUserId": null,
"dueAt": "2026-09-08T21:59:00.000Z",
"promotedNextActionId": "6d41ab55-0000-4000-8000-000000000000",
"supersedesEntryId": null,
"addedAt": "2026-09-01T14:32:00.000Z"
}
]
},
"correlationId": "0f6b1d92-0000-4000-8000-000000000000"
}Idempotency-Key returns the original session and
creates no second entry, audit record or event.400; record the
session again without a promotion.coaching.session_recorded, and
coaching.commitment_promoted when a commitment was promoted. No email or
notification is sent by this endpoint.Keep one idempotency key for one normalized intention. Current authorization is checked again on retry. A different payload or resource with that key, or an older key whose intention cannot be verified, returns 409 conflict without another mutation. Inspect the original outcome before creating a new command. Identical retries do not resend notifications.