Enrolment gating and staff overrides
Active enrolment (and waitlist to active conversion) runs a single evaluation that gathers all blocking issues at once rather than failing on the first check. The API exposes both a preview (dry run, HTTP 200) and enforce paths that return a problem details document when rules still block the action.
Gates evaluated (active enrolment)
Section titled “Gates evaluated (active enrolment)”The following checks run for create active and waitlist to active:
- Age - Activity minimum/maximum age from effective fields; members without a date of birth may fail with
enrollments.date_of_birth_requiredwhen any bound applies. - Prerequisites - Level prerequisites via completed levels, required skills, or historical enrolments;
MemberLevelOverridebypasses prerequisite checks for a level. - Enrolment policies - Required policies for the target activity, scoped via family billing context; if the member is in more than one family, policy evaluation may require an explicit
family_id(enrollments.family_context_required). - Capacity - When available capacity is zero,
enrollments.capacity_fullblocks active placement (unless overridden by staff).
Each issue is returned with severity (for example blocking), human-readable title / detail, optional pointer and meta, plus can_override and required_permission after annotation.
Staff overrides
Section titled “Staff overrides”Staff with the correct Django permission can attest that a specific gate may be bypassed for a given request by sending staff_overrides: a map from issue code to a truthy value (for example { "enrollments.capacity_full": true }).
Rules:
- Overrides apply per issue code. If
staff_overrides[code]is not set, the issue remains. - The server checks
required_permissionfor that code. If the user lacks it, the issue is replaced withenrollments.insufficient_permission_to_override(meta.original_coderecords the gate). - Warnings are not overridable via this map (
can_overridestays false).
Permissions used for enrolment gates (codenames on ActivityEnrollment):
| Gate area | Permission codename |
|---|---|
| Age / DOB | activities.override_enrollment_age |
| Prerequisites | activities.override_enrollment_prerequisites |
| Policies | activities.override_enrollment_policies |
| Capacity | activities.override_enrollment_capacity |
Preview vs enforce
Section titled “Preview vs enforce”Eligibility preview (HTTP 200)
Section titled “Eligibility preview (HTTP 200)”POST .../eligibility-preview evaluates gates and returns:
issues: the same annotated issue objects you would see inside a problemerrorsarray (field names align for client reuse).can_proceed:truewhen there are no blocking issues.
The preview does not mutate enrolments. It does not apply staff_overrides; it only reflects whether the current actor is staff for can_override hints.
Create enrolment / convert waitlist (enforce)
Section titled “Create enrolment / convert waitlist (enforce)”Creating an active enrolment or converting waitlist to active applies staff_overrides for staff users. If blocking issues remain, the API responds with 422 Unprocessable Entity, Content-Type: application/problem+json, and problem type:
https://docs.keja.co/troubleshooting/status-codes/enrollments.gates_failed
See the dedicated page for that type and full JSON shape: Enrolment gates failed (enrollments.gates_failed).
Client integration tips
Section titled “Client integration tips”- Drive UI and retry logic from each issue’s
code,can_override, andrequired_permission, not from parsingdetailstrings. - For policy gates when a member belongs to multiple families, pass
family_idon create/preview payloads when the API expects family-scoped policy context. - Re-run preview after changes (DOB, policy acceptance, family selection) before submitting an enrolment.