> 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/program-enrollments/update-fields-on-an-existing-program-enrollment.md).

# Update fields on an existing program enrollment

Use this endpoint to certify a student at graduation, reorder their programs, move an enrollment to a different catalog year, or add concentrations. Only the fields you send change. The student must already be enrolled in the program; to create an enrollment, use `POST` on the same path.

## PATCH /planner/enrollment/program/{username}/{program\_id}

>

```json
{"openapi":"3.0.0","info":{"title":"Stellic for Developers","version":"1.0"},"tags":[{"name":"Program 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":{"StudentProgramRequest":{"type":"object","properties":{"certification_status":{"type":"string","enum":["true","false"],"description":"Whether the student has completed (graduated from) this program. Send a string: `\"true\"` (case-insensitive) certifies, and any other string, including `\"false\"`, does not. A JSON boolean is accepted but always read as `false`, so boolean `true` does not certify. A non-zero number returns a 500."},"certification_date":{"type":"string","format":"date","description":"Certification date in `YYYY-MM-DD` format. On a new enrollment it is saved as sent; when the call updates an existing program (`PATCH`, or `POST` declaring an undeclared program), it is saved only if the same call certifies. A malformed value in a call that would save it returns a 500 and nothing is saved; otherwise it is a warning in `errors[]` on a 200. Validate dates before sending. The response renders this field as `Month DD, YYYY` with a zero-padded day (for example `\"May 05, 2026\"`) or `null`, not ISO."},"order":{"type":"integer","nullable":true,"description":"The order in which program requirements are displayed and processed in the audit for this student. Optional; if omitted, stored as `null` (not `0`). Final audit position also depends on certification status, diploma order, program type, primary-major status, and auto-order rules, so treat this as a hint."},"catalog_term":{"description":"SIS term code (as sent in your data sync, for example `fall-2024`) that pins the catalog year used to select the audit version for this program enrollment. Saved on both `POST` and `PATCH`; a changed value re-matches the student's programs to published audits, except certified or locked ones. Send `null` to clear it. An unknown code is a warning in `errors[]` and leaves the stored value unchanged. Not related to section enrollment.","type":"string"},"concentration_ids":{"type":"array","description":"List of concentration `short_name` values to link to this enrollment. Additive on this endpoint; `PATCH` cannot remove concentrations later. IDs that don't belong to this program are reported as warnings in `errors[]`.","items":{"type":"string"}}}},"StudentProgramResponseSuccess":{"type":"object","properties":{"status":{"type":"string"},"state":{"type":"object","properties":{"program":{"type":"string","description":"The program's `short_name`, as passed in the path."},"certification_status":{"type":"boolean"},"certification_date":{"type":"string","nullable":true,"description":"Rendered as `Month DD, YYYY` with a zero-padded day (for example `May 05, 2026`), not ISO."},"order":{"type":"integer","nullable":true},"concentrations":{"type":"array","description":"Every concentration linked to this enrollment, including ones the student has planned but isn't enrolled in (`enrolled` false).","items":{"type":"object","properties":{"name":{"type":"string"},"enrolled":{"type":"boolean"},"planned":{"type":"boolean"},"is_internal":{"type":"boolean"},"is_archived":{"type":"boolean"},"concentration_type":{"type":"object","properties":{"short_name":{"type":"string"},"name":{"type":"string"}}}}}},"units_label":{"type":"object","description":"Present only when the program is configured with a custom units label.","properties":{"field_name":{"type":"string"},"label_plural":{"type":"string"},"label_singular":{"type":"string"}}}}},"errors":{"type":"array","description":"Warning strings for optional fields that were skipped (`POST` and `PATCH` only; the `GET` response has no `errors` key).","items":{"type":"string"}}}},"StudentLinkResponseFailure":{"allOf":[{"$ref":"#/components/schemas/GenericResponseFailure"},{"type":"object","properties":{"identifiers":{"type":"object","properties":{"student_username":{"type":"string"}}}}}]},"GenericResponseFailure":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}},"paths":{"/planner/enrollment/program/{username}/{program_id}":{"patch":{"tags":["Program Enrollments"],"parameters":[{"name":"username","in":"path","required":true,"description":"Student's Stellic username. Send it as stored in Stellic; this endpoint does not lowercase or case-normalize it.","schema":{"type":"string"}},{"name":"program_id","in":"path","required":true,"description":"The program's short name, as sent in your data sync. Matched exactly; case-sensitive.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"A request body is required. An empty `{}` is rejected with 400. A body containing only\nfields this endpoint doesn't recognize passes validation and returns a 200 with the\nenrollment unchanged. Only the fields you send change. A changed `catalog_term` is saved\nand the student's programs are re-matched to published audits (certified or locked\nprograms keep their current audit). `concentration_ids` is additive on `PATCH`; it\ncannot remove concentrations. To certify, send `certification_status` as the string\n`\"true\"`; a JSON boolean `true` is read as `false`. `certification_date` is saved only\nwhen the same call certifies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudentProgramRequest"}}}},"responses":{"200":{"description":"Update applied. Inspect `errors[]` for soft warnings on invalid optional fields\n(unknown `catalog_term`, unknown concentration `short_name`, or a malformed\n`certification_date` in a call that doesn't certify). A body containing only\nunrecognized fields also returns 200 with the enrollment unchanged.\n`state.certification_date` is rendered as `Month DD, YYYY` with a zero-padded day\n(for example `\"May 05, 2026\"`) or `null`, not ISO. `state.order` may be `null`.\nConcentration entries have `name`, `enrolled`, `planned`, `is_internal`,\n`is_archived`, and a nested `concentration_type` (`short_name`, `name`); concentrations\nthe student has planned but isn't enrolled in are returned with `enrolled: false`.\n`state.units_label` is present only for programs configured with a custom units label.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudentProgramResponseSuccess"}}}},"400":{"description":"Hard validation failure. Typical causes: no matching non-test student, program doesn't\nexist, student is archived, student is not enrolled in this program, empty body. An unknown\n`catalog_term` or concentration does NOT return 400; it is a warning in a 200. A\nmalformed `certification_date` is a warning in a 200 unless the call certifies, in\nwhich case it returns a 500.\n\nCommon causes:\n- No matching non-test student, including when the only matching record is a test account - returns `No student exists with username: {username}`\n- Student is archived - returns `Student with username {username} is archived`\n- Program does not exist - returns `No program exists with unique identifier: {program_id}`\n- Program is a compliance requirement (compliance requirements are not programs a student can be enrolled in through the API) - returns `Compliance requirements are not available via the API: {program_id}`\n- Student is NOT enrolled in this program - returns `{username} is not enrolled into program {program_id}`\n- Empty or missing request body - returns `request body with student program information is required.`","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/StudentLinkResponseFailure"},{"type":"object","properties":{"identifiers":{"type":"object","properties":{"student_username":{"type":"string"},"program_id":{"type":"string"}}}}}]}}}},"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- Unhandled server error - returns `Stellic couldn't process your request`\n- Malformed `certification_date` in a call that certifies - returns `Stellic couldn't process your request`; nothing is saved\n- `certification_status` sent as a non-zero number - returns a 500; send the string `\"true\"` or `\"false\"`"}}}}}}
```

#### What happens after the call

* The fields you sent are updated right away.
* If `catalog_term` changed, the student's programs are re-matched to published audits right away. Certified or locked programs keep their current audit.
* The student's cached audit is invalidated right away. After general requirements are recalculated, it may be invalidated again in the background.
* General requirements and program order are recalculated in a background task. If your institution uses the pathway experience, auto-applied pathways are re-evaluated after that.
* Changes to order, certification, and catalog term appear in the student's activity history shortly after.

#### Notes

{% hint style="warning" %}
**Send `certification_status` as the string `"true"` to certify.** A JSON boolean `true` is read as `false`, so sending `true` to an already-certified enrollment un-certifies it and clears its certification date.
{% endhint %}

* **`certification_date` is saved only when the same call certifies.** A date sent alone, or sent for an enrollment that's already certified, is ignored, and un-certifying clears the date. To correct the date, send `"false"`, then `"true"` with the new date; at institutions with degree locking, this unlocks the program's audit and then locks it again. A malformed date in a certifying call returns a 500.
* **Concentrations merge.** Each listed concentration is linked, or switched to `enrolled: true` if it's already listed; ones you leave out stay as they are. To remove one, delete the enrollment and enroll again with the final list.

#### Related APIs

* [`POST /planner/enrollment/program/{username}/{program_id}`](/stellic-partner-api/program-enrollments/enroll-a-student-in-a-program.md) to create the enrollment, or to enroll again after a `DELETE`.
* [`GET /planner/enrollment/program/{username}/{program_id}`](/stellic-partner-api/program-enrollments/get-a-student-s-program-enrollment.md) to read the enrollment.
* [`DELETE /planner/enrollment/program/{username}/{program_id}`](/stellic-partner-api/program-enrollments/remove-a-student-s-program-enrollment.md) to remove the enrollment.
