BIO.RE
User

Set User Intent

One-time, post-registration choice — FAN or CREATOR. Drives the post-signup landing destination and is rejected on second call.

PATCH /api/v1/users/intent — 🔑 Bearer

Sets User.intent to FAN or CREATOR. One-time only — after the value is set, all subsequent calls return 400 intent_already_set. The atomic updateMany({ intent: null }) guard prevents a race between two concurrent requests.

After this succeeds, route the user accordingly: FAN → discovery feed, CREATOR → onboarding wizard. The flag never changes after the first set; account-type changes are out of scope for this endpoint.

Request

Body — SetIntentDto

FieldTypeRequiredValidationNotes
intentenum (UserIntent)IsEnum(UserIntent)One of: FAN / CREATOR
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKSuccessOnlyResponseDto

{
  "success": true
}
FieldTypeNotes
successbooleanAlways true on 200

Errors

HTTPcode / i18nKeyReason
400(DTO validation)intent not in the enum
400error.user.intent_already_setUser.intent is non-null — second call after the value was already set
401(guard)Missing / invalid bearer token
404error.user.not_foundToken decoded but user row missing

Side effects

  1. Atomic check-and-set: prisma.user.updateMany({ where: { id, intent: null }, data: { intent } }). If the row already had a non-null intent, count = 0 and no write happens.
  2. If count = 0, look up the user to distinguish "intent already set" from "user not found", then throw the matching error.
  3. On success: increment userIntentSetTotal Prometheus counter (labelled by chosen intent).
  4. Audit log: [intent] User {userId} set intent: {intent}.

Code samples

curl -X PATCH https://api.bio.re/api/v1/users/intent \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"intent": "CREATOR"}'
type UserIntent = 'FAN' | 'CREATOR';

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

export function useSetIntent() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (intent: UserIntent) => {
      const res = await fetch('/api/v1/users/intent', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ intent }),
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Set intent failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['users', 'profile'] });
      qc.invalidateQueries({ queryKey: ['auth', 'me'] });
    },
  });
}

Try it

PATCH
/api/v1/users/intent
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

curl -X PATCH "https://loading/api/v1/users/intent" \  -H "Content-Type: application/json" \  -d '{    "intent": "FAN"  }'
{
  "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
Controllerapps/api-core/src/modules/user/user.controller.ts92–99 (setIntent)
DTO (request)apps/api-core/src/modules/user/dto/index.ts45–49 (SetIntentDto)
DTO (response)apps/api-core/src/common/dto/common-response.dto.tsSuccessOnlyResponseDto
Serviceapps/api-core/src/modules/user/user.service.ts223–241 (setIntent)
Prisma enumpackages/prisma/prisma/schema.prismaenum UserIntent (FAN / CREATOR), User.intent

On this page