BIO.RE
Creator

Update DM Config

Set DM type / price / active flag in one atomic call. Side effects sync DMPackage rows + flip manual social link visibility on DM ON↔OFF transitions. Public bio cache invalidated.

PATCH /api/v1/creators/:creatorId/dm-config — 🔑 Bearer · Rate limit: 30 req / hour

Atomic DM configuration write. Updates CreatorProfile.{dmType, dmPrice, dmActive}, syncs the active DMPackage row to match, and flips the visibility of manual social bio links on DM ON↔OFF transitions (because manual social links are forbidden while DM is active — see POST /creators/:creatorId/links).

Price range is admin-managed. Min / max are read from dm.min_price (default 1) and dm.max_price (default 500). The 5000 cap in the DTO @Max is a hard safety ceiling — actual enforced bounds come from ConfigService.

Social link side effects (transitional, not idempotent across runs):

  • OFF → ON: every BioLink with isSocial: true AND active: true in the creator's bio page is flipped to active: false (so manual social links don't show alongside OAuth-verified ones while DM accepts paid messages).
  • ON → OFF: every BioLink with isSocial: true AND active: false is flipped back to active: true.

These mirrors run inside the same transaction as the DM update. They do NOT discriminate "links the user themselves disabled" — if a creator manually deactivated a social link before turning DM on, it gets re-activated when DM turns off again.

Request

Path parameters

ParamTypeValidationNotes
creatorIdstring (UUID)ParseUUIDPipeMust match the bearer's CreatorProfile.id (otherwise 403)

Body — DMConfigDto

FieldTypeRequiredValidationNotes
dmTypeenumIsEnum(DmType)One of FREE / SINGLE_PAY / PER_MESSAGE
dmPricenumberconditionalIsNumber({ maxDecimalPlaces: 2 }), Min(0), Max(5000)Required when dmType !== 'FREE' AND dmActive: true. Server enforces dm.min_price ≤ value ≤ dm.max_price (admin-managed). Stored as null when dmType === 'FREE'.
dmActivebooleanIsBoolean()Whether the creator currently accepts DMs
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKSuccessOnlyResponseDto

{
  "success": true
}
FieldTypeNotes
successbooleanAlways true on 200. The service computes deactivatedSocialLinks / reactivatedSocialLinks counts internally for telemetry, but the controller does not surface them in the response — re-fetch the creator profile via GET /creators/profile if you need the post-write social-link state.

Errors

HTTPcode / i18nKeyReason
400creator.dm.price_rangedmActive: true AND dmType !== 'FREE' AND dmPrice outside [dm.min_price, dm.max_price] (or missing). Message includes the current bounds.
400(DTO validation)Invalid dmType enum, dmPrice outside [0, 5000] hard ceiling, missing fields
401(guard)Missing / invalid bearer token
403(verifyCreatorOwnership)creatorId does not belong to the bearer's user
429(throttle)Rate limit exceeded (30 req/hour)

Side effects

  1. Ownership checkverifyCreatorOwnership(creatorId, userId) → 403 on mismatch.
  2. Price gate — when dmType !== 'FREE' AND dmActive: true: read dm.min_price / dm.max_price (admin defaults 1 / 500); reject if dmPrice missing or outside range.
  3. Read current creator.dmActive (for transition detection: wasActive vs willBeActive).
  4. Inside one transaction:
    • creatorProfile.update({ dmType, dmPrice: dmType === 'FREE' ? null : dmPrice, dmActive }).
    • DMPackage sync:
      • When dmType !== 'FREE' AND dmActive: find the active package; same type → update price; different type → deactivate old + create new; none → create new.
      • When FREE or dmActive: false: deactivate every active package (updateMany active=false).
    • Social link transition (only fires on flip, no-op if wasActive === willBeActive):
      • OFF→ON: bioLink.updateMany({ where: bioPageId AND isSocial AND active, data: { active: false } }). Count → deactivatedSocialLinks.
      • ON→OFF: bioLink.updateMany({ where: bioPageId AND isSocial AND NOT active, data: { active: true } }). Count → reactivatedSocialLinks.
  5. Audit log: [dm] Config set for creator {id}: {dmType} active={dmActive} (deactivated=N, reactivated=N).
  6. Cache invalidationinvalidateBioCache(creatorId).

Code samples

curl -X PATCH https://api.bio.re/api/v1/creators/c1a2b3c4-d5e6-7890-abcd-ef1234567890/dm-config \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "dmType": "SINGLE_PAY",
    "dmPrice": 5,
    "dmActive": true
  }'
type SetDMConfigInput = {
  dmType: 'FREE' | 'SINGLE_PAY' | 'PER_MESSAGE';
  dmPrice?: number; // required when dmType !== 'FREE' && dmActive
  dmActive: boolean;
};

async function setDMConfig(accessToken: string, creatorId: string, input: SetDMConfigInput): Promise<void> {
  const res = await fetch(`https://api.bio.re/api/v1/creators/${creatorId}/dm-config`, {
    method: 'PATCH',
    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 ?? 'Update DM config failed'), {
      code: json?.error?.code,
    });
  }
}
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useSetDMConfig(creatorId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (input: SetDMConfigInput) => {
      const res = await fetch(`/api/v1/creators/${creatorId}/dm-config`, {
        method: 'PATCH',
        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 ?? 'Update DM config failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['creators', creatorId, 'dm-config'] });
      // Bio links may have been toggled — refetch the editor view too
      qc.invalidateQueries({ queryKey: ['creators', creatorId, 'bio'] });
      qc.invalidateQueries({ queryKey: ['creators', 'profile'] });
    },
  });
}

Try it

PATCH
/api/v1/creators/{creatorId}/dm-config
AuthorizationBearer <token>

In: header

Path Parameters

creatorId*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

curl -X PATCH "https://loading/api/v1/creators/string/dm-config" \  -H "Content-Type: application/json" \  -d '{    "dmType": "FREE",    "dmActive": true  }'
{
  "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"
  }
}
{
  "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/creator/creator.controller.ts213–224 (setDMConfig)
DTO (request)apps/api-core/src/modules/creator/dto/creator.dto.ts117–126 (DMConfigDto)
DTO (response)apps/api-core/src/common/dto/common-response.dto.tsSuccessOnlyResponseDto
Serviceapps/api-core/src/modules/creator/creator.service.ts489–563 (setDMConfig)
Config keysapps/api-core/src/modules/config/config.service.tsdm.min_price (default 1), dm.max_price (default 500) — admin-managed
Prisma modelspackages/prisma/prisma/schema.prismaCreatorProfile.dm*, DMPackage, BioLink.isSocial, BioLink.active

On this page