Skip to main content
Version: Next

Participant Flow

The public registration experience lives at /register. Participants do not need a portal account. They only need the access code distributed by their school.


Step-by-step participant journey

+----------------------------------------------------+
| STEP 1 - Code Entry (/register) |
| |
| Participant enters: |
| * Access code (required) |
| * School code (optional hint, helps routing) |
| |
| System response: |
| * Issues a short-lived registration token |
| * Returns session config + participant hints |
| * Returns selectionContext (grades, classes, etc) |
| * Returns nextStep |
+---------------------+------------------------------+
|
+-----------+------------+
| |
v v
nextStep = nextStep =
"complete_form" "read_only_status" or
| "corrections_required"
| |
v v
+-------------------+ +----------------------------+
| STEP 2 - Form | | Status View |
| | | * already submitted |
| Auto-saved as | | * correction message shown |
| draft on every | | * or read-only if approved |
| field change | +----------------------------+
+--------+----------+
|
v
+-------------------+
| STEP 3 - Review |
| |
| Summary of what |
| will be submitted|
| warnings listed |
+--------+----------+
|
v
+-------------------+
| STEP 4 - Submit |
| |
| Submission status|
| shown on success |
+-------------------+

Code validation

Public registration page showing the access code entry field and school name

The public registration entry screen at /register. No login is required. The participant enters their unique access code to begin.

Request

POST /api/v1/public/registrations/access-codes/validate
{
"code": "A1B2",
"schoolCode": "HGS",
"deviceName": "Chrome / MacBook"
}

schoolCode is an optional routing hint - if a tenant runs multiple schools and codes are not globally unique, the school code narrows which school to search.

Response

{
"registrationToken": "eyJhbGci...",
"expiresInSeconds": 3600,
"nextStep": "complete_form",
"session": {
"id": "...",
"mode": "student_intake",
"title": "Form 1 Intake - 2026",
"classDivisionId": null,
"gradeLevelId": "grade-form-1-id",
"requiredFields": {},
"approvalPolicy": {}
},
"participant": {
"type": "student",
"displayNameHint": "John Doe",
"studentNumberHint": "S00123"
},
"selectionContext": {
"school": { "id": "...", "name": "Highfield Secondary" },
"gradeLevels": [...],
"classDivisions": [...],
"subjects": [...],
"departments": [...]
}
}

The registrationToken must be stored (in memory or sessionStorage) and passed as a Bearer token on all subsequent calls.

Frontend hook: useValidateRegistrationAccessCode()


The nextStep field

ValueMeaningWhat to show
complete_formCode is valid and this participant can still submitRegistration form
read_only_statusAlready submitted and under review, or approvedStatus page (read-only)
corrections_requiredStaff sent back a correction requestForm with correctionMessage banner

Loading form state (/me)

Registration form showing participant name fields, date of birth, grade level selector, and guardian section

The registration form after a valid code is entered. Locked fields (grade, class) are disabled. Auto-save indicator appears in the top-right corner.

Once a token is obtained, call GET /api/v1/public/registrations/me to load the current submission state:

const { data: me } = usePublicRegistrationMe(registrationToken);

The response includes:

  • session - session metadata (title, required fields, locked placement IDs)
  • participant - hints about who this code belongs to
  • selectionContext - available grades, classes, subjects, departments for dropdowns
  • submission - current draft payload, status, canEdit, and any correctionMessage

Placement locks

If the session has gradeLevelId or classDivisionId set, those values are authoritative and cannot be changed by the participant.

Implementation note

The front-end syncs locked placement across all three copies that form state may maintain (currentGradeLevelId, gradeLevelId, and admission.gradeLevelId) to prevent UI drift. If only classDivisionId is locked, the gradeLevelId is derived from selectionContext.classDivisions[].gradeLevelId.


Auto-save draft

Every meaningful form change triggers a debounced save-draft call:

PUT /api/v1/public/registrations/me/draft
{
"payload": { "firstName": "John", "lastName": "Doe", ... },
"sourceDocument": { ... }
}

Drafts are saved server-side with a version counter. If the browser crashes or closes, the participant can re-enter their code and resume from where they left off.

Frontend hook: useSavePublicRegistrationDraft(registrationToken)


Summary / pre-submit check

Before showing the final submit button, the form calls:

POST /api/v1/public/registrations/me/summary

The response indicates:

  • canSubmit - whether the payload passes all required field checks
  • warnings - non-blocking issues (e.g. a field that looks unusual)
  • missingFields - field paths that are required but empty
  • confirmationText - human-readable text to show on the confirmation screen

Frontend hook: useSummarizePublicRegistrationDraft(registrationToken)


Final submission

Confirmation screen showing successful submission with status message and session title

The success screen after a participant submits. It shows the session title, a confirmation message, and instructions on next steps (e.g. "a staff member will review your details").

POST /api/v1/public/registrations/me/submit

Same body as save-draft. On success the submission moves to submitted status and the access code moves to submitted.

The response includes:

  • submissionId
  • status - "submitted"
  • message - confirmation message to display
  • canEdit - false for newly submitted forms (staff review pending)

Frontend hook: useSubmitPublicRegistration(registrationToken)


Corrections resubmission

If staff send back a correction request (needs_correction), the participant:

  1. Re-enters their code (or refreshes if token is still valid)
  2. Sees nextStep: "corrections_required" and the correctionMessage from staff
  3. Updates the indicated fields
  4. Resubmits via:
POST /api/v1/public/registrations/me/corrections

Frontend hook: useResubmitPublicRegistrationCorrections(registrationToken)


Token transport

By default the registration token is sent as a standard Authorization: Bearer header. For environments that cannot set custom headers, the token can also be sent via X-Registration-Token header, or both simultaneously:

usePublicRegistrationMe(token, { tokenTransport: "header" });
// or
usePublicRegistrationMe(token, { tokenTransport: "both" });

Important constraints

  • The public /register routes do not use the tenant SchoolProvider. Do not use tenant-only comboboxes or hooks that call useSchoolContext() on these pages.
  • Build all dropdowns from selectionContext returned by the validate and /me responses - do not call school-scoped API hooks.
  • The registration token is short-lived (typically 1 hour). If it expires mid-session, the participant must re-enter their code to get a fresh token.

API quick reference

OperationMethod + PathHook
Validate codePOST /public/registrations/access-codes/validateuseValidateRegistrationAccessCode()
Get form stateGET /public/registrations/meusePublicRegistrationMe(token)
Save draftPUT /public/registrations/me/draftuseSavePublicRegistrationDraft(token)
Summarize draftPOST /public/registrations/me/summaryuseSummarizePublicRegistrationDraft(token)
SubmitPOST /public/registrations/me/submituseSubmitPublicRegistration(token)
Resubmit correctionsPOST /public/registrations/me/correctionsuseResubmitPublicRegistrationCorrections(token)