notifications

9 endpoints.

POST/api/notifications/certification-requirements Bearer token

Send a certification requirements notification email

Builds a digest of certification requirements grouped into due windows (overdue, next 10/30/60 days), plus the training events upcoming in the same horizon, renders it to HTML + plaintext, validates it against the @schemavaults/send-email request schema, and submits it to the configured transactional mail server. Unless `dry_run` is set, the digest is also pushed to the user's in-app inbox (`GET /api/users/me/notifications`, the `/notifications` page) — whatever becomes of the email, so a 404 or 502 here still leaves it there. Every requirement links to its page, and the quiz, course or job aid that completes it. `audience: trainee` covers the user's own requirements; `audience: manager` covers the trainees holding roles the user manages. Superusers only — this is the manual trigger behind the admin notifications page; scheduled sending will reuse the same pipeline. Use `dry_run` to validate without sending.

Request body

application/jsonrequiredSendCertificationNotificationRequest
FieldTypeRequiredDescription
userstring (uuid)yes

`users.id` of the user the digest is about — and, unless `email` overrides it, the recipient (resolved from their account's profile).

audiencestringyes

Whose deadlines the digest covers: `trainee` — the user's own certification requirements; `manager` — the requirements of every trainee holding a role the user manages (scoped to those managed roles).

one of "trainee" | "manager"

emailstring (email)no

Send to this address instead of the user's account email.

dry_runbooleanno

Build, render and validate the email without contacting the mail server. Defaults to false.

include_emptybooleanno

Send an "all clear" email even when no requirement is overdue or due within 60 days and no training event is upcoming. Defaults to false (such digests are skipped).

Responses

StatusDescriptionBody
200The digest was sent, validated (dry_run), or skipped as empty.CertificationNotificationResult
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
404No recipient email address could be resolved for the user.ErrorResponse
500The digest could not be built or rendered.ErrorResponse
502The mail server rejected the message or could not be reached.ErrorResponse
GET/api/notifications/schedule Bearer token

Read the notification schedule

When the scheduled certification requirement digests go out — every day or one weekday, at an hour of a timezone — with the next occurrence, the last one claimed, and the latest runs. Superusers only.

Responses

StatusDescriptionBody
200The current schedule.NotificationSchedule
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
500The schedule could not be read.ErrorResponse
PATCH/api/notifications/schedule Bearer token

Change the notification schedule

Sets any of: whether scheduled sending is on, the cadence (`daily` or `weekly`), the weekday, the hour and the IANA timezone. An omitted field is left as it is. The hourly cron tick reads the schedule from the database, so a change takes effect within the hour. Enabling a schedule whose latest occurrence has passed does not send retroactively: the first run is the next occurrence. Superusers only.

Request body

application/jsonrequiredUpdateNotificationScheduleRequest
FieldTypeRequiredDescription
enabledbooleanno—
cadencestringno

`daily`: every day at the hour. `weekly`: on `weekday` only.

one of "daily" | "weekly"

weekdayintegerno

Day of the week, 0 = Sunday … 6 = Saturday; used when weekly.

minimum 0 · maximum 6

hourintegerno

Hour of the day, 0–23, on the wall clock of `timezone`.

minimum 0 · maximum 23

timezonestringno

IANA timezone the hour is read in.

1–64 characters

Responses

StatusDescriptionBody
200The updated schedule.NotificationSchedule
400The request body failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
500The schedule could not be written to.ErrorResponse
GET/api/notifications/schedule/tick Bearer token

Tick the notification schedule (cron)

Called hourly by the deployment's cron (`vercel.json`). Resumes a run a previous tick left unfinished; otherwise, when the schedule is enabled and its latest occurrence has not been claimed yet, claims it and sends the digests to every trainee with a role and every manager, within one invocation's time budget. Every tick, whatever the schedule, also pushes the quiz notifications time sets off to the in-app inbox: sittings that opened, results a sitting's close released, and attempts that reached the grading queue — auto-submitting attempts whose deadline has passed first. Authenticated by the deployment's `CRON_SECRET` as a bearer token, not by an OIDC access token; a deployment without the variable answers 401 to everyone.

Responses

StatusDescriptionBody
200What the tick did.NotificationTickResult
401The cron secret is missing or wrong.ErrorResponse
500The tick failed part-way; the next tick resumes the run.ErrorResponse
POST/api/notifications/schedule/run Bearer token

Send the scheduled digests now

Starts a batch outside the schedule — the same digests to the same population the cron tick would send, whether or not scheduled sending is on — and processes as much of it as one invocation's time budget allows; a larger population is finished by the hourly tick. An unfinished earlier run is resumed instead of starting another, and while another invocation is still working on one the answer is `busy` with nothing started. Superusers only.

Responses

StatusDescriptionBody
200The run, with how far it got.NotificationTickResult
401The access token is missing or invalid.ErrorResponse
403The caller is not a superuser.ErrorResponse
500The run failed part-way; the next tick resumes it.ErrorResponse
GET/api/users/me/notifications Bearer token

List the caller's notifications

The caller's in-app notifications, newest first, a page at a time: every certification digest pushed to their account, with the sections and links it was sent with. `unread=true` lists only the ones not yet read. `unread_count` is the caller's unread total whatever the filter, as the top bar's bell shows it.

Parameters

NameInTypeRequiredDescription
unreadquerystringnoSet true to list only unread notifications.
pagequeryintegerno1-indexed page number; defaults to 1.

Responses

StatusDescriptionBody
200One page of up to 20 notifications.UserNotificationList
400A query parameter failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
500The notifications could not be read.ErrorResponse
GET/api/users/me/notifications/unread-count Bearer token

Count the caller's unread notifications

How many of the caller's notifications are unread: the number on the top bar's bell, which polls this while a page is open.

Responses

StatusDescriptionBody
200The caller's unread count.UnreadNotificationCount
401The access token is missing or invalid.ErrorResponse
500The notifications could not be read.ErrorResponse
POST/api/users/me/notifications/read-all Bearer token

Mark every notification read

Marks all of the caller's unread notifications read at once. Answers how many changed; calling it with nothing unread changes nothing and answers 200.

Responses

StatusDescriptionBody
200How many notifications were marked read; none are left unread.MarkAllNotificationsReadResult
401The access token is missing or invalid.ErrorResponse
500The notifications could not be updated.ErrorResponse
PATCH/api/users/me/notifications/{notification_id} Bearer token

Mark a notification read or unread

Marks one of the caller's notifications read — keeping the time it was first read — or unread again. Another user's notification is as good as missing.

Parameters

NameInTypeRequiredDescription
notification_idpathstring (uuid)yese.g. 3d1f2a8e-6b0c-4b8e-9d55-0e7f6c1a2b3c

Request body

application/jsonrequiredUpdateUserNotificationRequest
FieldTypeRequiredDescription
readbooleanyes

True marks it read; false marks it unread again.

Responses

StatusDescriptionBody
200The notification, as marked.UserNotification
400The request failed validation.ErrorResponse
401The access token is missing or invalid.ErrorResponse
404The caller has no notification with that id.ErrorResponse
500The notification could not be updated.ErrorResponse