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.
GET /api/v1/notifications/preferences โ ๐ Bearer ยท Rate limit: 60 req / minute
Returns the merged preference matrix for the calling user. The server reads two tables in parallel โ NotificationDefault (the admin-managed system defaults for every eventKey) and NotificationPreference (per-user overrides) โ and merges them: each row's per-channel boolean is userPref ?? defaultPref. The customized flag tells the UI whether the user has saved an explicit override for that eventKey.
Default-driven catalog. The set of eventKey rows returned is the union of NotificationDefault.eventKey values (admin-managed). New event types appear here automatically when admin adds them to defaults. The user-prefs table is only used for overrides.
customized: false means "uses defaults". When the user has no NotificationPreference row for that eventKey, all 5 channel booleans come from the default. When they save an override (via PATCH /preferences/:eventKey), customized flips to true and the channels reflect the override. There's no "reset to defaults" endpoint; manually delete the override row server-side or just save the same values as the defaults.
Request
No body, no params.
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | โ | JWT from POST /auth/login |
Response
200 OK โ ArrayApiResponseOf<NotificationPreferenceItemDto>
{
"success": true,
"data": [
{
"eventKey": "new_message_creator",
"email": true,
"push": true,
"inApp": true,
"webPush": true,
"sms": false,
"customized": false
},
{
"eventKey": "payout_processed",
"email": true,
"push": false,
"inApp": true,
"webPush": true,
"sms": true,
"customized": true
}
]
}Item fields
| Field | Type | Notes |
|---|---|---|
eventKey | string | Identifier of the notification event (e.g. new_message_creator, message_replied_fan, payout_processed, email_subscription_confirm) |
email | boolean | Email channel enabled (user override or default) |
push | boolean | Native mobile push channel |
inApp | boolean | In-app feed (the inbox shown by GET /notifications) |
webPush | boolean | Browser web-push channel (subscriptions managed via /notifications/webpush/*) |
sms | boolean | SMS channel |
customized | boolean | true when the user has a NotificationPreference row for this event (override saved); false when the row reflects pure defaults |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | (guard) | Missing / invalid bearer token |
429 | (throttle) | Rate limit exceeded (60 req/min) |
Side effects
- In parallel:
notificationPreference.findMany({ where: { userId }, orderBy: eventKey asc })+notificationDefault.findMany({ orderBy: eventKey asc }). - Iterate
defaults: for each, find the matching pref byeventKeyand merge โuserPref?.channel ?? def.channelfor every channel boolean. customized = !!pref(true only when an override row exists).- Return the merged array. No mutations.
Code samples
curl https://api.bio.re/api/v1/notifications/preferences \
-H "Authorization: Bearer $ACCESS_TOKEN"type NotificationPreferenceItem = {
eventKey: string;
email: boolean;
push: boolean;
inApp: boolean;
webPush: boolean;
sms: boolean;
customized: boolean;
};
async function getNotificationPreferences(accessToken: string): Promise<NotificationPreferenceItem[]> {
const res = await fetch('https://api.bio.re/api/v1/notifications/preferences', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Preferences fetch failed'), {
code: json?.error?.code,
});
}
return json.data;
}import { useQuery } from '@tanstack/react-query';
export const notificationKeys = {
preferences: () => ['notifications', 'preferences'] as const,
};
export function useNotificationPreferences() {
return useQuery({
queryKey: notificationKeys.preferences(),
queryFn: async () => {
const res = await fetch('/api/v1/notifications/preferences');
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Preferences fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as NotificationPreferenceItem[];
},
staleTime: 60_000, // catalog rarely changes within a session
});
}Try it
Authorization
bearer In: header
Response Body
application/json
application/json
curl -X GET "https://loading/api/v1/notifications/preferences"{
"success": true,
"data": [
{
"eventKey": "string",
"email": true,
"push": true,
"inApp": true,
"webPush": true,
"sms": true,
"customized": 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"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller (inline impl) | apps/api-core/src/modules/notification/user-notification.controller.ts | 121โ148 (getPreferences โ direct Prisma, parallel reads + merge) |
| DTO (response item) | apps/api-core/src/modules/notification/dto/notification-client-response.dto.ts | 59โ80 (NotificationPreferenceItemDto) |
| Prisma models | packages/prisma/prisma/schema.prisma | NotificationDefault (admin catalog), NotificationPreference (user overrides, compound unique userId_eventKey) |