BIO.RE
Creator

Connect Social Account

Verify a creator's ownership of a social account via OAuth callback. Stores the link plus access/refresh tokens for later platform calls. Recomputes totalFollowers.

POST /api/v1/creators/social/connect — 🔑 Bearer · Rate limit: 30 req / hour

Verifies the creator actually owns the social account they're connecting by exchanging the OAuth code (and PKCE codeVerifier for X/Twitter) for the platform's identity. On success, persists the link in SocialAccount (with stored OAuth tokens for future platform calls), creates an initial SocialMetrics row, and recomputes CreatorProfile.totalFollowers across all linked accounts.

The code and redirectUri come from the platform's OAuth callback — your frontend / mobile redirects the user to the platform's auth screen, the platform redirects back with ?code=..., and you pass that here. codeVerifier is required for X/Twitter (PKCE flow) — generate it client-side, store in session storage, send the code_challenge derived from it on the initial auth request, and submit the verifier here.

One social account per (platform, platformUserId) pair. If the same social account is already linked to a different bio.re creator, the call fails with 409 account_linked_elsewhere — not silently overwritten.

Request

Body — ConnectSocialDto

FieldTypeRequiredValidationNotes
platformstringIsString()Platform identifier (e.g. instagram, x, youtube, tiktok) — server uses this to pick the OAuth handler
codestringIsString()OAuth authorization code from the platform's callback
redirectUristringIsString()The same redirect URI used in the auth request — platforms re-validate it
codeVerifierstringoptionalIsString()Required for X/Twitter (PKCE). Other platforms ignore it.
HeaderRequiredNotes
Authorization: Bearer <accessToken>JWT from POST /auth/login

Response

201 CreatedSuccessOnlyResponseDto

{
  "success": true
}
FieldTypeNotes
successbooleanAlways true on 201. Read the connected list back via GET /creators/social to render the updated UI.

Errors

HTTPcode / i18nKeyReason
400creator.social.verification_failedOAuth verification failed (bad code, expired, redirect mismatch, missing PKCE verifier for X)
400(DTO validation)Missing required fields
401(guard)Missing / invalid bearer token
409creator.social.already_connectedThe same (platform, platformUserId) is already linked to this creator
409creator.social.account_linked_elsewhereThe same (platform, platformUserId) is linked to a different creator
429(throttle)Rate limit exceeded (30 req/hour)

Side effects

  1. Call the social verification handler for platform with (code, redirectUri, codeVerifier). This is a server-to-platform OAuth token exchange + identity probe.
  2. If verified === false or no platformUserId returned → verification_failed.
  3. Lookup SocialAccount by (platform, platformUserId). If found and same user → already_connected. If found and different user → account_linked_elsewhere.
  4. Inside one transaction:
    • SocialAccount.create({ id, userId, platform, platformUserId, platformUsername, verified: true, connectedAt: now(), accessToken?, refreshToken?, tokenExpiresAt? }) — OAuth tokens stored when the platform returned them.
    • SocialMetrics.create({ id, socialAccountId, followerCount, lastSyncedAt: now() }) — initial follower count from the verification probe.
    • Sum every SocialMetrics.followerCount row for this user → CreatorProfile.totalFollowers.
  5. Audit log: [social] Connected {platform} for user {userId} (followers: <count>).

Code samples

curl -X POST https://api.bio.re/api/v1/creators/social/connect \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "platform": "instagram",
    "code": "AQBx8...",
    "redirectUri": "https://bio.re/auth/callback/instagram"
  }'
type ConnectSocialInput = {
  platform: string;
  code: string;
  redirectUri: string;
  codeVerifier?: string; // required for X/Twitter (PKCE)
};

async function connectSocial(accessToken: string, input: ConnectSocialInput): Promise<void> {
  const res = await fetch('https://api.bio.re/api/v1/creators/social/connect', {
    method: 'POST',
    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 ?? 'Social connect failed'), {
      code: json?.error?.code,
    });
  }
}
import { useMutation, useQueryClient } from '@tanstack/react-query';

export function useConnectSocial() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: async (input: ConnectSocialInput) => {
      const res = await fetch('/api/v1/creators/social/connect', {
        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 ?? 'Social connect failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
    },
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['creators', 'social'] });
      qc.invalidateQueries({ queryKey: ['creators', 'profile'] });
    },
  });
}

Try it

POST
/api/v1/creators/social/connect
AuthorizationBearer <token>

In: header

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/social/connect" \  -H "Content-Type: application/json" \  -d '{    "platform": "instagram",    "code": "AQBx8...",    "redirectUri": "https://bio.re/callback"  }'
{
  "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"
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/creator/creator.controller.ts62–71 (connectSocial)
DTO (request)apps/api-core/src/modules/creator/dto/creator-social.dto.ts4–16 (ConnectSocialDto)
DTO (response)apps/api-core/src/common/dto/common-response.dto.tsSuccessOnlyResponseDto
Serviceapps/api-core/src/modules/creator/creator.service.ts203–263 (connectSocialVerified + connectSocial)
OAuth verifierapps/api-core/src/modules/creator/social/socialVerification.verify() (per-platform OAuth token exchange + identity probe)
Prisma modelspackages/prisma/prisma/schema.prismaSocialAccount, SocialMetrics, CreatorProfile.totalFollowers

On this page