BIO.RE
Notifications

Mark Notification Read

Flip a single notification's read flag to true and stamp readAt. Ownership-checked. Idempotent — re-marking only refreshes the timestamp.

PATCH /api/v1/notifications/:id/read — 🔑 Bearer · Rate limit: 60 req / minute

Marks a single notification as read. Ownership is enforced via findFirst({ id, userId }); missing or cross-user lookups return 404 not_found (same code — no enumeration leak between "doesn't exist" and "not yours").

Idempotent re-read. Re-marking an already-read notification refreshes the readAt timestamp to the new "read again" moment. The read flag stays true. If you only want to mark unread→read once, gate client-side off the existing read: true value before issuing the call.

Request

Path parameters

ParamTypeValidationNotes
idstring (UUID)ParseUUIDPipeThe notification to mark read

No body.

HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKSuccessOnlyResponseDto

{
  "success": true
}
FieldTypeNotes
successbooleanAlways true on 200. Re-fetch the list / unread count to see the post-write state, or update local cache optimistically.

Errors

HTTPcode / i18nKeyReason
400(validation)id not a valid UUID
401(guard)Missing / invalid bearer token
404error.notification.not_foundNotification not found OR belongs to a different user (same code on purpose)
429(throttle)Rate limit exceeded (60 req/min)

Side effects

  1. Ownership-aware lookupprisma.notification.findFirst({ where: { id, userId } }). Returns null for missing OR cross-user → throw not_found.
  2. prisma.notification.update({ where: { id }, data: { read: true, readAt: new Date() } }).
  3. Return { success: true }.

Code samples

curl -X PATCH https://api.bio.re/api/v1/notifications/n1a2b3c4-d5e6-7890-abcd-ef1234567890/read \
  -H "Authorization: Bearer $ACCESS_TOKEN"
async function markNotificationRead(accessToken: string, notificationId: string): Promise<void> {
  const res = await fetch(`https://api.bio.re/api/v1/notifications/${notificationId}/read`, {
    method: 'PATCH',
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Mark read failed'), {
      code: json?.error?.code,
    });
  }
}
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useMarkNotificationRead() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (notificationId: string) => {
      const res = await fetch(`/api/v1/notifications/${notificationId}/read`, {
        method: 'PATCH',
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Mark read failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
    },
    // Optimistic UI: flip local state, invalidate on success
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['notifications'] });
    },
  });
}

Try it

PATCH
/api/v1/notifications/{id}/read
AuthorizationBearer <token>

In: header

Path Parameters

id*string

Response Body

application/json

application/json

application/json

curl -X PATCH "https://loading/api/v1/notifications/string/read"
{
  "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

SourcePathLines
Controller (inline impl)apps/api-core/src/modules/notification/user-notification.controller.ts90–104 (markRead — direct Prisma, ownership-aware findFirst)
DTO (response)apps/api-core/src/common/dto/common-response.dto.tsSuccessOnlyResponseDto
Prisma modelpackages/prisma/prisma/schema.prismaNotification.read, Notification.readAt

On this page