BIO.RE
User

Record Consent

Append-only log of user consent decisions (TOS, privacy, cookies, etc). Captures IP and user-agent for compliance audit.

POST /api/v1/users/consent — 🔑 Bearer

Records a single immutable consent decision in the ConsentRecord log. Captures the user's choice (accepted: true | false), the document type and version, plus req.ip and the User-Agent header for compliance audit. Append-only — there is no update/delete; revising consent for the same document is a new row.

Compliance-grade. The IP and user-agent are stored to prove who, when, and from where a consent was given. The frontend sends only the document type / version / accepted flag; the request metadata (req.ip, req.headers['user-agent']) is captured server-side.

Request

Body — RecordConsentDto

FieldTypeRequiredValidationNotes
documentTypestringIsString()Free-form key — typical values: tos, privacy, cookies, marketing-emails
documentVersionstringIsString()Semver-ish version of the document being agreed to (e.g. 2.1, 2026-04-29)
acceptedbooleanIsBoolean()true for an opt-in, false for an explicit decline (declines are also logged for audit)
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

201 CreatedApiResponseOf<ConsentRecordedDto>

{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
FieldTypeNotes
idstring (UUID)ConsentRecord.id — useful only for tracing in support tickets

Errors

HTTPcode / i18nKeyReason
400(DTO validation)Missing / wrong-typed fields
401(guard)Missing / invalid bearer token

Side effects

  1. Generate id = randomUUID().
  2. Insert ConsentRecord { id, userId, documentType, documentVersion, accepted, ipAddress: req.ip, userAgent: req.headers['user-agent'] }. No deduplication — calling twice with the same document/version creates two rows.
  3. Audit log: [consent] {documentType} v{documentVersion} accepted/declined by user {userId}.
  4. No further side effects — does not flip any flag on User. Document gating is read separately via the consent history (or hasConsent() server-side helper).

Code samples

curl -X POST https://api.bio.re/api/v1/users/consent \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "documentType": "tos",
    "documentVersion": "2.1",
    "accepted": true
  }'
type RecordConsentInput = {
  documentType: string;
  documentVersion: string;
  accepted: boolean;
};

async function recordConsent(accessToken: string, input: RecordConsentInput): Promise<string> {
  const res = await fetch('https://api.bio.re/api/v1/users/consent', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(input),
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Record consent failed'), {
      code: json?.error?.code,
    });
  }
  return json.data.id as string;
}
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useRecordConsent() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (input: RecordConsentInput) => {
      const res = await fetch('/api/v1/users/consent', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(input),
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Record consent failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data.id as string;
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['users', 'consent'] });
    },
  });
}

Try it

POST
/api/v1/users/consent
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://loading/api/v1/users/consent" \  -H "Content-Type: application/json" \  -d '{    "documentType": "string",    "documentVersion": "string",    "accepted": true  }'
{
  "success": true,
  "data": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
{
  "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
Controllerapps/api-core/src/modules/user/user.controller.ts248–261 (recordConsent)
DTO (request)apps/api-core/src/modules/user/dto/index.ts20–24 (RecordConsentDto)
DTO (response)apps/api-core/src/modules/user/dto/user-client-response.dto.ts182–185 (ConsentRecordedDto)
Serviceapps/api-core/src/modules/user/user.service.ts719–734 (recordConsent)
Prisma modelpackages/prisma/prisma/schema.prismaConsentRecord

On this page