> 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/enroll-a-student-in-a-section.md).

# Enroll a student in a section

Use this endpoint to add one section enrollment without resending the student's whole term. If the student has no course enrollment for that course and term, one is created; if they already have one, the section is added to it and a planned course becomes registered. To send a full term at once, use [`PUT /planner/enrollment/course/{username}/{term_id}`](/stellic-partner-api/course-enrollments/replace-a-student-s-course-enrollments-for-a-term.md).

## POST /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":{"PlanCourseCourseSectionCreateRequest":{"allOf":[{"$ref":"#/components/schemas/PlanCourseCourseSectionRequest"},{"type":"object","required":["term"]}]},"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}":{"post":{"tags":["Section Enrollment"],"parameters":[{"name":"id","in":"path","required":true,"description":"Your ID for this section enrollment, commonly your SIS's enrollment record ID. Use a new\nvalue for every enrollment: an `{id}` already on any student's enrolled section returns a\n400. May contain `/`, but must not end with a slash followed by a timestamp-shaped segment,\nsuch as `/2024-08-15T10:00:00`, which is reserved for timestamped DELETE.\n","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanCourseCourseSectionCreateRequest"}}}},"responses":{"200":{"description":"Enrollment created (or an existing section enrollment for the same course and section\nreused with the supplied `{id}`). Always inspect `errors[]`: a non-empty list with\n`status: \"success\"` means the enrollment was created but some optional fields were\nrejected and stored as null. `state` is a single object, not an array.\n`state.section.campus` is the catalog section's campus; there is no top-level `campus`,\n`student_username`, `term`, or `grade_scheme` in the response.","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) do NOT return\na 400; they come back as entries in the 200 response's `errors[]` with the offending\nfields stored as null.\n\nStudent lookup failures return their text in `message` instead of `errors[]`.\n\nCommon causes:\n- An enrolled section on any student's default plan already has this `{id}` - returns `section enrollment already exists with this id` (response body includes the existing enrollment in `state`)\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`, `term`) is missing or null - returns `{field} is a required field.`\n- Student not found - returns HTTP 400 with `No student exists with username: {username}` in `message`. Test student accounts are excluded from lookup. Check whether the student was converted to staff and the student record was marked as a test account.\n- Student is archived - returns `Student with username {username} is archived`\n- Section catalog ID doesn't exist - returns `Section with id: {section} does not exist.`\n- Term ID doesn't exist - returns `Term with id: {term} does not exist`\n- The term's year is out of the allowed range - returns `{year} is an invalid year for a PlanCourse`\n- Course code doesn't match the catalog's allowed format - returns `Invalid course code {code}.`\n- A removed course enrollment for this course and term has a newer `sis_update_timestamp` (institutions on timestamp-driven data sync) - returns `Enrollment already deleted. Ignoring POST request`"},"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"}}}}}},"405":{"description":"Method not allowed. An authenticated POST with an `{id}` ending in a slash followed\nby a reserved timestamp-shaped segment, such as `/2024-08-15T10:00:00`, selects the\nDELETE-only route instead of reaching POST validation. Change the enrollment ID;\nretrying the same POST will not resolve the error."},"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

* A course enrollment is created right away if the student doesn't have one for the course and term, and linked to a catalog course version when one can be identified.
* If the course enrollment already exists and `is_enrollment_component` is true, the registration status, repeat code, grade option, and enrollment level you send are written to it, and a grade is stored only if none is stored yet.
* The section enrollment is created right away under your `{id}`. If one already exists for the same course and section, for example a planned one, it's reused and given your `{id}`.
* A new course, and changes to registration status, repeat code, grade option, or enrollment level, 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.
* The search index is updated in a background task.

#### Notes

* **Send nonzero units for an existing course enrollment with null or zero units.** When `is_enrollment_component` is true, include a valid nonzero `units` value to avoid a 500 response.
* For section-enrollment PUT and PATCH, send newly supplied attributes by their catalog name (SIS ID). A display name accepted on POST can fail update validation if it differs from the catalog name. PUT and PATCH do not save attribute changes.
* **Use a new `{id}` for every enrollment.** An `{id}` already on any student's enrolled section returns a 400 with `section enrollment already exists with this id`, so replaying a successful POST fails rather than repeating it.
* **Invalid optional values don't fail the call.** An unrecognized grade, registration status, repeat code, grade option, or enrollment level is stored as null and listed in `errors[]`. Check `errors[]` on every 200.
* **Unknown course codes are added to your catalog.** Check codes against your catalog before sending to keep typos from becoming courses.
* **Each section needs its own call.** Linked sections, such as a lecture and its lab, aren't enrolled together, and capacity and waitlists aren't checked.
* **Omit coded fields you don't want to set.** Empty `grade_option` or `enrollment_level` is treated as omitted when creating the course enrollment and leaves existing values unchanged. A missing enrollment level can still be listed in `errors[]`.
* **Send `sis_update_timestamp` on every call if you use timestamp-driven data sync.** When the course enrollment already exists and `is_enrollment_component` is true, omitting it clears the stored timestamp.

#### Related APIs

* [`GET /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/get-a-section-enrollment.md) to read the enrollment.
* [`PATCH /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/update-an-existing-section-enrollment-partial.md) to update it.
* [`DELETE /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/remove-a-section-enrollment.md) to remove it.
* [`PUT /planner/enrollment/course/{username}/{term_id}`](/stellic-partner-api/course-enrollments/replace-a-student-s-course-enrollments-for-a-term.md) to send a student's full term.
* [`POST /catalog/section/{id}`](/stellic-partner-api/catalog-sections/create-a-catalog-section.md) to create the catalog section first.
