Enrollments
An Enrollment ties a user to a class for a school year. OnEdHub derives enrollments from Vigilo group memberships (students), group teacher lists (teachers from the schedule) and function schedules (kontaktlærere and other function-based teacher roles).
The date rules are different for students and teachers. They are documented separately below — do not assume the student rule applies to a teacher enrollment.
| Enrollment | Derived from | role | Date rule |
|---|---|---|---|
| Student | Group membership (studentGroupMemberships) | student | Student dates |
| Teacher from schedule | Group teacher list (groupTeachersFromSchedule) | teacher | Teacher dates |
| Teacher from function schedule | Function schedule with a group/class | teacher | Function schedule dates |
Student enrollment dates
When a student is a member of a group, the effective membership period (the dates the student is treated as an active group member) is not necessarily the same as the dates registered on the membership. It is calculated from three sources:
- Group membership dates — the start/end dates registered on the membership itself.
- Student dates — the student's overall enrollment start/end dates.
- School year boundaries — the start and end of the school year the membership belongs to.
The membership cannot be active outside any of these windows, so the effective dates are bounded by all three.
Effective start date (beginDate)
The effective start date is the latest of:
- The group membership start date.
- The student's start date (if set).
- The school year start date.
A student is never considered an active group member before they were enrolled, before the membership officially started, or before the school year began — whichever comes last wins.
Examples
| Student start | Membership start | School year start | Effective start |
|---|---|---|---|
| (none) | 2025-09-01 | 2025-08-01 | 2025-09-01 |
| 2025-10-01 | 2025-08-01 | 2025-08-01 | 2025-10-01 |
| 2025-08-15 | 2025-09-01 | 2025-08-01 | 2025-09-01 |
| 2025-07-01 | 2025-06-01 | 2025-08-01 | 2025-08-01 |
Effective end date (endDate)
The effective end date is the earliest of:
- The group membership end date (if set).
- The student's end date (if set).
- The school year end date.
A student is never considered an active group member after they left, after the membership officially ended, or after the school year ended — whichever comes first wins. If neither the membership nor the student has an end date, the membership runs to the end of the school year.
Examples
| Student end | Membership end | School year end | Effective end |
|---|---|---|---|
| (none) | (none) | 2026-08-01 | 2026-08-01 |
| 2026-03-12 | (none) | 2026-08-01 | 2026-03-12 |
| 2026-09-01 | (none) | 2026-08-01 | 2026-08-01 |
| (none) | 2026-03-12 | 2026-08-01 | 2026-03-12 |
| (none) | 2026-09-01 | 2026-08-01 | 2026-08-01 |
| 2026-03-11 | 2026-03-12 | 2026-08-01 | 2026-03-11 |
| 2026-03-12 | 2026-09-01 | 2026-08-01 | 2026-03-12 |
| 2026-09-01 | 2026-03-12 | 2026-08-01 | 2026-03-12 |
| 2026-09-01 | 2026-10-01 | 2026-08-01 | 2026-08-01 |
When the student's dates fall outside the school year
The rules above assume the student is enrolled at some point during the school year. If the student's enrollment period does not overlap the school year at all, the effective end date lands before the effective start date and no membership exists for that school year — the student simply wasn't there.
Two examples of this:
| Student dates | School year | Outcome |
|---|---|---|
| 2026-09-01 → 2027-01-01 | 2025-08-01 → 2026-08-01 | Student wasn't enrolled until after the school year ended — no membership for this school year. |
| 2024-01-01 → 2025-06-01 | 2025-08-01 → 2026-08-01 | Student left before the school year started — no membership for this school year. |
The enrollment is removed, not deactivated. When the windows don't overlap, the enrollment is deleted from the API — it does not linger with
status: "tobedeleted". If you had already fetched it, a later pull will simply not contain it, andGET /enrollments/{sourcedId}will return404.Your sync must therefore be able to handle records disappearing, not just changing status. A sync that only ever upserts what it receives will keep a stale enrollment forever. Reconcile per class, or treat a daily full pull as authoritative.
If you see a membership where the student's enrollment period doesn't overlap the school year, this typically indicates a data inconsistency in the source system worth reviewing — the membership shouldn't have been registered against that school year in the first place.
Summary
Begin date = the latest of all three start dates. End date = the earliest of all three end dates.
The school year always acts as a hard boundary on both ends — a membership is only ever counted within its school year.
Teacher enrollment dates
Teacher enrollments derived from a group's schedule-based teacher list follow a different rule from students. There is no membership start date involved — only the employee's active period and the school year.
beginDate
| Employee active period | beginDate |
|---|---|
Active period exists, fromDate is after the school year start | fromDate |
Active period exists, fromDate is on/before the school year start, or not set | School year start |
| No active period at all | Unchanged — the existing enrollment keeps its current beginDate; a brand-new enrollment gets the school year start |
endDate
| Employee active period | endDate |
|---|---|
Active period exists, toDate is before the school year end | toDate |
Active period exists, toDate is on/after the school year end, or not set | School year end |
No active period, existing endDate is already in the past | Unchanged |
No active period, existing endDate is in the future or not set | Today |
The "no active period" rows matter in practice: a teacher whose employment record has gone away is closed off as of today rather than being left running to the end of the school year.
Teachers removed from a group
When a teacher is no longer in the group's schedule-based teacher list, their enrollment is not deleted. It is closed off instead:
status→tobedeletedendDate→ today, unless the existingendDateis already in the past, in which case it is kept
Two kinds of enrollment are deliberately left alone by this pass: student enrollments, and enrollments for kontaktlærere (contact teachers) on the class — the latter are owned by the function-schedule flow below and would otherwise be clobbered.
So: students disappear, teachers get tobedeleted. Handle both.
Function schedule enrollment dates
Teacher enrollments created from a function schedule (kontaktlærer and other function-based roles) take their dates straight from the function schedule — they are not clamped to the school year:
beginDate= the function schedule's start dateendDate= the function schedule's end date
On a function schedule delete event the original beginDate and metadata are kept and only the end date and status are updated.
If a function schedule has no group/class assigned, no enrollment can be created and the event is skipped. This is logged but does not fail — the practical consequence is a silently missing teacher enrollment, so a kontaktlærer who is absent from the API is worth checking against the function schedule in Vigilo.
status
status is derived from the calculated dates — it is not a field you can filter on to mean "currently teaching" without understanding the rule.
For student and schedule-based teacher enrollments:
- If the underlying user is
tobedeleted, the enrollment istobedeleted. - Otherwise
activewhenbeginDate <= todayandendDate > today. - Otherwise
tobedeleted.
endDateis exclusive,beginDateis inclusive. An enrollment that starts today isactive. An enrollment that ends today is alreadytobedeleted. This asymmetry is intentional but easy to get wrong when reimplementing the rule locally.
For function-schedule enrollments the rule is active only when startDate < today andendDate > today — both ends exclusive, so a function schedule that starts today is not yet active.
dateLastModified and no-op updates
OnEdHub does not rewrite an enrollment when a re-processed source event would produce an identical record. If nothing but the timestamp would change, the write is skipped and dateLastModified stays where it was.
This makes dateLastModified a better change signal than it used to be, but read Pagination & sync cadence before relying on it — the nightly OAS sync still re-stamps entities, so it is only a true delta filter intra-day.