> 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/section-enrollment/update-an-existing-section-enrollment-full.md).

# Update an existing section enrollment (full)

Use this endpoint to update a section enrollment and its course enrollment's grades, units, and registration details. It works like `PATCH` except for invalid values: `PUT` stores them as null and lists them in `errors[]`, while `PATCH` rejects the whole call. `student_username`, `course`, and `section` must match the stored enrollment; to move an enrollment, delete it and create a new one.

## PUT /planner/enrollment/section/{id}

>

```json
{"openapi":"3.0.0","info":{"title":"Stellic for Developers","version":"1.0"},"tags":[{"name":"Section Enrollment"}],"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":{"PlanCourseCourseSectionRequest":{"allOf":[{"$ref":"#/components/schemas/PlanCourseCourseSection"},{"$ref":"#/components/schemas/CourseVersioningFields"}]},"PlanCourseCourseSection":{"type":"object","properties":{"student_username":{"description":"Student's Stellic username. On PUT and PATCH it must match the stored enrollment.","type":"string"},"course":{"description":"Course code in your catalog. On POST, a well-formed code that isn't in your catalog is added to it. On PUT and PATCH it must match the stored enrollment.","type":"string"},"section":{"description":"Catalog section ID. On PUT and PATCH it must match the stored enrollment.","type":"string"},"term":{"description":"Term ID for the enrollment. Required on POST; ignored on PUT and PATCH.","type":"string"},"is_enrollment_component":{"description":"Whether this section is the enrollment component of the course. On POST, when\nthe course enrollment already exists and this is true, supplied final and\nmid-semester grades fill only missing grades. Valid, nonempty\n`registration_status`, `repeat_code`, `grade_option`, and `enrollment_level`\ncan replace stored values. Units are selected, not added: when values differ,\nthe first nonempty, nonzero value wins, incoming first; identical values are\nretained. If the stored units are null, supply valid nonzero units. Omitting\nunits or sending null when stored units are null returns a 500. The course\nenrollment's timestamp is processed too. `is_transfer` and\n`transfer_university` remain unchanged; use PUT or PATCH to change them.\n\nOn PUT and PATCH, JSON booleans and the strings \"true\" and \"false\" are\nsupported. Prefer JSON booleans. String matching is case-insensitive, but any\nstring other than \"true\", including \"true \", becomes false rather than\nbeing rejected. False bypasses the stale-update skip and still allows\ncourse-level fields to change, but leaves the course enrollment's timestamp\nunchanged.","oneOf":[{"type":"boolean"},{"type":"string"}]},"grade_scheme":{"description":"The grading scheme under which the student received a grade, used with `grade_option` to read `final_grade` and `mid_semester_grade`.","type":"string"},"grade_option":{"description":"The grading option under which the student received a grade for this course. On PUT and PATCH, an empty string is treated as omitted; on an applied update, JSON null clears the stored value.","type":"string","nullable":true},"is_transfer":{"description":"Whether the course was transferred in. On PUT and PATCH, omitting it sets it to `false`.","type":"boolean"},"transfer_university":{"description":"Institution the course was transferred from.","type":"string"},"course_topic":{"description":"Topic the student enrolled in. Used only when POST creates the course enrollment.","type":"string"},"attributes":{"description":"Attribute names on the enrollment. On section-enrollment POST, saved only when\ncreating the course enrollment; an unknown name can create a catalog attribute.\nSection-enrollment PUT and PATCH don't save attributes, but validate newly\nsupplied names when the update proceeds. An unrecognized nonempty name is listed\nin a successful PUT response's `errors[]` and causes a 400 on PATCH. Validate\nattribute names before sending them.","type":"array","items":{"type":"string"}},"registration_status":{"description":"Registration status for the course, for example `ENRL` or `WAIT`. On PUT and PATCH, an empty string is treated as omitted; on an applied update, JSON null clears the stored value. Omit `registration_reason` when clearing the status.","type":"string","nullable":true},"registration_reason":{"description":"Your SIS identifier for the reason behind the registration status. Max 100 characters.\nOn PUT and PATCH, send `registration_status` with it; a reason sent alone is\nignored. A nonempty reason forms the lookup value `{status}#{reason}`, which\nmust exist in your institution's registration-status catalog. An unknown\ncombined value returns a validation 400 on PATCH only if the update proceeds;\na timestamp-driven skip does not validate it. Omit the reason when clearing\nthe status with JSON null.","type":"string"},"repeat_code":{"description":"Repeat code for the course. On PUT and PATCH, an empty string is treated as omitted and leaves the stored value unchanged; on an applied update, JSON null clears it.","type":"string","nullable":true},"final_grade":{"description":"Final grade letter the student received. On PUT and PATCH, the first grade on an enrollment needs `grade_option` or `grade_scheme`.","type":"string"},"mid_semester_grade":{"description":"Mid-semester grade letter the student received.","type":"string"},"units":{"description":"Units the student received.","type":"number"},"enrollment_level":{"description":"Enrollment level for the course, for example `UGRD` or `GRAD`. On PUT and PATCH, an empty string is treated as omitted and leaves the stored value unchanged.","type":"string"},"sis_update_timestamp":{"description":"Your SIS's time for the change, as `YYYY-MM-DDTHH:MM:SS`. On PUT and PATCH,\nan update older than the course enrollment's stored timestamp is skipped\nonly when `is_enrollment_component` is true. On an applied update, the\nsection enrollment's timestamp is replaced with the supplied value, or\ncleared when omitted. The course enrollment's timestamp is replaced or\ncleared only when `is_enrollment_component` is true; false leaves it\nunchanged even when course-level fields change. A malformed nonempty\ntimestamp, including an invalid calendar value, can cause a 500 before\nany enrollment write. Validate it before sending.","type":"string"}},"required":["course","section","is_enrollment_component","student_username"]},"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"}}},"PlanCourseCourseSectionResponse":{"allOf":[{"$ref":"#/components/schemas/PlanCourseResponseFields"},{"type":"object","properties":{"is_enrollment_component":{"description":"Present when a value is stored.","type":"boolean"},"sis_update_timestamp":{"description":"Stored SIS update timestamp of the section enrollment, as `YYYY-MM-DD HH:MM:SS`. Omitted when none is stored.","type":"string"},"section":{"description":"The catalog section.","type":"object","properties":{"id":{"description":"Stellic's internal ID for the catalog section.","type":"integer"},"name":{"description":"Section name.","type":"string"},"campus":{"description":"The catalog section's campus.","type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"screen_name":{"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/section/{id}":{"put":{"tags":["Section Enrollment"],"parameters":[{"name":"id","in":"path","required":true,"description":"The `{id}` you assigned when you created the section enrollment. Matches only enrolled\nsections on the student's default plan; removed, planned, or other-plan enrollments are\ntreated as not found. May contain `/`, but avoid IDs ending in a slash followed by a\ntimestamp such as `/2024-08-15T10:00:00`, which selects the DELETE-only timestamped route.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanCourseCourseSectionRequest"}}}},"responses":{"200":{"description":"Enrollment updated. `state` is a single object, not an array. Always inspect\n`errors[]`: a non-empty list with `status: \"success\"` means the update landed but\nsome optional fields were rejected and cleared (set to null). There is no top-level\n`student_username`, `term`, `grade_scheme`, or `campus` in the response;\n`state.section.campus` is the catalog section's campus.\n\n`state.sis_update_timestamp`, when present, is the section enrollment's timestamp.\nWhen that timestamp is cleared, the key is omitted, not returned as null. The course\nenrollment's timestamp used for stale-update comparison is not separately returned\nin state, including when `is_enrollment_component` is false and preserves it.\n\nOn a timestamp-driven skip, the response shape is different: `status: \"success\"`\nwith a `message` field and the stored state, and no `errors` key. The skip takes\neffect when `is_enrollment_component` is true, whether sent as a JSON boolean or\nthe string \"true\". On PUT and PATCH, false bypasses this skip and preserves the\ncourse enrollment's `sis_update_timestamp`; it does not suppress other course-level\nupdates. Grades, registration status, repeat code, grade option, enrollment level,\nunits, and transfer details still update under the usual validation and omission\nrules. Do not use false to isolate changes to the section enrollment.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"state":{"$ref":"#/components/schemas/PlanCourseCourseSectionResponse"},"errors":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Request aborted before any change is written. Per-field validation issues (invalid\ngrade, registration status, repeat code, grade option, enrollment level, unknown\nattribute names) do NOT return a 400 on PUT; they come back as entries in the 200\nresponse's `errors[]` and the offending fields on the course enrollment are cleared\nto null. Use PATCH if you'd rather the whole call fail on any invalid optional.\nStudent lookup failures return their text in `message` instead of `errors[]`.\n\nCommon causes:\n- Missing or empty request body - returns `section enrollment is a required field` and `Invalid payload`\n- A required body field (`student_username`, `course`, `section`, `is_enrollment_component`) is missing or null - returns `{field} is a required field.`\n- No enrolled section with this `{id}` on the student's default plan (after about 3 seconds of retries) - returns `Section enrollment with id:{id} does not exist`\n- `student_username`, `course`, or `section` doesn't match the stored enrollment, even when the incoming timestamp is older - returns `Validating identifier fields failed for {field}. Request body has {incoming_value}, db has {existing_value}`\n- Student does not exist or is a test student account, including an archived test student account (after retries with exponential backoff) - returns `No student exists with username: {username}`\n- Non-test student is archived - returns `Student with username {username} is archived`"},"401":{"description":"Missing or invalid Bearer token.\n\nCommon causes:\n- Unauthenticated / invalid 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.\n\nCommon causes:\n- Server error - returns `Stellic couldn't process your request`"}}}}}}
```

#### What happens after the call

* The section enrollment and its course enrollment are updated right away.
* The course enrollment's catalog course version is refreshed when one can be identified.
* Changes to registration status, repeat code, grade option, enrollment level, units, and transfer details appear in the student's activity history shortly after.
* The student's cached audit is invalidated in a background task, so audit and progress views reflect the change once it runs.

#### Notes

* **`is_enrollment_component: false` does not make updates section-only.** On PUT and PATCH, false bypasses the stale-update skip and leaves the course enrollment's `sis_update_timestamp` unchanged. Grades, registration status, repeat code, grade option, enrollment level, units, and transfer details still update the course enrollment under the usual validation and omission rules. Do not use false to isolate changes to the section enrollment.
* **Older updates are skipped when `is_enrollment_component` is true.** If the course enrollment's stored `sis_update_timestamp` is newer than yours, nothing changes. This applies at every institution, so send your SIS's change time, not the time of the call.
* **Omitted fields can reset stored values.** On an applied update, omitting `is_transfer` sets it to `false`. Omitting `sis_update_timestamp` clears the section enrollment's timestamp; it also clears the course enrollment's timestamp when `is_enrollment_component` is true. With false, the course enrollment's timestamp is unchanged. Send the current values to retain them. Other omitted directly updated enrollment fields retain their stored values. For a topic course, saving also synchronizes its course topic from the associated catalog sections. The existing topic can be replaced or removed even when you omit `course_topic`; do not rely on omission to preserve it.
* **The response timestamp belongs to the section enrollment.** `state.sis_update_timestamp`, when present, is the section enrollment's timestamp. When that timestamp is cleared, the key is absent, not null. The course timestamp used for stale-update comparison is not separately returned in state, including when false preserves it.
* **Invalid values overwrite stored ones.** An unrecognized grade, registration status, repeat code, grade option, or enrollment level replaces the stored value with null. Use `PATCH` if you'd rather the call fail.
* **Omission and empty strings retain coded fields; JSON null clears them.** On an applied PUT or PATCH update, omitting `registration_status`, `repeat_code`, `grade_option`, or `enrollment_level`, or sending `""`, leaves that stored code unchanged. Send explicit JSON `null` to clear one of these codes.
* **The first grade needs `grade_option` or `grade_scheme`.** If the enrollment has no grade yet, send one of them with `final_grade`.
* **`attributes` isn't saved.** Change attributes with [`PUT /planner/enrollment/course/{username}/{term_id}`](/stellic-partner-api/course-enrollments/replace-a-student-s-course-enrollments-for-a-term.md).

#### Related APIs

* [`PATCH /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/update-an-existing-section-enrollment-partial.md) to update the enrollment and reject invalid values.
* [`GET /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/get-a-section-enrollment.md) to read the enrollment.
* [`POST /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/enroll-a-student-in-a-section.md) to create a section enrollment.
* [`DELETE /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/remove-a-section-enrollment.md) to remove it.
