BIO.RE
Creator

Update Bio Link

Sparse update of a bio link. URL revalidation re-runs embed auto-detection. Title HTML-stripped + moderation-checked. Schedule end must follow start. Cache invalidation is best-effort.

PATCH /api/v1/creators/links/:linkId — 🔑 Bearer · Rate limit: 30 req / hour

Sparse update of a single BioLink. Ownership is checked via the link's bio page → creator → user chain. URL changes re-validate (http(s):// only, javascript: blocked) and re-trigger embed auto-detection. Title changes run through trust-safety moderation. Schedule bounds are validated when both are present.

Embed re-detection on URL change: when you update url without explicitly setting embedType / embedMeta, the server re-runs detectEmbedType() and writes the freshly detected values. To clear an auto-detected embed without changing the URL, set embedType: 'CUSTOM' with embedMeta: {} explicitly.

Cache invalidation is best-effort. After the DB write, the server tries to look up the owning creatorId to purge the public bio page cache. If that lookup fails (e.g. transient DB issue), the cache will expire naturally on its TTL — the update itself is not rolled back.

Request

Path parameters

ParamTypeValidationNotes
linkIdstring (UUID)ParseUUIDPipeMust belong to a bio page owned by the bearer's user

Body — UpdateBioLinkDto

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

FieldTypeValidationNotes
titlestringMaxLength(100)HTML-stripped + moderation-checked
urlstringIsUrl()Re-validated; auto-detected embed gets re-applied unless embedType/embedMeta also supplied
iconstringMaxLength(50)
sortOrdernumberIsInt, Min(0), Max(1000)
activeboolean
embedTypeenumIsIn(['YOUTUBE','SPOTIFY','TIKTOK','SOUNDCLOUD','TWITCH','APPLE_MUSIC','CUSTOM'])Wins over auto-detection
embedMetaobjectIsObjectWins over auto-detected meta
scheduledStartstring (ISO 8601)IsDateStringPass null semantically by sending undefined (omit) — no explicit clear-to-null in DTO
scheduledEndstring (ISO 8601)IsDateStringMust be after scheduledStart (when both present)
isSocialboolean
platformstringMaxLength(30)Lowercased server-side; null clears
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 for the post-write state.

Errors

HTTPcode / i18nKeyReason
400creator.links.schedule_invalidscheduledEnd <= scheduledStart
400creator.links.content_flaggedModeration flagged the new title
400creator.links.invalid_urlURL not http(s):// OR contains javascript:
400(DTO validation)Field length / type / enum failures
401(guard)Missing / invalid bearer token
403creator.links.not_ownerLink's bio page belongs to a different user
404creator.links.not_foundBioLink row missing
429(throttle)Rate limit exceeded (30 req/hour)

Side effects

  1. Ownership checkverifyLinkOwnership(linkId, userId) traverses BioLink → BioPage → CreatorProfile.userId; mismatch → 403.
  2. Schedule bounds validation when both supplied.
  3. Build sparse data object:
    • titlestripHtmlTags().
    • urlvalidateUrl(). If supplied AND embedType/embedMeta not supplied, re-run detectEmbedType() and inject embedType + embedMeta.
    • platform → lowercased (or null to clear).
    • scheduledStart / scheduledEnd → ISO string parsed to Date (or null).
  4. Moderation gate (when title supplied) — flag → reject.
  5. prisma.bioLink.update({ where: { id: linkId }, data }).
  6. Best-effort cache invalidation — try to look up the link's creatorId; if found, invalidateBioCache(creatorId). Failures are swallowed (cache will expire naturally).

Code samples

curl -X PATCH https://api.bio.re/api/v1/creators/links/l1a2b3c4-d5e6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Updated title",
    "active": false
  }'
type UpdateBioLinkInput = {
  title?: string;
  url?: string;
  icon?: string;
  sortOrder?: number;
  active?: boolean;
  embedType?: EmbedType;
  embedMeta?: Record<string, unknown>;
  scheduledStart?: string;
  scheduledEnd?: string;
  isSocial?: boolean;
  platform?: string;
};

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

export function useUpdateBioLink(creatorId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (vars: { linkId: string; input: UpdateBioLinkInput }) => {
      const res = await fetch(`/api/v1/creators/links/${vars.linkId}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(vars.input),
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Update link failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['creators', creatorId, 'bio'] });
    },
  });
}

Try it

PATCH
/api/v1/creators/links/{linkId}
AuthorizationBearer <token>

In: header

Path Parameters

linkId*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/links/string" \  -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"
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/creator/creator.controller.ts163–174 (updateLink), 107–114 (verifyLinkOwnership)
DTO (request)apps/api-core/src/modules/creator/dto/creator.dto.ts82–115 (UpdateBioLinkDto)
DTO (response)apps/api-core/src/common/dto/common-response.dto.tsSuccessOnlyResponseDto
Serviceapps/api-core/src/modules/creator/creator.service.ts393–442 (updateLink), 42–50 (validateUrl), 1065–1081 (detectEmbedType)
Moderationapps/api-core/src/modules/trust-safety/trust-safety.service.tscheckContent()
Prisma modelspackages/prisma/prisma/schema.prismaBioLink, BioPage (relation chain for ownership)

On this page