BIO.RE
Notifications

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.

HeaderRequiredNotes
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

FieldTypeNotes
eventKeystringIdentifier of the notification event (e.g. new_message_creator, message_replied_fan, payout_processed, email_subscription_confirm)
emailbooleanEmail channel enabled (user override or default)
pushbooleanNative mobile push channel
inAppbooleanIn-app feed (the inbox shown by GET /notifications)
webPushbooleanBrowser web-push channel (subscriptions managed via /notifications/webpush/*)
smsbooleanSMS channel
customizedbooleantrue when the user has a NotificationPreference row for this event (override saved); false when the row reflects pure defaults

Errors

HTTPcode / i18nKeyReason
401(guard)Missing / invalid bearer token
429(throttle)Rate limit exceeded (60 req/min)

Side effects

  1. In parallel: notificationPreference.findMany({ where: { userId }, orderBy: eventKey asc }) + notificationDefault.findMany({ orderBy: eventKey asc }).
  2. Iterate defaults: for each, find the matching pref by eventKey and merge โ€” userPref?.channel ?? def.channel for every channel boolean.
  3. customized = !!pref (true only when an override row exists).
  4. 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

GET
/api/v1/notifications/preferences
AuthorizationBearer <token>

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

SourcePathLines
Controller (inline impl)apps/api-core/src/modules/notification/user-notification.controller.ts121โ€“148 (getPreferences โ€” direct Prisma, parallel reads + merge)
DTO (response item)apps/api-core/src/modules/notification/dto/notification-client-response.dto.ts59โ€“80 (NotificationPreferenceItemDto)
Prisma modelspackages/prisma/prisma/schema.prismaNotificationDefault (admin catalog), NotificationPreference (user overrides, compound unique userId_eventKey)

On this page