Get KYC Status
Read current KYC state plus the active provider id and completion timestamp. Owner-only. Provider value is opaque to clients.
GET /api/v1/creators/kyc/status — 🔑 Bearer
Returns the calling creator's current KYC state, the active provider identifier (opaque), the provider-side session id (for support trails), and the completion timestamp.
Status lifecycle. Created accounts start at NOT_STARTED. After POST /creators/kyc/start, the status flips to PENDING (or sometimes provider-side IN_REVIEW mapped to PENDING). The provider's webhook (handled internally) then flips it to APPROVED / REJECTED / EXPIRED. There is no client-driven path back to NOT_STARTED once started — re-running POST /creators/kyc/start after an expired or rejected session will start a new attempt with a fresh kycProviderId.
Request
No body, no params. Identity comes from the bearer token.
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<CreatorKycStatusDto>
{
"success": true,
"data": {
"status": "APPROVED",
"provider": "<provider-id>",
"providerId": "...",
"completedAt": "2026-04-29T20:00:00.000Z"
}
}| Field | Type | Notes |
|---|---|---|
status | enum | One of NOT_STARTED / PENDING / APPROVED / REJECTED / EXPIRED |
provider | string | null | Active KYC provider identifier (admin-managed via external.kyc.active_provider). Treat as opaque — don't branch on it, don't display it. null until first /kyc/start. |
providerId | string | null | The provider's session id. null until first /kyc/start. |
completedAt | string (ISO 8601) | null | When status flipped to APPROVED / REJECTED / EXPIRED. null while NOT_STARTED / PENDING. |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | (guard) | Missing / invalid bearer token |
404 | error.creator.not_found | No CreatorProfile for this user — call POST /creators/upgrade first |
Side effects
- Lookup
CreatorProfilebyuserId; thrownot_foundif missing. - Delegate to
creatorLevelService.getKYCStatus(creator.id)→kycService.getStatus(creator.id):- Read
CreatorProfile.kycStatus+kycProvider+kycProviderId+kycCompletedAt. - Return as-is. No mutations — provider state changes happen via webhook, not on read.
- Read
Polling guidance
- Poll while
status === 'PENDING'(every 5–10s while a verification dialog is open). - Stop polling once
statusisAPPROVED/REJECTED/EXPIRED. - Don't poll forever — if a session sits
PENDINGfor hours, surface a "still pending — refresh later" message rather than a tight loop.
Code samples
curl https://api.bio.re/api/v1/creators/kyc/status \
-H "Authorization: Bearer $ACCESS_TOKEN"type KycStatus = {
status: 'NOT_STARTED' | 'PENDING' | 'APPROVED' | 'REJECTED' | 'EXPIRED';
provider: string | null;
providerId: string | null;
completedAt: string | null;
};
async function getKycStatus(accessToken: string): Promise<KycStatus> {
const res = await fetch('https://api.bio.re/api/v1/creators/kyc/status', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'KYC status fetch failed'), {
code: json?.error?.code,
});
}
return json.data;
}import { useQuery } from '@tanstack/react-query';
export const creatorKeys = {
kycStatus: () => ['creators', 'kyc', 'status'] as const,
};
export function useKycStatus() {
return useQuery({
queryKey: creatorKeys.kycStatus(),
queryFn: async () => {
const res = await fetch('/api/v1/creators/kyc/status');
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'KYC status fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as KycStatus;
},
// Poll while PENDING, stop once terminal
refetchInterval: (query) => {
const data = query.state.data;
if (!data) return 5_000;
return data.status === 'PENDING' ? 5_000 : false;
},
staleTime: 30_000,
});
}Try it
Authorization
bearer In: header
Response Body
application/json
application/json
application/json
curl -X GET "https://loading/api/v1/creators/kyc/status"{
"success": true,
"data": {
"status": "APPROVED",
"provider": "sumsub",
"providerId": "string",
"completedAt": "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"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/creator/creator.controller.ts | 293–303 (getKycStatus) |
| DTO (response) | apps/api-core/src/modules/creator/dto/creator-kyc-status-response.dto.ts | 7–19 (CreatorKycStatusDto) |
| Service | apps/api-core/src/modules/creator/creator-level.service.ts | 157–159 (getKYCStatus — delegates to KycService) |
| KYC service | apps/api-core/src/modules/creator/kyc/kyc.service.ts | getStatus() |
| Webhook (status flips happen here) | apps/api-core/src/modules/creator/kyc/kyc-webhook.controller.ts | (internal scope, not in this portal) |
| Config | (admin-managed) | external.kyc.active_provider ConfigService key |
| Prisma model | packages/prisma/prisma/schema.prisma | CreatorProfile.kycStatus, CreatorProfile.kycProvider, CreatorProfile.kycProviderId, CreatorProfile.kycCompletedAt |
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.
Initiate Stripe Connect
Create a Stripe Connect Express account and return a hosted onboarding URL. KYC must be APPROVED first. Idempotent — repeated calls return a fresh link for the same account.