Subscribe to Bio Page
Public fan-side email signup for a creator's bio page mailing list. Double-opt-in — confirmation email is sent through the active email provider (admin-managed) with a token.
POST /api/v1/creators/:bioPageId/subscribe — 🌐 Public · Rate limit: 5 req / hour
Adds a fan email to a creator's bio page mailing list. Double-opt-in — the row is created with confirmed: false, a confirmToken is generated, and a confirmation email is dispatched via the active email provider (admin-managed via external.email.active_provider). The subscription only becomes "active" once the user clicks the link in the email, which calls GET /creators/subscribe/confirm.
Email collection must be enabled on the bio page for this endpoint to work. The creator opts in via PATCH /creators/:creatorId/bio { emailCollectionEnabled: true }. Otherwise the call fails with 400 not_enabled.
Per-bio-page uniqueness. A given email can subscribe to the same bio page only once — Prisma P2002 (unique constraint) is mapped to 409 already_subscribed. Different bio pages don't conflict.
Request
Path parameters
| Param | Type | Validation | Notes |
|---|---|---|---|
bioPageId | string (UUID) | ParseUUIDPipe | The bio page hosting the mailing list |
Body — SubscribeToBioDto
| Field | Type | Required | Validation | Notes |
|---|---|---|---|---|
email | string | ✓ | IsEmail() | Fan's email address. Server lowercases + trims before storing. |
name | string | optional | MaxLength(100) | Display name. HTML-stripped server-side to avoid stored XSS via subscriber list rendering. |
No headers required.
Response
200 OK — ApiResponseOf<MessageResponseDto>
{
"success": true,
"data": {
"message": "Please check your email to confirm subscription"
}
}| Field | Type | Notes |
|---|---|---|
message | string | Localizable confirmation prompt — surface as a toast/banner. |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | creator.subscribe.not_enabled | BioPage.emailCollectionEnabled !== true |
400 | (DTO validation) | Invalid email format; name exceeds 100 chars; bioPageId not a UUID |
409 | creator.subscribe.already_subscribed | This email is already in the BioEmailSubscriber table for this bio page (Prisma P2002 race) |
429 | (throttle) | Rate limit exceeded (5 req/hour) |
Side effects
- Lookup
BioPage; ifemailCollectionEnabled !== true→ thrownot_enabled. - Normalize email:
.toLowerCase().trim(). - Sanitize
name:stripHtmlTags(name). - Generate
confirmToken = randomUUID(). bioEmailSubscriber.create({ bioPageId, email, name, confirmToken })— PrismaP2002mapped toalready_subscribed.- Build confirm URL:
<seo.canonical_base_url>/subscribe/confirm?token=<confirmToken>(admin-managed base URL). - Fire-and-forget dispatch via
notificationService.send({ eventKey: 'email_subscription_confirm', userId: 'system', variables: { confirmUrl, email } })— failure is logged, request still succeeds. - Return the safe message. Subscription is still pending — see
GET /creators/subscribe/confirmfor the activation step.
Code samples
curl -X POST 'https://api.bio.re/api/v1/creators/b1a2b3c4-d5e6-7890-abcd-ef1234567890/subscribe' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]",
"name": "Fan Doe"
}'type SubscribeToBioInput = {
email: string;
name?: string;
};
async function subscribeToBio(bioPageId: string, input: SubscribeToBioInput): Promise<string> {
const res = await fetch(`https://api.bio.re/api/v1/creators/${bioPageId}/subscribe`, {
method: 'POST',
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 ?? 'Subscribe failed'), {
code: json?.error?.code,
});
}
return json.data.message as string;
}import { useMutation } from '@tanstack/react-query';
export function useSubscribeToBio(bioPageId: string) {
return useMutation({
mutationFn: async (input: SubscribeToBioInput) => {
const res = await fetch(`/api/v1/creators/${bioPageId}/subscribe`, {
method: 'POST',
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 ?? 'Subscribe failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data.message as string;
},
});
}Try it
Authorization
bearer In: header
Path Parameters
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
curl -X POST "https://loading/api/v1/creators/string/subscribe" \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]" }'{
"success": true,
"data": {
"message": "Operation completed successfully"
}
}{
"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 | 339–349 (subscribe) |
| DTO (request) | apps/api-core/src/modules/creator/dto/creator.dto.ts | 128–134 (SubscribeToBioDto) |
| DTO (response) | apps/api-core/src/common/dto/common-response.dto.ts | MessageResponseDto |
| Service | apps/api-core/src/modules/creator/creator.service.ts | 631–660 (subscribeToBioPage) |
| Notification pipeline | apps/api-core/src/modules/notification/notification.service.ts | send() (active email provider, admin-managed) |
| Config | apps/api-core/src/modules/config/config.service.ts | seo.canonical_base_url (admin-managed) |
| Email provider | (admin-managed) | external.email.active_provider |
| Prisma model | packages/prisma/prisma/schema.prisma | BioPage.emailCollectionEnabled, BioEmailSubscriber (@@unique([bioPageId, email])) |
Get Dashboard Login Link
One-shot login URL for the creator's Stripe Express dashboard. Requires the account to be ACTIVE. Use to deep-link into payouts, payment methods, and settings.
Confirm Subscription
Public token endpoint. The fan clicks the link in the confirmation email; this flips confirmed=true and clears the token. Single-use.