> For the complete documentation index, see [llms.txt](https://docs.stellic.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.stellic.com/stellic-partner-api/course-enrollments/replace-a-student-s-course-enrollments-for-a-term.md).

# Replace a student's course enrollments for a term

Use this endpoint to send the complete set of registered courses a student should have for a term, for example to post registrations at term start or final grades at term end. Registered courses you leave out are removed; planned courses and courses brought in through transfer articulation are left alone. To change one section without resending the term, use [`POST /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/enroll-a-student-in-a-section.md) or [`PATCH /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/update-an-existing-section-enrollment-partial.md).

## PUT /planner/enrollment/course/{username}/{term\_id}

>

```json
{"openapi":"3.0.0","info":{"title":"Stellic for Developers","version":"1.0"},"tags":[{"name":"Course Enrollments"}],"servers":[{"url":"https://{institution}.stellic.com/partnerapi/v1","variables":{"institution":{"default":"{institution}","description":"Your institution's Stellic subdomain: the part before `.stellic.com` in the URL you use to sign in. It becomes the host in every request, as in `https://{institution}.stellic.com/partnerapi/v1/...`. Replace the `{institution}` placeholder with your subdomain before sending."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Personal Access Token issued by your Stellic integration partner. Send it as `Authorization: Bearer <token>` on every request. One token per institution; do not share across environments."}},"schemas":{"PlanCourseRequest":{"allOf":[{"$ref":"#/components/schemas/PlanCourse"},{"$ref":"#/components/schemas/CourseVersioningFields"}]},"PlanCourse":{"type":"object","properties":{"course":{"description":"Course code in your catalog. A well-formed code that isn't in your catalog is added to it.","type":"string"},"campus":{"description":"Campus where the student took the course. Case-insensitive. If it's missing or unknown, the course is saved without sections.","type":"string"},"grade_scheme":{"description":"The grading scheme under which the student received a grade for this course.","type":"string"},"grade_option":{"description":"The grading option under which the student received a grade for this course.","type":"string"},"is_registered":{"description":"Whether the course is registered. Defaults to `true`; `false` stores it as planned. Send a JSON boolean or the string `\"true\"`: any other value counts as `false`.","type":"boolean"},"is_transfer":{"description":"Whether the course was transferred in. Defaults to `false`.","type":"boolean"},"transfer_university":{"description":"Institution the course was transferred from.","type":"string"},"is_residency":{"description":"Whether the course counts as resident credit at your institution. Defaults to `false`. `true` also adds the residency credit tag.","type":"boolean"},"is_exam":{"description":"Whether the credit was earned by exam. Defaults to `false`. `true` also adds the exam credit tag.","type":"boolean"},"sections":{"description":"Catalog section IDs the student is enrolled in. An empty list doesn't remove enrolled sections unless `is_transfer` is `true`.","type":"array","items":{"type":"string"}},"course_topic":{"description":"Topic the student enrolled in, by the topic's short name or name. It must be a topic of this course; an unknown topic is stored as empty and adds an entry to `errors[]`.","type":"string"},"attributes":{"description":"Attribute names on the enrollment, used in search and degree requirements.","type":"array","items":{"type":"string"}},"tags":{"description":"Enrollment tags, each identified by the tag's short name. Unknown short names aren't saved.","type":"array","items":{"type":"object","properties":{"short_name":{"description":"The tag's short name.","type":"string"}},"required":["short_name"]}},"registration_status":{"description":"Registration status for the course, for example `ENRL` or `WAIT`.","type":"string"},"registration_reason":{"description":"Your SIS identifier for the reason behind the registration status. Max 100 characters.","type":"string"},"repeat_code":{"description":"Repeat code for the course.","type":"string"},"final_grade":{"description":"Final grade letter the student received in the course.","type":"string"},"mid_semester_grade":{"description":"Mid-semester grade letter the student received in the course.","type":"string"},"units":{"description":"Units the student received.","type":"number"},"enrollment_level":{"description":"Enrollment level for the course, for example `UGRD` or `GRAD`.","type":"string"},"sis_update_timestamp":{"description":"Your SIS's time for the change, as `YYYY-MM-DDTHH:MM:SS`. Where your institution runs timestamp-driven data sync, an update to a course whose stored timestamp is newer is skipped.","type":"string"}},"required":["course"]},"CourseVersioningFields":{"type":"object","description":"Optional fields that link the course enrollment to one version of the course in your catalog.\nTo match a version, send `course_id` and a `course_start_term` that is a known term ID, plus\n`course_offering_number` if your catalog uses offering numbers. If you send none of these\nfields, or `course_start_term` isn't a known term ID, the version recorded on the catalog\nsection is used instead (on the course endpoint, the first listed section that has one); when\nno section has one, the enrollment keeps its current version. Values that match no catalog\nversion leave the current version in place, and no error is returned. Honored by\n`PUT /planner/enrollment/course/{username}/{term_id}`, `POST /planner/enrollment/section/{id}`\n(only when it creates the course enrollment), and `PUT`/`PATCH /planner/enrollment/section/{id}`.\n","properties":{"course_id":{"description":"Your institution's own identifier for the course version (the course ID from your SIS).","type":"string"},"course_offering_number":{"description":"Offering number of the course version, as a string.","type":"string"},"course_start_term":{"description":"Term ID in which this course version starts. If it isn't a known term ID, the other versioning fields are ignored and the section's version is used.","type":"string"}}},"PlanCourseResponse":{"allOf":[{"$ref":"#/components/schemas/PlanCourseResponseFields"},{"type":"object","properties":{"sections":{"description":"Catalog section IDs on the course enrollment.","type":"array","items":{"type":"string"}}}}]},"PlanCourseResponseFields":{"type":"object","properties":{"code":{"description":"Course code.","type":"string"},"units":{"description":"Units the student received.","type":"number","nullable":true},"grade":{"description":"Final grade letter.","type":"string","nullable":true},"mid_semester_grade":{"description":"Mid-semester grade letter.","type":"string","nullable":true},"grade_option":{"type":"string","nullable":true},"is_transfer":{"type":"boolean"},"transfer_university":{"type":"string","nullable":true},"registration_status":{"type":"string","nullable":true},"repeat_code":{"type":"string","nullable":true},"attributes":{"description":"Attribute names on the enrollment.","type":"array","items":{"type":"string"}},"tags":{"description":"Names of the enrollment's tags.","type":"array","items":{"type":"string"}},"enrollment_level":{"type":"string","nullable":true},"course_topic":{"description":"Name of the registered topic, or null.","type":"string","nullable":true},"is_residency":{"type":"boolean"},"is_exam":{"type":"boolean"},"cross_course_link_destination_ids":{"description":"Internal IDs you can ignore.","type":"array","items":{"type":"integer"}},"cross_course_link_source_ids":{"description":"Internal IDs you can ignore.","type":"array","items":{"type":"integer"}},"version_id":{"description":"Stellic ID of the linked catalog course version. Present only when the course enrollment is linked to one.","type":"integer"}}}}},"paths":{"/planner/enrollment/course/{username}/{term_id}":{"put":{"tags":["Course Enrollments"],"parameters":[{"name":"username","in":"path","required":true,"description":"Student's Stellic username.","schema":{"type":"string"}},{"name":"term_id","in":"path","required":true,"description":"Send the term ID as stored. The format is specific to your institution (for\nexample `fall-2024` or `Fall#2024`). Send `#` as `%23` (`Fall%232024`); an unencoded\n`#` ends the URL path.\n","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The complete list of registered courses for the student and term. Registered courses you\nleave out are removed; planned courses and courses brought in through transfer articulation\nare left alone. Each course you send replaces the stored one: for a course the student\nalready has, omitted fields follow the defaults and preservation rules in the field descriptions.\n","content":{"application/json":{"schema":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/PlanCourseRequest"},{"type":"object","properties":{"is_registered":{"type":"boolean","nullable":true,"description":"Whether the course is registered. Omitted or null defaults to `true`. New course enrollments are always created as registered, even when you send `false`; `false` changes only an existing enrollment to planned. Accepts JSON booleans or the strings `\"true\"` and `\"false\"`, ignoring case and surrounding whitespace. Other values reject the request with a 400."},"is_transfer":{"type":"boolean","nullable":true,"description":"Whether the course was transferred in. Omitted or null defaults to `false`, including on update. Accepts JSON booleans or the strings `\"true\"` and `\"false\"`, ignoring case and surrounding whitespace. Other values reject the request with a 400."},"is_residency":{"type":"boolean","nullable":true,"description":"Whether the course counts as resident credit at your institution. Omitted or null defaults to `false` on create. On an existing course, omission, null, or `false` can leave a stored `true` unchanged when no other basic course field changes; verify the saved value after updating. `true` also adds the residency credit tag. Accepts JSON booleans or the strings `\"true\"` and `\"false\"`, ignoring case and surrounding whitespace. Other values reject the request with a 400."},"is_exam":{"type":"boolean","nullable":true,"description":"Whether the credit was earned by exam. Omitted or null defaults to `false` on create. On an existing course, omission, null, or `false` can leave a stored `true` unchanged when no other basic course field changes; verify the saved value after updating. `true` also adds the exam credit tag. Accepts JSON booleans or the strings `\"true\"` and `\"false\"`, ignoring case and surrounding whitespace. Other values reject the request with a 400."},"exclude_from_progress":{"type":"boolean","nullable":true,"description":"Whether to exclude the course from degree progress. Saved on create and update. Omitted or null defaults to `false`, including on update, so send `true` on every call to keep the course excluded. Accepts JSON booleans or the strings `\"true\"` and `\"false\"`, ignoring case and surrounding whitespace. Other values reject the request with a 400."},"enrollment_level":{"type":"string","description":"Enrollment level for the course, for example `UGRD` or `GRAD`. On an existing course, omitting it, sending an unrecognized level, or sending your institution's default level preserves the stored level. Send a different configured level to replace it."},"course_topic":{"type":"string","description":"Topic the student enrolled in, by the topic's short name or name. It must be a topic of this course. A valid supplied topic takes precedence over section topics. If omitted, null, empty, or whitespace-only, the first listed section of this course that has a topic supplies it. An unknown supplied topic adds an entry to `errors[]` without falling back to a section topic. If no topic resolves, an existing planned topic is preserved when you send `is_registered: false`; otherwise the stored topic is cleared. This preservation also applies when the supplied topic is invalid."}}}]}}}}},"responses":{"200":{"description":"Request processed. Per-course outcomes are split between `success[]` and `errors[]`.\nAlways inspect `errors[]`; a 200 does NOT mean every course succeeded.\n","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"description":"One object per course enrollment that was created, updated, or removed, in its state at that moment. `sections` is a list of catalog section IDs. `version_id` is present when the course is linked to a catalog course version; the two `cross_course_link_*` lists are internal IDs you can ignore. There is no `semester`, `year`, `campus`, or `is_registered` key.","type":"array","items":{"$ref":"#/components/schemas/PlanCourseResponse"}},"errors":{"description":"Error entries have two shapes. Ordinary course errors contain `course`, `campus`, and an array-valued `message`, with one string per failed step and one entry per (course, campus) pair. Invalid-topic errors contain only a string-valued `message`, for example `{\"message\": \"Invalid course_topic value 'UNKNOWN' for course CS-111.\"}`. Check whether `message` is a string or an array before processing it; do not assume every entry contains a list of messages.","type":"array","items":{"type":"object","properties":{"course":{"type":"string","description":"Course code from your payload."},"campus":{"type":"string","description":"Lowercased campus from your payload, or null when none was sent."},"message":{"type":"array","items":{"type":"string"}}}}}}}}}},"400":{"description":"Request rejected before any course is processed. An invalid boolean flag on any course\nrejects the whole request. A well-formed course code that isn't in your catalog is added to it and the\nenrollment is created without sections; a malformed code is skipped and appears in\nneither `success[]` nor `errors[]`.\n\nThe term is checked first. An unknown term returns its error even when the body is\ninvalid or the student is missing. After the term check, the endpoint validates the\nbody list and required `course` fields before looking up the student.\n\nCommon causes:\n- Request body is not a JSON array - returns `Request data needs to be a list of course enrollments`\n- An item in the array is missing `course` or has `course: null` - returns a 400 with an empty `{\"errors\": []}` body (no message)\n- Student not found - test student accounts are excluded from lookup, even if archived. If no other student matches the username, returns `{\"errors\": [{\"message\": \"No student exists with username: {username}\"}]}`\n- Matching non-test student is archived - returns `{\"errors\": [{\"message\": \"Student with username {username} is archived\"}]}`\n- `term_id` does not match an enrollment term, including when it matches only a transfer-source term - returns `Term with id: {term_id} does not exist`\n- An invalid `is_registered`, `is_transfer`, `is_residency`, `is_exam`, or `exclude_from_progress` value - returns `{\"errors\": [{\"message\": \"`{field}` must be true or false, got `{value}`\"}]}`"},"401":{"description":"Missing or invalid Bearer token."},"403":{"description":"Your IP address isn't on the allowlist for this endpoint. Contact Stellic to add it; the response includes the `client_ip` Stellic saw.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"client_ip":{"type":"string"}}}}}},"500":{"description":"Unhandled server error. Retry with exponential backoff."}}}}}}
```

#### What happens after the call

* Courses the student doesn't have for the term are created right away as registered, with their sections, attributes, and tags.
* Courses the student already has are updated right away to match the payload. `is_registered: false` makes a registered course planned, and omitting `is_registered` makes a planned course registered.
* Registered courses missing from the payload are removed right away, along with their section enrollments, attributes, tags, and notes.
* Each course is linked to a catalog course version when one can be identified.
* Where your institution has attribute inheritance turned on, attributes from the catalog sections are added to non-transfer courses. For transfer credit, attributes from the base catalog course are added, not attributes specific to a course version. Send any version-specific attributes you want to retain in `attributes`; an update can remove them if omitted.
* Adds, updates, and removals appear in the student's activity history shortly after. Section changes use section names, and resending unchanged units doesn't add a units-change entry.
* The student's cached audit is invalidated in a background task, so audit and progress views reflect the change once it runs.

#### Notes

{% hint style="warning" %}
**Each course is replaced, not merged.** For a course the student already has, omitted fields can be cleared: leaving out `final_grade`, registration status, or `attributes` erases the stored value. Omitted `units` become null for registered courses; courses saved as planned receive default units instead. Depending on how Stellic has set up your institution, a variable-credit course saved as planned can remain null. Ask your Stellic Representative how yours is set up. Send every field for every course on every call, and check the field descriptions for omission rules.
{% endhint %}

* **Invalid values are dropped without an error.** An unrecognized registration status, grade, repeat code, grade option, attribute, or tag is stored as empty, replacing any existing value, and isn't listed in `errors[]`. Check values against your institution's configured codes before sending.
* **Sections need a valid `campus`.** If `campus` is missing or unknown, incoming section enrollments are not applied. Supplied section IDs can still supply the course topic or catalog version before the incoming section list is discarded. Existing non-transfer enrollments keep their already-enrolled sections, but this does not preserve section-inherited attributes. With attribute inheritance enabled, those attributes can be removed unless you explicitly supply them in `attributes`. Send a valid `campus` and the intended `sections` on every call when relying on attribute inheritance.
* **Shared section IDs can add unexpected attributes.** With attribute inheritance enabled, a course enrollment can receive attributes from another catalog section with the same section ID. Verify inherited attributes if your sections share IDs, and contact your Stellic Representative if attributes from another section appear.
* **An empty `sections` list doesn't remove enrolled sections** unless the course is a transfer (`is_transfer: true`). To remove them, leave the course out of the payload or call [`DELETE /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/remove-a-section-enrollment.md) for each section.
* **Unknown course codes are added to your catalog.** The enrollment is then saved without sections, so check codes against your catalog to keep typos from becoming courses. A code that doesn't fit the catalog's format (under 3 or over 40 characters, non-ASCII, or containing `|`, `,`, `"`, parentheses, brackets, or braces) is skipped and appears in neither `success[]` nor `errors[]`.
* **To set a course version, send `course_id` and a known `course_start_term`.** If you send neither, or the start term isn't known, the version recorded on the first listed section is used. A `course_id` without a start term, or values that match no version, keep the current version, as does a course whose sections have no version.
* **The call isn't atomic.** Creates, removals, and updates run in stages, so a failure can leave the term partly updated. Resending the same payload is safe and completes the update.
* **Use an enrollment term with a nonempty ID.** If its stored ID is blank, ask your Stellic Representative to configure one. Don't use a transfer-source term: if the ID matches only a transfer-source term, the endpoint returns a term-does-not-exist 400.
* **Encode `#` in `{term_id}`.** If your term IDs contain `#`, send it as `%23`; an unencoded `#` ends the URL path.
* **Don't write sections for the same student and term at the same time.** This endpoint and the section endpoints don't lock each other, and overlapping calls can create duplicate course enrollments.

#### Related APIs

* [`POST /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/enroll-a-student-in-a-section.md) to add one section enrollment.
* [`PATCH /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/update-an-existing-section-enrollment-partial.md) to update one section enrollment.
* [`DELETE /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/remove-a-section-enrollment.md) to remove one section enrollment.
* [`POST /catalog/section/{id}`](/stellic-partner-api/catalog-sections/create-a-catalog-section.md) to create a catalog section before enrolling students in it.
