POST /api/v1/groups/{groupId}/memberships
Adds one open Enrollment to a Group without changing its Program, Cohort or Path. Re-adding after removal opens a new period and preserves the old one.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Conditional |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | An organization-scoped program_director assignment is required for MEMBER |
| Resource scope | Group and Enrollment must belong to the API key's Organization and match Program/Cohort scope |
| Resource state | Group must not be expired; Enrollment must not be terminal; capacity must remain available |
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | UUID | yes | Target Group in the key's Organization |
| Header | Required | Description |
|---|---|---|
x-api-key | yes | API key created for this Organization |
Idempotency-Key | yes | Stable key for this membership intent |
X-Correlation-Id | no | Caller correlation id, returned or generated |
| Field | Type | Required | Nullable | Description |
|---|---|---|---|---|
enrollmentId | UUID | yes | no | Enrollment to add |
curl -X POST "$COHORTE_API_BASE_URL/api/v1/groups/$GROUP_ID/memberships" \
-H "x-api-key: $COHORTE_API_KEY" \
-H "Idempotency-Key: group-member-$ENROLLMENT_ID" \
-H 'Content-Type: application/json' \
-d "{\"enrollmentId\":\"$ENROLLMENT_ID\"}"201 Created — a membership period was opened.200 OK — the command was replayed or the same Enrollment was already active.400 Bad Request — input is invalid, the Group is expired/full, the Enrollment is terminal, or it does not match scope.401 Unauthorized — the API key is invalid or no longer attached to a member.403 Forbidden — the bearer is not an allowed operator.404 Not Found — the Group or Enrollment is unknown or belongs to another Organization.500 Internal Server Error — the membership could not be opened.| Field | Type | Nullable | Description |
|---|---|---|---|
success | boolean | no | Always true on success |
replayed | boolean | no | true when no new period was created |
data.id | UUID | no | Membership-period identifier |
data.groupId | UUID | no | Target Group |
data.enrollmentId | UUID | no | Added Enrollment |
data.joinedAt | ISO 8601 string | no | Period start |
data.leftAt | ISO 8601 string | yes | null for the open period |
correlationId | string | no | Request correlation identifier |
{"success":true,"replayed":false,"data":{"id":"44444444-4444-4444-8444-444444444444","groupId":"33333333-3333-4333-8333-333333333333","enrollmentId":"22222222-2222-4222-8222-222222222222","joinedAt":"2026-09-18T08:00:00.000Z","leftAt":null},"correlationId":"req-42"}group.member_added event are written atomically.