Get Bio Page (Editor)
Read the creator's own BioPage row plus its links (ordered) and template. Ownership-checked. Use this for the editor — public fan-side rendering uses GET /bio/:username instead.
GET /api/v1/creators/:creatorId/bio — 🔑 Bearer
Returns the creator-owned view of the BioPage for a creatorId, including all BioLink rows (ordered by sortOrder ASC) and the joined BioTemplate. Ownership is enforced — the bearer's user id must match CreatorProfile.userId; otherwise 403.
Editor path, not render path. This endpoint returns the raw editable record (including customCss, themeOverride, published flag) — render the public view via GET /bio/:username (cached, scope-filtered). Use this only inside the creator dashboard.
Request
Path parameters
| Param | Type | Validation | Notes |
|---|---|---|---|
creatorId | string (UUID) | ParseUUIDPipe | Must match the bearer's CreatorProfile.id (otherwise 403) |
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<BioPageResponseDto>
{
"success": true,
"data": {
"id": "b1a2b3c4-d5e6-7890-abcd-ef1234567890",
"creatorId": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
"templateId": "t1a2b3c4-d5e6-7890-abcd-ef1234567890",
"bio": "Designer & creator",
"themeOverride": null,
"customCss": null,
"embedEnabled": false,
"published": true,
"emailCollectionEnabled": false,
"links": [
{
"id": "l1a2b3c4-d5e6-7890-abcd-ef1234567890",
"title": "My Site",
"url": "https://example.com",
"icon": "globe",
"sortOrder": 0,
"active": true,
"isSocial": false,
"platform": null,
"embedType": null,
"embedMeta": null,
"scheduledStart": null,
"scheduledEnd": null
}
],
"template": { /* BioTemplate row when templateId is set */ }
}
}Top-level fields
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | BioPage.id |
creatorId | string (UUID) | Owning CreatorProfile.id |
templateId | string (UUID) | null | Selected template (null when none) |
bio | string | null | Body — stored sanitized (HTML tags stripped at write time) |
themeOverride | object | null | Free-form JSON theme override |
customCss | string | null | Custom CSS — stored sanitized (expression(...), javascript:, non-https url(...) stripped at write time) |
embedEnabled | boolean | When true, third-party sites may embed via iframe (also gates GET /bio/embed/:username) |
published | boolean | When false, public render endpoint returns 404 |
emailCollectionEnabled | boolean | When true, POST /creators/:bioPageId/subscribe accepts emails |
links | array | All BioLink rows (ordered by sortOrder ASC) — includes inactive / scheduled-future links (the public render filters them) |
template | object | null | Joined BioTemplate when templateId is set |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | (validation) | creatorId not a valid UUID |
401 | (guard) | Missing / invalid bearer token |
403 | (verifyCreatorOwnership) | creatorId does not belong to the bearer's user |
404 | creator.bio.not_found | BioPage row missing for this creator |
Side effects
- Ownership check —
verifyCreatorOwnership(creatorId, userId)readsCreatorProfile.userIdand throws 403 on mismatch. prisma.bioPage.findUnique({ where: { creatorId }, include: { links: { orderBy: { sortOrder: 'asc' } }, template: true } }).- Throw
not_foundif missing. - Return the row. No mutations.
Code samples
curl https://api.bio.re/api/v1/creators/c1a2b3c4-d5e6-7890-abcd-ef1234567890/bio \
-H "Authorization: Bearer $ACCESS_TOKEN"async function getBioPage(accessToken: string, creatorId: string): Promise<unknown> {
const res = await fetch(`https://api.bio.re/api/v1/creators/${creatorId}/bio`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Bio page fetch failed'), {
code: json?.error?.code,
});
}
return json.data; // BioPageResponseDto with .links and .template
}import { useQuery } from '@tanstack/react-query';
export const creatorKeys = {
bioPage: (creatorId: string) => ['creators', creatorId, 'bio'] as const,
};
export function useBioPage(creatorId: string) {
return useQuery({
queryKey: creatorKeys.bioPage(creatorId),
queryFn: async () => {
const res = await fetch(`/api/v1/creators/${creatorId}/bio`);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Bio page fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data;
},
enabled: Boolean(creatorId),
staleTime: 30_000,
});
}Try it
Authorization
bearer In: header
Path Parameters
Response Body
application/json
application/json
application/json
application/json
curl -X GET "https://loading/api/v1/creators/string/bio"{
"success": true,
"data": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"creatorId": "688ebf54-d343-4104-8711-82c2feac534a",
"templateId": "196100ac-4eec-4fb6-a7f7-86c8b584771d",
"bio": "string",
"themeOverride": {},
"customCss": "string",
"embedEnabled": true,
"published": true,
"emailCollectionEnabled": true,
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z",
"links": [
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"bioPageId": "19ae7b68-8677-4862-a3d1-274c4a95a121",
"title": "string",
"url": "string",
"icon": "string",
"sortOrder": 0,
"active": true,
"isSocial": true,
"platform": "string",
"embedType": "string",
"embedMeta": {},
"scheduledStart": "2019-08-24T14:15:22Z",
"scheduledEnd": "2019-08-24T14:15:22Z",
"clickCount": 0,
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"template": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"description": "string",
"tokens": {},
"thumbnail": "string",
"active": true,
"sortOrder": 0,
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
}
}{
"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
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/creator/creator.controller.ts | 125–134 (getBioPage) |
| DTO (response) | apps/api-core/src/modules/creator/dto/creator-client-response.dto.ts | 105–180 (BioPageResponseDto) |
| Service | apps/api-core/src/modules/creator/creator.service.ts | 297–304 (getBioPage), ownership helper verifyCreatorOwnership |
| Prisma models | packages/prisma/prisma/schema.prisma | BioPage, BioLink (relation links), BioTemplate (relation template), CreatorProfile.userId (ownership) |
Get Recent Activity
Merged feed of the creator's recent messages, processed payouts, and new bio-page subscribers. Three sources combined, sorted by timestamp DESC, sliced to limit.
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.