Remove a student from a group, without rewriting its membership. This endpoint never adds anyone. The identifiers travel in the request body, so they never appear in access logs.
One student per call. Send studentIds or studentExternalReferenceIds, never both, with exactly one identifier. The field is an array so the limit can be raised later without breaking your integration. To replace a whole membership use PUT /v3/groups/{id}/students; to move a batch without touching courses use POST /v3/groups/students/batch-upsert.
Not idempotent, by design. Removing a student who is not a member of the group is rejected with STUDENT_NOT_IN_GROUP: your view of the membership and ours disagree, and that deserves an error rather than a silent success. A successful removal therefore fails if you replay it — treat that code as benign if your events can be delivered more than once.
Who keeps their place on a course anyway, when cascadeToCourses is true: a student who also reaches that course through another group they still belong to. That is the only case, which is why coursesUpdatedCount can be lower than the number of upcoming courses of the group. A declared absence or an existing signature does not keep a student on the attendance; the absence itself is kept, and is applied again automatically if the student is added back to the group.
With cascadeToCourses=false nobody is taken off anything: only the group membership changes, and every student keeps their place on every course.
All or nothing. An unknown student identifier fails the whole call and writes nothing.
Rate limit: standard. Concurrency: two identical calls sent at the same moment share a single execution; a call replayed after the response has come back runs again in full.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||

