> 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/catalog-sections/create-a-catalog-section.md).

# Create a catalog section

Use this endpoint to add a section to the catalog between data syncs, for example a late-added offering. The section belongs to an existing course, campus, and term, and can carry instructors, meeting times, parent sections, auto-enroll sections, a description, and attributes. To remove a cancelled section in the current or a future term, use [`DELETE /catalog/section/{id}`](/stellic-partner-api/catalog-sections/delete-a-catalog-section.md).

## POST /catalog/section/{id}

>

```json
{"openapi":"3.0.0","info":{"title":"Stellic for Developers","version":"1.0"},"tags":[{"name":"Catalog Sections"}],"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":{"CreateCourseSectionRequest":{"type":"object","properties":{"name":{"description":"Name of the section","type":"string"},"external_id":{"description":"Identifier your SIS uses for the section, such as a CRN. Optional; omitted or null values are stored as `\"\"`.","type":"string","nullable":true},"course":{"description":"Code of an existing course. Required; a missing field or a non-string value returns a 400.","type":"string"},"campus":{"description":"Name of the section's campus (not its display name). Lowercased, then matched exactly.","type":"string"},"term":{"description":"SIS term code (term ID) of the section's term.","type":"string"},"type":{"description":"Short name of an existing section type, for example `LEC`.","type":"string"},"start_date":{"description":"Date which classes for this section starts","type":"string","format":"date"},"end_date":{"description":"Date which classes for this section ends","type":"string","format":"date"},"linked_sections":{"description":"List of Unique identifiers of the linked sections","type":"array","items":{"type":"string"}},"topic":{"description":"Short name of one of the course's topics.","type":"string"},"delivery_mode":{"description":"Short name of an existing delivery mode.","type":"string"},"max_enrollment":{"description":"Number of total seats available","type":"integer"},"available_enrollment":{"description":"Total number of open seats available.","type":"integer"},"max_waitlist":{"description":"Maximum number of students that can be on the waitlist.","type":"integer"},"waitlist":{"description":"Total number of students in the waitlist.","type":"integer"},"details":{"$ref":"#/components/schemas/CourseSectionDetails"},"instructors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for instructor"}}}},"timings":{"description":"Meeting times for the section. Each entry takes one lowercase full weekday name in `day` (`MON` or a list of days returns a 400) and times as `HH:MM`.","type":"array","items":{"$ref":"#/components/schemas/ClassTimings"}},"support_permission_number":{"description":"Boolean value to indicate if the section allows users to pass in an optional permission number during registration. Must be a JSON boolean; null, strings, and numbers return a 400. Omit it to use `false`.","type":"boolean"},"first_auto_enroll_section":{"description":"Catalog ID of the first auto-enroll section, in the same term. Nullable. An ID that matches no section in the term returns a 400.","type":"string","nullable":true},"second_auto_enroll_section":{"description":"Catalog ID of the second auto-enroll section. Same rules as `first_auto_enroll_section`.","type":"string","nullable":true}},"required":["name","course","campus","term","type","start_date","end_date"]},"CourseSectionDetails":{"type":"object","description":"Request shape for the optional `details` block on create.","properties":{"title":{"description":"Title of the section.","type":"string"},"description":{"description":"Description of the section. Optional; omitted or null values are stored as `\"\"`.","type":"string","nullable":true},"units":{"description":"Units for the section.","type":"integer"},"units_min":{"description":"Minimum units for the section.","type":"integer"},"units_max":{"description":"Maximum units for the section.","type":"integer"},"attributes":{"description":"Section-level attributes. Each `{id}` is matched on attribute name, then on display name. An `{id}` that matches neither is added to your catalog as a new attribute with that name.","type":"array","items":{"type":"object","properties":{"id":{"description":"Attribute name or display name.","type":"string"}}}}}},"ClassTimings":{"type":"object","properties":{"start":{"description":"Start time of the class","type":"string"},"end":{"description":"End time of the class","type":"string"},"day":{"description":"Weekday for the class time","type":"string","enum":["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]},"room":{"description":"Room in which class will be conducted","type":"string"},"building":{"description":"Name of the building where class will be conducted","type":"string"},"start_date":{"description":"Start date of the class timings if the timings are only for specific dates","type":"string","format":"date"},"end_date":{"description":"End date of the class timings if the timings are only for specific dates","type":"string","format":"date"}},"required":["start","end","day","room","building"]},"CourseSection":{"type":"object","description":"Serialized response from `POST /catalog/section/{id}`. Fields are always present in the response, with `null` where the underlying value is unset. Does NOT include `course`, `external_id`, or `waitlist` (the live count).","properties":{"id":{"description":"Catalog ID of the section (the value you sent in the path).","type":"string"},"name":{"description":"Name of the section","type":"string"},"campus":{"description":"Name of the section's campus, as stored.","type":"string"},"term":{"description":"SIS term code (term ID) of the section's term.","type":"string"},"type":{"description":"Short name of the section type.","type":"string"},"start_date":{"description":"Date classes for this section start.","type":"string","format":"date"},"end_date":{"description":"Date classes for this section end.","type":"string","format":"date"},"linked_section":{"description":"Catalog ID of the first parent section (the earliest-created when there are several); `null` only when the section has no parents. Kept for backward compatibility with the deprecated `linked_section` input field; read `linked_sections` for the full list.","type":"string","nullable":true},"linked_sections":{"description":"Catalog IDs of all parent sections (any size, including empty).","type":"array","items":{"type":"string"}},"first_auto_enroll_section":{"description":"Catalog ID of the first auto-enroll section, or `null`.","type":"string","nullable":true},"second_auto_enroll_section":{"description":"Catalog ID of the second auto-enroll section, or `null`.","type":"string","nullable":true},"co_req_sections":{"description":"Co-requisite sections this section requires. Echoed from `co_req_destinations` for backward compatibility.","type":"array","items":{"type":"object"}},"co_req_destinations":{"description":"Sections this section is a co-requisite source for.","type":"array","items":{"type":"object"}},"co_req_sources":{"description":"Sections that are co-requisite sources for this section.","type":"array","items":{"type":"object"}},"topic":{"description":"Short name of the course topic, or `null`.","type":"string","nullable":true},"delivery_mode":{"description":"Short name of the delivery mode, or `null`.","type":"string","nullable":true},"teaching_location":{"description":"Short name of the teaching location, or `null`.","type":"string","nullable":true},"status":{"description":"Section status name, or `null`.","type":"string","nullable":true},"max_enrollment":{"description":"Number of total seats available.","type":"integer"},"available_enrollment":{"description":"Total number of open seats available.","type":"integer"},"max_waitlist":{"description":"Maximum number of students that can be on the waitlist.","type":"integer"},"hidden":{"description":"Whether the section is hidden in the catalog. Not writable on this endpoint; always echoed for reconciliation against your data sync.","type":"boolean"},"tags":{"description":"List of tag names attached to the section.","type":"array","items":{"type":"string"}},"details":{"$ref":"#/components/schemas/CourseSectionDetailsResponse"},"instructors":{"description":"Instructor identifiers as strings: each instructor's SIS ID or, if that instructor has no SIS ID, username. An instructor with neither identifier is omitted. Note this is `string[]`, not the `[{id: ...}]` shape the request body uses.","type":"array","items":{"type":"string"}},"timings":{"description":"Class timings for the section.","type":"array","items":{"$ref":"#/components/schemas/ClassTimings"}},"support_permission_number":{"description":"Whether the section allows users to pass an optional permission number during registration.","type":"boolean"}}},"CourseSectionDetailsResponse":{"type":"object","description":"Response shape for the `details` block on create, or `null` when the section was created without `details`. Differs from the request: `attributes` is `string[]` (attribute names), not `[{id: ...}]`.","nullable":true,"properties":{"title":{"type":"string","nullable":true},"description":{"description":"Description of the section, or `\"\"` if none was sent.","type":"string"},"units":{"type":"integer","nullable":true},"units_min":{"type":"integer","nullable":true},"units_max":{"type":"integer","nullable":true},"attributes":{"description":"Attribute names attached to the section. Note this is `string[]`, not the `[{id: ...}]` shape the request body uses.","type":"array","items":{"type":"string"}}}}}},"paths":{"/catalog/section/{id}":{"post":{"tags":["Catalog Sections"],"parameters":[{"name":"id","in":"path","required":true,"description":"Catalog ID for the new section (its stable identifier in your SIS). Leading and trailing spaces are trimmed. Must not already be in use.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCourseSectionRequest"}}}},"responses":{"200":{"description":"A successful call returns the created section as described below. HTTP 200 can also indicate failure: on an otherwise valid Bearer-token call, `\"timings\": null` returns `{\"success\":false,\"message\":{\"code\":400,\"text\":\"...\"}}`, where `text` contains an error message. This failure does not create the section. Validate that `timings` is an array of objects before sending; omit it or send `[]` for no meetings.\n\nOn success, the response is the serialized section and always includes: `id, name, campus, term, type, start_date, end_date, linked_section, linked_sections, first_auto_enroll_section, second_auto_enroll_section, co_req_sections, co_req_destinations, co_req_sources, topic, delivery_mode, teaching_location, status, details, instructors, timings, max_enrollment, available_enrollment, max_waitlist, hidden, tags, support_permission_number`. It does NOT include `course`, `external_id`, or `waitlist`. `instructors` is `string[]`, with each instructor identified by SIS ID or, if that instructor has no SIS ID, username. An instructor with neither identifier is omitted. `max_enrollment`, `available_enrollment`, and `max_waitlist` are each `null` when you omit the corresponding input on creation. `details` is `null` when you omit `details`, send `\"details\": null`, or send `\"details\": {}`. These inputs create no section details; a later PUT with a non-empty `details.description` returns HTTP 400. An omitted or null `details.description` becomes `\"\"` only when a non-empty, otherwise valid `details` object creates section details, including a non-null `details.title`. When section details exist, `details.attributes` is `string[]` (attribute names).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CourseSection"}}}},"400":{"description":"Validation failure. Messages raised while the section is saved are returned as plain strings, for example `Course with code:CS-999 does not exist`.\n\nCommon causes:\n- Section with `{id}` already exists - returns `Course section with id:{id} already exists`\n- Empty body - returns `\"course_section\" is a required field`\n- `term` unknown - returns `Unique Semester with uni_id:{term} does not exist`\n- `name`, `course`, `campus`, `term`, `type`, `start_date`, or `end_date` missing - returns `[...] fields are required and were not found in course_section`\n- `course` or `campus` is not a string, including null - returns `\"{field}\" must be a string`\n- `first_auto_enroll_section` or `second_auto_enroll_section` is neither a string nor null - returns `\"{field}\" must be a string`\n- `details.description` is neither a string nor null - returns `\"details.description\" must be a string`\n- `support_permission_number` is not a JSON boolean, including null, strings, or numbers - returns `'support_permission_number' is a field that requires a boolean value`\n- `hidden` is not a JSON boolean, including null, strings, or numbers - returns `'hidden' is a field that requires a boolean value`. POST does not apply `hidden`, even when it is a valid boolean, and creates the section with `hidden: false`. Omit it on creation. To hide the section, send a subsequent `PUT /catalog/section/{id}` with `{\"hidden\":true}`. Creation and hiding are separate writes; this API cannot create a section as hidden in one call.\n- A non-null `is_transfer`, `is_registered`, `is_residency`, `is_exam`, or `exclude_from_progress` value is neither a JSON boolean nor a string `true`/`false` (case-insensitive, with surrounding whitespace ignored). These values are checked before the section is created. Returns HTTP 400 with body `{\"errors\":[{\"message\":\"<stringified validation detail>\"}]}`. For these five flags, `message` contains the Python-style representation of an errors dictionary, including `ErrorDetail` representations, rather than only the boolean-validation message. Handle the outer `errors` array and treat `message` as text, not as another JSON object.\n- An object in the `timings` array is missing one of `start`, `end`, `day`, `room`, `building` - returns HTTP 400 with body `{\"errors\":[{\"message\":\"<documented message>\"}]}`, where `message` is `[...] fields are required for \"timings\" and were not found in request body`\n- An object in the `timings` array has null `start`, `end`, `day`, `room`, or `building` - returns HTTP 400 with body `{\"errors\":[{\"message\":\"<documented message>\"}]}`, where `message` is `\"{field}\" is a required field and cannot be null`. This does not cover a null `timings` container or null array entries; see the 200 and 500 descriptions.\n- `course` unknown - returns `Course with code:{code} does not exist`\n- `type` unknown\n- `topic` unknown for this course - returns `Course Topic with id:{topic} does not exist`\n- `delivery_mode` unknown - returns `Delivery mode with id:{delivery_mode} does not exist`\n- `campus` unknown after lowercasing - returns `Campus with name:{campus} does not exist`\n- Campus doesn't offer the term - returns `Campus Unique Semester with usem:{term} and campus:{campus} does not exist`\n- Another section on this course, campus, and term has the same `name` - returns `A course section with name:{name}, course:{course}, campus:{campus}, term:{term} already exists`\n- `linked_sections` parent(s) not found on this course - returns `Course Sections with ids:{ids} do not exist`\n- Instructor(s) unknown - returns `Instructors with uni_ids:{ids} do not exist`\n- `first_auto_enroll_section` or `second_auto_enroll_section` unknown in the term - returns `Course section with id:{auto_enroll_id} for {field} does not exist in term:{term}`\n- Timing `day` not a lowercase full weekday name - returns the value in quotes, for example `'MON'`; a list of days returns `unhashable type: 'list'`\n- Time not `HH:MM` or date not `YYYY-MM-DD` - returns a message starting `time data`"},"401":{"description":"Missing or invalid Bearer token.\n\nCommon causes:\n- Auth failure"},"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.\n\nCommon causes:\n- A null entry in `timings`, such as `\"timings\": [null]`, returns HTTP 500 before the section is created. Validate that every entry is an object before sending. Omit `timings` or send `[]` for no meetings.\n- Server error"}}}}}}
```

#### What happens after the call

* The section is created right away under your `{id}`, with its instructors, meeting times, parent sections, and attributes.
* The section is added to catalog search right away. Its instructors, meeting times, and attributes may not show in search until it's next reindexed.
* Course-level attributes and equivalencies aren't changed.

#### Notes

* **Validate `timings` before sending.** Supply an array of objects, or omit it or send `[]` for no meetings. On an otherwise valid Bearer-token call, `"timings": null` returns HTTP 200 with `{"success":false,"message":{"code":400,"text":"..."}}`; `text` contains an error message. A null entry, such as `"timings": [null]`, returns HTTP 500. Neither failure creates the section. These differ from null required fields inside a timing object, which return the documented HTTP 400 validation response.
* **Include a title when creating section details.** A description can be added or changed later only on a section that already has section details.
  * **POST (creation):** Include a non-null string `details.title` with `details.description` and your other section details. A blank title (`""`) is accepted. Sending only `details.description`, or sending `details.title` as `null`, returns HTTP 400 and rolls back section creation. Do not copy the description-only PUT payload into POST.
  * **PUT (update):** For a section that already has section details, send `{"details":{"description":"New description"}}` to `PUT /catalog/section/{id}`, not a top-level `description`. You do not need to resend the title. Omitting `details.description` leaves it unchanged; sending `null` or `""` clears an existing description to `""`. If the section has no section details, a non-empty description returns HTTP 400 with `{"errors":[{"message":"Course section with id:{id} has no section details to add a description to. Send the description with the section's other details when creating it."}]}`. This validation failure occurs before saving any requested updates; other updates in the same request remain unsaved. Sending `null` or `""` when no section details exist instead succeeds without creating section details. The PUT response's top-level `description` is `null` when the section has no section details, and `""` when its section details have a blank description.
* **Auto-enroll sections must already exist in the same term.** Create them first, then name them in `first_auto_enroll_section` or `second_auto_enroll_section`. An ID that matches no section in the term returns a 400 and creates nothing. For PUT, you can update either field independently: omitting a field preserves its link, sending `null` or `""` clears it, and sending a non-empty ID matches a section in this section's own term.
* **Parent sections must belong to the same course.** Create the parent first, then list its `{id}` in the child's `linked_sections`; a parent on another course is reported as not found.
* **Meeting days are lowercase full names, one per entry.** Send `monday` through `sunday` with times as `HH:MM`. `MON` or a list of days returns a 400.
* **Unknown attribute names are added to your catalog.** An attribute that doesn't match an existing name is created, so check names first to keep typos out of your catalog.
* **`campus` is the campus name, not its display name.** It's lowercased and must then match exactly.
* **Create first, then hide.** POST does not apply `hidden`, even when you send a valid boolean, and creates the section with `hidden: false`. To hide it, send a subsequent `PUT /catalog/section/{id}` with `{"hidden":true}`. Creation and hiding are separate writes; this API cannot create a section as hidden in one call.
* Omit `exclude_from_progress` from section requests; it does not change the catalog section.
* To change instructors after creation, use `PUT /catalog/section/{id}` with a non-empty `instructors` array. Validate every entry's `id` as a non-null string before sending; IDs match instructor SIS ID or username. This replaces the whole roster, so include every instructor you want to retain. Omitting `instructors`, sending `null`, or sending `[]` leaves the roster unchanged; `[]` cannot remove the last instructor.
* PUT instructor validation differs from creation: an entry missing `id` returns HTTP 400 with `{"errors":[{"message":"Every entry in \"instructors\" needs an \"id\""}]}`. Unknown string IDs return HTTP 400 in the same error shape, with message `Instructors with ids:[...] do not exist`, rather than POST's `uni_ids` wording. Malformed ID types can instead return HTTP 500 with `{"errors":[{"message":"..."}]}`, where `message` is a generic error message. For example, `{"instructors":[{"id":"definitely-unknown"},{"id":123}]}` returns HTTP 500 when both IDs are unknown. These instructor-resolution failures occur before saving: the roster, seat counts, and visibility remain unchanged. Correct the IDs to non-null strings before resending.
* A successful instructor PUT returns `id`, `waitlist`, `max_enrollment`, `available_enrollment`, `max_waitlist`, `support_permission_number`, `hidden`, `first_auto_enroll_section`, `second_auto_enroll_section`, and `description`. Unlike the POST response, it does not include `instructors`; do not use the PUT response to reconcile the roster.
* Only delete sections in the current or a future term. Eligibility uses Stellic's institution-wide current-term calculation, not the individual section's `start_date` or `end_date`. Between terms, the most recently ended term may still count as current and remain eligible for deletion. Check the section's dates before deleting; do not rely on this guard alone to protect every section whose classes have ended. DELETE leaves a section classified as past-term unchanged and returns HTTP 400 with `{"errors":[{"message":"Course section with id:{id} is in past term:{term} and cannot be deleted"}]}`.

#### Related APIs

* [`PUT /catalog/section/{id}`](/stellic-partner-api/catalog-sections/update-section-enrollment-caps.md) to update the section's seat counts, instructors, visibility, auto-enroll sections, or description.
* [`DELETE /catalog/section/{id}`](/stellic-partner-api/catalog-sections/delete-a-catalog-section.md) to delete a cancelled section in the current or a future term.
* [`PUT /catalog/section/timings/{id}`](/stellic-partner-api/catalog-sections/replace-a-section-s-timings.md) to replace its meeting times.
* [`POST /catalog/section/restriction/{id}`](/stellic-partner-api/catalog-sections/add-a-restriction-to-a-section.md) to restrict who can see or register for it.
* [`POST /planner/enrollment/section/{id}`](/stellic-partner-api/section-enrollment/enroll-a-student-in-a-section.md) to enroll a student in it.
