Skip to content

Student Notes API ​

Base URLs: /api/v1/student-notes, /api/v1/notifications

Notes on a student record (migration 308). Anyone who can open a student's record can add a note to it: rich text (tables included, as in staff email) and attached files. The author decides who else may read it, and the people that reaches are notified by the header bell and by email. The note itself is never sent by email: the message says who wrote a note and about which student, and links to it.

Who sees what ​

ReaderSees
The authorTheir own notes, always, including private and retracted ones
A reader acting under a role the author choseThe note, if their own grant scope on STUDENTS.TAB.NOTES reaches the student
Holders of STUDENTS.NOTES.VIEW_ALL (registrar)Every note that is not private, including retracted ones
Holders of STUDENTS.NOTES.VIEW_PRIVATE (registrar)Private notes too, but only after a reveal: a written reason, logged, opening them for 30 minutes
The studentNotes shared with the STUDENT role that are neither private nor retracted (My Notes)
  • A chosen role is scoped. Choosing Advisor reaches the student's own advisors, not every advisor; choosing Academic Administrator reaches those whose unit covers the student's program. The same rule decides who is notified.
  • Roles are matched against the role the reader is acting under. Someone who is both an advisor and an instructor sees an advisor-only note while acting as an advisor.
  • Instructors hold STUDENTS.TAB.NOTES at OWN_SECTIONS: they may add and read notes about students actively enrolled in a section they teach in a term that has not ended. That opens the notes only; the rest of the record stays closed to them.
  • Impersonation is not a reveal. An administrator impersonating an author does not see that author's private notes, and private notes cannot be revealed while impersonating.
  • Anything the caller may not reach answers 404, never 403.

Notes are permanent ​

A trigger refuses any change to a note's subject, body, category, term or author, and refuses every DELETE. The author may only:

  • add roles (never remove them), which notifies the people newly reached;
  • make a private note shared (never the reverse);
  • retract the note, which hides it from everyone it was shared with. The author and VIEW_ALL holders still see it, marked as retracted.

None of the note tables are row-audited: the generic auditor would copy every note body, private ones included, into core.audit_log. The tables carry their own who and when.

Endpoints ​

All staff routes are gated on STUDENTS.TAB.NOTES at VIEW, writes included, because anyone who can see a student's record may write a note on it. {personID} is the student's person UUID.

MethodPathNotes
GET/student-notes/students/{personID}/notesThe notes the caller may see. ?include_private=1 adds private notes while a reveal is open. Also returns can_view_all, can_reveal_private, can_read_access_log, hidden_private_count and private_revealed_until
GET/student-notes/students/{personID}/compose-optionsRoles a note may be shared with, recent terms, and the NOTES attachment limits
POST/student-notes/students/{personID}/notesJSON, or multipart with the JSON in a note part and files in files parts
POST/student-notes/students/{personID}/private-reveal{"reason": "..."} (5–1000 characters). Needs STUDENTS.NOTES.VIEW_PRIVATE
GET/student-notes/students/{personID}/private-access-logNeeds STUDENTS.NOTES.ACCESS_LOG
GET/student-notes/private-access-logEvery reveal, every student. Needs STUDENTS.NOTES.ACCESS_LOG
POST/student-notes/notes/{noteID}/readMarks the note read, and clears its bell entry
POST/student-notes/notes/{noteID}/audienceAuthor only. {"viewer_role_ids": [..], "make_shared": bool}
POST/student-notes/notes/{noteID}/retractAuthor only
GET/student-notes/notes/{noteID}/attachments/{attachmentID}Always a download
GET/student-notes/mineThe student's own (STUDENT.MY_NOTES)
POST/student-notes/mine/{noteID}/read
GET/student-notes/mine/{noteID}/attachments/{attachmentID}

Creating a note ​

json
{
  "subject": "Probation meeting",
  "body_html": "<p>Met to discuss <strong>probation</strong>.</p><table>...</table>",
  "note_category_id": 2,
  "term_id": 6123,
  "is_private": false,
  "viewer_role_ids": [4, 7]
}
  • body_html is cleaned with the staff-email allowlist (sanitizehtml.CleanEmail). It may be empty only if files are attached.
  • A private note must have no viewer_role_ids.
  • note_category_id is a NOTE_CATEGORY lookup value (records.lookup_note_category).
  • Files must pass the NOTES attachment surface, which an administrator sets on Admin > Email Sending > Attachments, inside the organizational ceiling. The denylist and the content check apply as for email.

Notifications ​

core.notifications is a general per-user inbox shown as the header bell. Rows hold a kind, an English title, params that the bell puts into its own translated wording, and a link_path. They never hold content.

MethodPath
GET/notifications (paginated)
GET/notifications/unread-count
POST/notifications/{notificationID}/read
POST/notifications/read-all

All four are self-service: they read and write only the session user's own rows. The two writes are allowed in restricted write mode, like other per-user state.

A note notifies each user once, however many of the chosen roles they hold and however often its audience is widened (uq_notifications_recipient_source). Email uses the templates STUDENT_NOTE_ADDED (staff) and STUDENT_NOTE_SHARED_WITH_YOU (the student), editable under Admin > Email Templates. The link in them is built from APP_PUBLIC_URL, which defaults to SAML_SP_BASE_URL.

Student Information System Documentation