- Platform defaults. One
NotificationTypeConfigrow per type, edited by Perform admins. - Studio overrides. One
StudioNotificationConfigrow per studio and type, edited by the studio owner or head coach. An override replaces the default for that studio. - Trainee preferences. A list of muted groups on
Client.mutedNotificationGroups, edited by the trainee in the app.
backend/apps/core-api/src/modules/notification-configs/ (routes, controller, service, repository, schema) and backend/apps/core-api/src/lib/notification-messages.ts for the message helpers and the normalizer.
Mounting and auth
The web lane is
serviceAuth, webUserContext, webLimiter. Write routes add requireRole(StudioRole.OWNER, StudioRole.HEAD_COACH), which throws FORBIDDEN (403) with insufficient role for anyone else. See API overview.
authenticateTrainee (middleware/trainee-auth.ts) expects Authorization: Bearer <trainee token> and verifies it with BETTER_AUTH_SECRET. It rejects with UNAUTHORIZED (401) when the token is missing or invalid, or when the studio no longer exists. A preview session can only make GET requests. Any other method gets FORBIDDEN (403) with details.reason PREVIEW_READ_ONLY. These two trainee routers do not apply requireTraineeAppAccess, so a trainee whose app access is paused can still call them.
Notification types
notificationConfigType in the schema lists 15 values. Twelve are active (ACTIVE_NOTIFICATION_TYPES). The other three are retired: they stay in the enum and have defaults, but every list endpoint filters them out.
Active types
These columns come from
NOTIFICATION_CATALOGUE and NOTIFICATION_CONFIG_DEFAULTS. Variables are written in templates as {{name}}. The 6 hour throttle is PLAN_UPDATE_THROTTLE_HOURS. The default title and body texts are in Hebrew and live in NOTIFICATION_CONFIG_DEFAULTS.
The categories, in order, are workout, forms, plans, subscription, messages (NOTIFICATION_CATEGORIES).
Retired types
FORM_ASSIGNED, CHECK_IN_DUE and CHECK_IN_TOMORROW are not in ACTIVE_NOTIFICATION_TYPES.
Kinds and messages
Every setting is exposed as a list of messages (notificationMessage in the schema).
- single. One message with the fixed id
default. It is built from the row’stitleTemplateandbodyTemplate. The storedmessagescolumn is not used. - formSent. Up to 3 messages, each for a form type.
pickFormSentMessagepicks the enabled message whoseformTypematches the form, and falls back to the enabledALLmessage. The defaults are oneCHECK_INmessage and oneALLmessage. - formReminders. Up to 10 reminders, each with a day offset from the form’s day and a time. The default is one reminder the day before a check-in at
19:00that opens the home screen.
messagesOf reads the stored messages JSON. When it is missing, empty or does not parse, the type’s default messages are used.
Preference groups
A trainee mutes groups, not types.notificationPreferenceGroup has six values, in this order:
COACH_MESSAGES is locked (LOCKED_NOTIFICATION_GROUPS). A trainee cannot switch it off, and isNotificationMuted ignores it even if it somehow lands in the muted list.
notificationGroupFor(type, formType) decides the group. For FORM_SENT and FORM_REMINDERS the form type decides: CHECK_IN maps to CHECK_INS, anything else, including no form type, maps to FORMS. Every other type uses the fixed NOTIFICATION_TYPE_GROUPS map.
Platform defaults (admin)
Both admin endpoints first callensureDefaults, which upserts a default row for each of the 12 active types. Existing rows are left untouched.
GET /v1/admin/notification-configs
Lists the platform default row for every active type, sorted by type.
Auth: serviceAuth only.
Response: 200 with an array of NotificationTypeConfig rows.
PATCH /v1/admin/notification-configs/:type
Updates one platform default.
Auth: serviceAuth only.
string
required
Any of the 15
notificationConfigType values.boolean
Master switch for the type.
string
At least 1 character.
string
May be empty.
boolean
Whether the push plays a sound.
string
HH:MM, 24 hour.FORM_SENT and FORM_REMINDERS, the service also rewrites the first stored message so the two representations agree: its title takes titleTemplate, its subtitle takes bodyTemplate, and its time takes sendTime when that message already has a time.
Response: 200 with the updated row, in the same shape as the list.
Errors:
Studio overrides, flat shape (web)
These two endpoints expose one flat row per type. They predate the multi-message settings and stay for callers that only need title, body and time.GET /v1/web/notification-configs
Lists the effective config for each active type in the caller’s studio.
Auth: web lane, any role.
For each active type the studio override is returned when one exists, otherwise the platform default. customized says which one it is.
Response: 200.
PATCH /v1/web/notification-configs/:type
Creates or updates the studio’s override for one type.
Auth: web lane, roles OWNER or HEAD_COACH.
string
required
A
notificationConfigType value.boolean
Switch for this studio.
string
At least 1 character.
string
May be empty.
boolean
Whether the push plays a sound.
string
HH:MM, 24 hour.StudioNotificationConfig row. A studio’s first PATCH therefore copies every other field from the platform default into the override. Later changes to the platform default no longer reach that studio for that type.
For the multi-message kinds, the first message is rewritten the same way as in the admin PATCH.
Response: 200.
messages is included only for FORM_SENT and FORM_REMINDERS.
Errors: VALIDATION (422), FORBIDDEN (403) insufficient role, UNAUTHORIZED (401).
The route accepts retired types too. For one of those the service writes an override built from the code defaults, but no list endpoint ever returns it.
Studio settings, structured shape (web)
This is the shape the notification settings screen uses. Each setting carries its catalogue metadata, its messages and the platform defaults to reset to.GET /v1/web/studio/notification-settings
Returns every active type with its messages, plus the category order.
Auth: web lane, any role.
Response: 200. Settings come in ACTIVE_NOTIFICATION_TYPES order.
enabled, sound and messages are the studio’s effective values. defaults always reflects the platform row.
PUT /v1/web/studio/notification-settings/:type
Replaces the studio’s whole setting for one type.
Auth: web lane, roles OWNER or HEAD_COACH.
string
required
Must be an active type.
boolean
required
Switch for the whole type in this studio.
boolean
required
Whether the push plays a sound.
object[]
required
1 to 20 messages in the
notificationMessage shape described above. The per-type limit is checked after parsing.normalizeSetting cleans and checks the messages:
titlehas line breaks collapsed to a space and is trimmed. It must not be empty.subtitleis trimmed.- Message ids must be unique.
- single: the message id becomes
defaultand the message is always enabled. Extra fields are dropped. - formSent:
formTypedefaults toALL. Onlyid,enabled,title,subtitleandformTypeare kept. - formReminders:
formTypedefaults toCHECK_IN.offsetDaysis required and must be between -3 and 3. A negative offset (before the form’s day) is only allowed forCHECK_IN.timeis required and must beHH:MM.onlyIfNotFilledis forced tofalsefor a negative offset and defaults totrueotherwise.opensdefaults toform. Reminders are sorted byoffsetDays, thentime.
titleTemplate, bodyTemplate and sendTime in step with the first message. messages is stored only for the multi-message kinds.
Response: 200 with the normalized setting.
Issue codes from the normalizer:
There is no endpoint to delete an override. To go back to the platform text, send the values from
defaults. The setting stays customized: true.
Trainee endpoints
GET /v1/trainee/notification-configs
Returns the effective config per active type for the trainee’s studio, with the trainee’s mutes applied. The app uses it for notifications it schedules on the device.
Auth: trainee bearer token.
enabled is the studio’s switch and not muted by the trainee. The mute check here calls isNotificationMuted(type, muted) with no form type, so both FORM_SENT and FORM_REMINDERS follow the FORMS group in this list.
Response: 200.
messages and customized are not included.
Errors: UNAUTHORIZED (401), NOT_FOUND (404) trainee not found.
GET /v1/trainee/notification-preferences
Lists the groups the trainee can control.
Auth: trainee bearer token.
A group is listed only when at least one enabled type in the studio feeds it:
- A single-kind type adds its fixed group.
- A multi-message type adds a group per enabled message. A message with
formTypeALL, or none, adds bothFORMSandCHECK_INS.CHECK_INaddsCHECK_INS.INTAKEaddsFORMS.
200.
UNAUTHORIZED (401), NOT_FOUND (404) trainee not found.
PATCH /v1/trainee/notification-preferences
Switches one group on or off for the trainee.
Auth: trainee bearer token. Refused for preview sessions.
string
required
One of
CHECK_INS, FORMS, PLANS, WORKOUT_REMINDERS, COACH_MESSAGES, SUBSCRIPTION.boolean
required
false adds the group to Client.mutedNotificationGroups. true removes it.200 with the same shape as the GET, after the change.
Errors:
Related pages
- Forms for the form types that
FORM_SENTandFORM_REMINDERSmessages target. - Coach assistant sends
TRAINER_MESSAGEpushes and respects the studio switch.