Start KYC Verification
Initiate identity verification with the active KYC provider. Returns a redirect URL for the hosted verification flow. Vendor identity is admin-managed — clients receive an opaque provider id.
POST /api/v1/creators/kyc/start — 🔑 Bearer · Rate limit: 5 req / hour
Starts an identity verification session with the active KYC provider (admin-managed via external.kyc.active_provider; failover handled server-side). Returns a hosted-flow redirectUrl plus opaque sessionId and provider identifier — the client opens the URL, the user completes verification on the provider's site, and the provider notifies us via webhook.
Admin kill switch. When the KYC kill switch is active (admin-managed), every call returns 503 features.kyc_disabled regardless of state. Surface a "verification temporarily unavailable" UI when you see that response.
The provider field in the response is the active provider's identifier string (e.g. "<provider-id>"). It's opaque to clients — don't branch on its value, don't display it. It exists for support / debugging trails. Vendor identity stays in admin; this endpoint guarantees a session via whichever provider is active.
Request
No body, no params. Identity comes from the bearer token.
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
201 Created — ApiResponseOf<KycStartResponseDto>
{
"success": true,
"data": {
"redirectUrl": "https://provider.example.com/verify?session=...",
"sessionId": "sess_...",
"provider": "<provider-id>"
}
}| Field | Type | Notes |
|---|---|---|
redirectUrl | string | Open this URL in a popup or full-page redirect. The user completes ID upload + biometrics on the provider's hosted flow. |
sessionId | string | The provider's session id — useful only for support tickets. |
provider | string | Identifier of the active KYC provider (admin-managed). Treat as opaque. |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
400 | (KYC service) | Already approved or in-progress (provider-specific message) |
401 | (guard) | Missing / invalid bearer token |
404 | error.creator.not_found | No CreatorProfile for this user — call POST /creators/upgrade first |
429 | (throttle) | Rate limit exceeded (5 req/hour) |
503 | features.kyc_disabled | Admin kill switch KYC is active |
Side effects
- Kill switch check (
@RequireKillSwitch('KYC')) — if active, throw503 features.kyc_disabledbefore reaching the service. - Lookup
CreatorProfilebyuserId; thrownot_foundif missing. - Delegate to
creatorLevelService.startKYC(creator.id)→kycService.startVerification(creator.id):- Resolve the active KYC provider from admin config.
- Create a verification session via the provider's API (server-to-server).
- Persist
kycProvider+kycProviderId+ flipkycStatustoPENDINGonCreatorProfile. - Return
{ redirectUrl, sessionId, provider }.
- The user completes verification out-of-band; the provider's webhook (handled by
webhooks/kyc/*, internal scope) flipskycStatustoAPPROVED/REJECTEDand setskycCompletedAt.
Code samples
curl -X POST https://api.bio.re/api/v1/creators/kyc/start \
-H "Authorization: Bearer $ACCESS_TOKEN"type KycStart = {
redirectUrl: string;
sessionId: string;
provider: string;
};
async function startKyc(accessToken: string): Promise<KycStart> {
const res = await fetch('https://api.bio.re/api/v1/creators/kyc/start', {
method: 'POST',
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'KYC start failed'), {
code: json?.error?.code,
});
}
return json.data;
}
// Usage: open redirectUrl in a popup or full-page redirect
function launchKyc(redirectUrl: string) {
window.location.assign(redirectUrl);
}import { useMutation, useQueryClient } from '@tanstack/react-query';
export function useStartKyc() {
const qc = useQueryClient();
return useMutation({
mutationFn: async () => {
const res = await fetch('/api/v1/creators/kyc/start', { method: 'POST' });
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'KYC start failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as KycStart;
},
onSuccess: () => {
// Status will flip to PENDING server-side
qc.invalidateQueries({ queryKey: ['creators', 'kyc', 'status'] });
qc.invalidateQueries({ queryKey: ['creators', 'profile'] });
},
});
}Try it
Authorization
bearer In: header
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://loading/api/v1/creators/kyc/start"{
"success": true,
"data": {
"redirectUrl": "https://provider.example.com/verify?session=abc123",
"sessionId": "sess_abc123",
"provider": "sumsub"
}
}{
"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
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/creator/creator.controller.ts | 277–291 (startKyc) |
| DTO (response) | apps/api-core/src/modules/creator/dto/creator-client-response.dto.ts | 547–556 (KycStartResponseDto) |
| Service | apps/api-core/src/modules/creator/creator-level.service.ts | 150–152 (startKYC — delegates to KycService) |
| KYC service | apps/api-core/src/modules/creator/kyc/kyc.service.ts | startVerification() (provider abstraction) |
| Kill switch | apps/api-core/src/common/guards/kill-switch.guard.ts | RequireKillSwitch('KYC') |
| Config | (admin-managed) | external.kyc.active_provider ConfigService key |
| Prisma model | packages/prisma/prisma/schema.prisma | CreatorProfile.kycStatus (enum KycStatus), CreatorProfile.kycProvider, CreatorProfile.kycProviderId |
Update Bank Details
Sparse update of bank fields (IBAN/SWIFT/holder/country/name) plus optional preferred-method choice. ANY bank field change resets the verified flag — admin must re-verify before BANK_TRANSFER becomes available again.
Get KYC Status
Read current KYC state plus the active provider id and completion timestamp. Owner-only. Provider value is opaque to clients.