BIO.RE
Creator

Update Bio Page

Sparse update of the BioPage. Bio strips HTML tags, customCss strips XSS vectors, bio body runs through trust-safety content moderation. Cache invalidated on success.

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

Sparse update — only fields present in the body are written. bio is HTML-stripped, customCss is XSS-sanitized (expression(...), javascript:, non-https url(...) removed), and bio content runs through the trust-safety moderation gate before persistence. On success, the cached public bio page for this creator is invalidated.

XSS sanitization is destructive. Submitted <script> / <img onerror=...> etc. will be silently stripped — the response shape ({ success: true }) doesn't tell you what was removed. Validate client-side too if you want to surface "we removed N tags" UI.

Content moderation gate (no NSFW). When bio is supplied, it is run through trustSafetyService.checkContent(). If the moderation flags it, the entire request fails with 400 creator.bio.content_flagged and no fields are written (the moderation check runs after sparse-data assembly but before the DB write).

Request

Path parameters

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

Body — UpdateBioPageDto

All fields optional. Send only what you want to change.

FieldTypeValidationNotes
biostringMaxLength(5000)HTML-stripped server-side AND moderation-checked
templateIdstring (UUID) | nullIsUUID('4') (when not null)Set to null to clear
themeOverrideobjectFree-form JSON; not sanitized — keep design tokens, don't smuggle scripts
customCssstringMaxLength(10000)XSS-sanitized (see above)
embedEnabledbooleanGates GET /bio/embed/:username
publishedbooleanWhen false, public render returns 404
emailCollectionEnabledbooleanGates POST /creators/:bioPageId/subscribe
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

200 OKSuccessOnlyResponseDto

{
  "success": true
}
FieldTypeNotes
successbooleanAlways true on 200. Re-fetch via GET /creators/:creatorId/bio to see the post-write state.

Errors

HTTPcode / i18nKeyReason
400(DTO validation)bio > 5000, customCss > 10000, templateId not a UUID
400creator.bio.content_flaggedModeration flagged the submitted bio
401(guard)Missing / invalid bearer token
403(verifyCreatorOwnership)creatorId does not belong to the bearer's user
404creator.bio.not_foundBioPage row missing for this creator
429(throttle)Rate limit exceeded (30 req/hour)

Side effects

  1. Ownership checkverifyCreatorOwnership(creatorId, userId) → 403 on mismatch.
  2. Build sparse data object from defined keys only.
  3. Sanitize:
    • biostripHtmlTags() (regex /<[^>]*>/g).
    • customCsssanitizeCss() (blocks expression(...), javascript:, non-https/data url(...) rewritten to url(about:blank, plus tag strip).
  4. Moderation gate (only when bio is supplied): trustSafetyService.checkContent(input.bio). If flagged, throw content_flaggedno DB write occurs.
  5. prisma.bioPage.update({ where: { creatorId }, data }).
  6. Audit log: [bio] Updated for creator {creatorId}.
  7. Cache invalidationinvalidateBioCache(creatorId) purges the public-render cache for this creator's username so the next GET /bio/:username re-builds from DB.

Code samples

curl -X PATCH https://api.bio.re/api/v1/creators/c1a2b3c4-d5e6-7890-abcd-ef1234567890/bio \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "bio": "Designer & creator",
    "published": true,
    "emailCollectionEnabled": true
  }'
type UpdateBioPageInput = {
  bio?: string;
  templateId?: string | null;
  themeOverride?: Record<string, unknown>;
  customCss?: string;
  embedEnabled?: boolean;
  published?: boolean;
  emailCollectionEnabled?: boolean;
};

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

export function useUpdateBioPage(creatorId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (input: UpdateBioPageInput) => {
      const res = await fetch(`/api/v1/creators/${creatorId}/bio`, {
        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 ?? 'Bio page update failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['creators', creatorId, 'bio'] });
      qc.invalidateQueries({ queryKey: ['creators', 'profile'] });
    },
  });
}

Try it

PATCH
/api/v1/creators/{creatorId}/bio
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

application/json

curl -X PATCH "https://loading/api/v1/creators/string/bio" \  -H "Content-Type: application/json" \  -d '{}'
{
  "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"
  }
}
{
  "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.ts136–148 (updateBioPage)
DTO (request)apps/api-core/src/modules/creator/dto/creator.dto.ts21–45 (UpdateBioPageDto)
DTO (response)apps/api-core/src/common/dto/common-response.dto.tsSuccessOnlyResponseDto
Serviceapps/api-core/src/modules/creator/creator.service.ts306–327 (updateBioPage), 30–32 (stripHtmlTags), 34–40 (sanitizeCss)
Moderation gateapps/api-core/src/modules/trust-safety/trust-safety.service.tscheckContent()
Cacheapps/api-core/src/modules/creator/bio-analytics.service.tsinvalidateCache() (in-memory bio page cache)
Prisma modelpackages/prisma/prisma/schema.prismaBioPage (bio / templateId / themeOverride / customCss / embedEnabled / published / emailCollectionEnabled)

On this page