Update Notification Preference
Save the user's per-channel override for a single event. Upserts a NotificationPreference row. eventKey must exist in the admin-managed defaults catalog.
PATCH /api/v1/notifications/preferences/:eventKey โ ๐ Bearer ยท Rate limit: 30 req / minute
Upserts the user's NotificationPreference row for a single eventKey. Only the channel booleans you supply are touched (sparse update); on first write, missing channels default to true. The server validates that eventKey exists in the admin-managed NotificationDefault catalog (else 404 event_not_found).
true is the create default. When this is the user's first save for an eventKey (no override row yet), every channel you don't explicitly send defaults to true. To opt-out of every channel except email, you must explicitly send push: false, inApp: false, webPush: false, sms: false โ sending only email: true would leave the others enabled.
Subsequent updates are sparse. Once an override row exists, only the channels you send are written. Omitted channels keep their stored values. The asymmetry between "first save = true defaults" and "subsequent save = preserve" is a Prisma upsert consequence โ the update branch only touches supplied keys, the create branch fills the rest.
Request
Path parameters
| Param | Type | Validation | Notes |
|---|---|---|---|
eventKey | string | (no UUID pipe โ raw string) | Must match a row in NotificationDefault (server checks). E.g. new_message_creator, payout_processed. |
Body
A loose object โ server uses class-validator-free pass-through, but the schema documents 5 boolean channel fields.
| Field | Type | Required | Notes |
|---|---|---|---|
email | boolean | optional | Email channel toggle |
push | boolean | optional | Native push toggle |
inApp | boolean | optional | In-app feed toggle |
webPush | boolean | optional | Web push toggle |
sms | boolean | optional | SMS toggle |
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | โ | JWT from POST /auth/login |
Response
200 OK โ SuccessOnlyResponseDto
{
"success": true
}| Field | Type | Notes |
|---|---|---|
success | boolean | Always true on 200. Re-fetch GET /notifications/preferences to read the merged post-write state (the row will now have customized: true). |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | (guard) | Missing / invalid bearer token |
404 | error.notification.event_not_found | eventKey doesn't match any NotificationDefault row (catalog gate) |
429 | (throttle) | Rate limit exceeded (30 req/min โ tighter than other endpoints) |
Side effects
- Catalog gate โ
notificationDefault.findUnique({ where: { eventKey } }). Missing โ throwevent_not_found. - Upsert โ
notificationPreference.upsert({ where: { userId_eventKey }, update: <body>, create: <body with true defaults for unset channels> }). - Return
{ success: true }. No notification dispatch effects โ preference takes effect on the NEXT event fired.
Code samples
# Disable email for new-message-creator events
curl -X PATCH https://api.bio.re/api/v1/notifications/preferences/new_message_creator \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"email": false}'
# Opt-out of every channel except in-app (first save โ must be explicit)
curl -X PATCH https://api.bio.re/api/v1/notifications/preferences/payout_processed \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"email": false, "push": false, "webPush": false, "sms": false, "inApp": true}'type ChannelToggles = {
email?: boolean;
push?: boolean;
inApp?: boolean;
webPush?: boolean;
sms?: boolean;
};
async function updateNotificationPreference(
accessToken: string,
eventKey: string,
channels: ChannelToggles,
): Promise<void> {
const res = await fetch(`https://api.bio.re/api/v1/notifications/preferences/${eventKey}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(channels),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Preference update failed'), {
code: json?.error?.code,
});
}
}import { useMutation, useQueryClient } from '@tanstack/react-query';
export function useUpdateNotificationPreference() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (vars: { eventKey: string; channels: ChannelToggles }) => {
const res = await fetch(`/api/v1/notifications/preferences/${vars.eventKey}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(vars.channels),
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Preference update failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['notifications', 'preferences'] });
},
});
}Try it
Authorization
bearer In: header
Path Parameters
Response Body
application/json
application/json
application/json
curl -X PATCH "https://loading/api/v1/notifications/preferences/string"{
"success": true
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller (inline impl) | apps/api-core/src/modules/notification/user-notification.controller.ts | 151โ181 (updatePreference โ catalog gate + Prisma upsert) |
| DTO (response) | apps/api-core/src/common/dto/common-response.dto.ts | SuccessOnlyResponseDto |
| Prisma models | packages/prisma/prisma/schema.prisma | NotificationDefault (catalog gate), NotificationPreference (compound unique userId_eventKey) |
Get Notification Preferences
Per-event channel matrix (email / push / in-app / webPush / SMS) merged from system defaults plus user overrides. The customized flag tells the UI whether each row is user-modified.
Clear Read Notifications
Bulk-delete every read notification for the calling user. Returns the count actually deleted. Unread rows are untouched.