Skip to main content

Overview

The API provides a full KYC (Know Your Customer) identity verification flow. Three verification modes are available, controlled by a server-side dynamic configuration:
  • WebSDK mode (default) — the embedded third-party SDK handles everything: document upload, selfie capture, and liveness checks
  • Hybrid mode — your app uploads documents via the API, then launches the SDK for a liveness-only step
  • Document-only mode — your app uploads documents via the API with no SDK or liveness step required
The active mode is communicated to your frontend via meta.kyc_mode on the kyc_verification onboarding step. The value is "websdk", "hybrid", or "document_only". The KYC system is:
  • Webhook-updated — verification results arrive asynchronously; your client polls for the final outcome
  • GDPR-compliant — only the user’s name is stored locally; all identity documents and PII are held by the verification provider
  • Onboarding-integrated — when KYC is approved while the user is on the kyc_verification onboarding step, onboarding auto-advances to the next step
Sumvin holds the outcome, not the documents. The envelope around the verification is ours; the payload stays with the provider.

Quick Start

WebSDK Mode

1. Generate an SDK access token

curl -X POST /v0/kyc/access-token \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "access_token": "sbx:abc123def456...",
  "expires_in": 600,
  "user_id": "usr_abc123",
  "_links": {
    "self": { "href": "/v0/kyc/access-token", "method": "POST" },
    "status": { "href": "/v0/kyc/status" }
  }
}
The token is valid for 600 seconds (10 minutes). Generate a new one if it expires before the user finishes.

2. Initialize the embedded SDK

Pass the access_token to the verification SDK in your frontend. The SDK handles document upload, selfie capture, and liveness detection. Refer to the SDK provider’s documentation for framework-specific integration guides.

3. Poll for verification status

Once the user completes the SDK flow, poll the status endpoint until a terminal state is reached:
curl /v0/kyc/status?refresh=true \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "status": "approved",
  "applicant_id": "65abc123def456",
  "verified_at": 1704067200000,
  "rejected_at": null,
  "reject_reason": null,
  "_links": {
    "self": { "href": "/v0/kyc/status" },
    "user": { "href": "/v0/user/me" },
    "refresh": { "href": "/v0/kyc/status?refresh=true" }
  }
}

4. Onboarding auto-advances

If the user is on the kyc_verification onboarding step when their KYC is approved, onboarding automatically advances to the next step. Re-fetch onboarding state to see the updated step:
curl /v0/user/me?expand=onboarding \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"

Hybrid Mode

1. Create an applicant

curl -X POST /v0/kyc/applicant \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "applicant_id": "65abc123def456",
  "external_user_id": "usr_abc123",
  "already_existed": false,
  "_links": {
    "self": { "href": "/v0/kyc/applicant", "method": "POST" },
    "documents": { "href": "/v0/kyc/documents", "method": "POST" },
    "required-docs": { "href": "/v0/kyc/documents/required" },
    "status": { "href": "/v0/kyc/status" }
  }
}

2. Upload identity documents

curl -X POST /v0/kyc/documents \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>" \
  -F "document_type=PASSPORT" \
  -F "country=GBR" \
  -F "file=@passport.jpg"
{
  "document_type": "PASSPORT",
  "country": "GBR",
  "side": null,
  "image_id": "img_abc123",
  "_links": {
    "self": { "href": "/v0/kyc/documents", "method": "POST" },
    "required-docs": { "href": "/v0/kyc/documents/required" },
    "applicant": { "href": "/v0/kyc/applicant", "method": "POST" },
    "status": { "href": "/v0/kyc/status" }
  }
}

3. Check all required documents are uploaded

curl /v0/kyc/documents/required \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "steps": [
    {
      "step_type": "IDENTITY",
      "has_documents": true,
      "review_answer": null,
      "moderation_comment": null
    }
  ],
  "all_uploaded": true,
  "_links": {
    "self": { "href": "/v0/kyc/documents/required" },
    "documents": { "href": "/v0/kyc/documents", "method": "POST" },
    "status": { "href": "/v0/kyc/status" },
    "submit": { "href": "/v0/kyc/submit", "method": "POST" }
  }
}

4. Launch liveness-only SDK

Generate a liveness-scoped access token and initialize the SDK for the liveness step only:
curl -X POST /v0/kyc/access-token?level=liveness \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "access_token": "sbx:liveness_token...",
  "expires_in": 600,
  "user_id": "usr_abc123",
  "_links": {
    "self": { "href": "/v0/kyc/access-token", "method": "POST" },
    "status": { "href": "/v0/kyc/status" }
  }
}
Pass the token to the SDK. The SDK opens directly to the liveness capture step — no document upload screens.

5. Submit for review

After the liveness step completes, submit the applicant for review:
curl -X POST /v0/kyc/submit \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "status": "pending",
  "applicant_id": "65abc123def456",
  "_links": {
    "self": { "href": "/v0/kyc/submit", "method": "POST" },
    "status": { "href": "/v0/kyc/status" },
    "details": { "href": "/v0/kyc/details" }
  }
}

6. Poll for verification status

Same as WebSDK mode — poll the status endpoint with ?refresh=true until a terminal state is reached. GET /v0/kyc/status — Poll current status.

Document-Only Mode

1. Create an applicant

curl -X POST /v0/kyc/applicant \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "applicant_id": "65abc123def456",
  "external_user_id": "usr_abc123",
  "already_existed": false,
  "_links": {
    "self": { "href": "/v0/kyc/applicant", "method": "POST" },
    "documents": { "href": "/v0/kyc/documents", "method": "POST" },
    "required-docs": { "href": "/v0/kyc/documents/required" },
    "status": { "href": "/v0/kyc/status" }
  }
}

2. Upload identity documents

curl -X POST /v0/kyc/documents \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>" \
  -F "document_type=PASSPORT" \
  -F "country=GBR" \
  -F "file=@passport.jpg"

3. Check all required documents are uploaded

curl /v0/kyc/documents/required \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
Confirm all_uploaded is true before submitting.

4. Submit for review

No liveness step is required. Submit directly after documents are uploaded:
curl -X POST /v0/kyc/submit \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"

5. Poll for verification status

Same as WebSDK mode — poll the status endpoint with ?refresh=true until a terminal state is reached. GET /v0/kyc/status — Poll current status.

KYC Modes

The verification mode is determined by a server-side dynamic configuration. Your frontend reads the mode from meta.kyc_mode on the kyc_verification onboarding step and renders the appropriate flow.
Modemeta.kyc_modeDocument UploadLiveness CheckWhen to Use
WebSDK"websdk"SDK handlesSDK handlesDefault — simplest integration
Hybrid"hybrid"API upload (see below)SDK liveness-onlyCustom document capture UI
Document-only"document_only"API upload (see below)NoneDocument verification without liveness
POST /v0/kyc/documents — Upload a document image. In WebSDK mode, you generate an access token and hand control to the SDK. In hybrid mode, you create an applicant, upload documents through the API, launch the SDK for liveness only, then submit for review. In document-only mode, you create an applicant, upload documents, and submit — no SDK initialization is needed.
The mode can change between sessions if the configuration is updated server-side. Always read meta.kyc_mode from the current onboarding step rather than hardcoding a mode.

KYC State

Reading Status

Fetch the current KYC status at any time:
GET /v0/kyc/status
Add ?refresh=true to pull the latest result from the verification provider instead of relying on the locally cached state. Use refresh=true during active verification when you need the most current status.

Response Shape

{
  "status": "in_progress",
  "applicant_id": "65abc123def456",
  "verified_at": null,
  "rejected_at": null,
  "reject_reason": null,
  "_links": {
    "self": { "href": "/v0/kyc/status" },
    "user": { "href": "/v0/user/me" },
    "refresh": { "href": "/v0/kyc/status?refresh=true" },
    "start-verification": { "href": "/v0/kyc/access-token", "method": "POST" }
  }
}
FieldTypeDescription
statusstringCurrent KYC status (see KYC Statuses)
applicant_idstring or nullProvider applicant ID. Null if verification not started
verified_atinteger or nullApproval timestamp (epoch milliseconds)
rejected_atinteger or nullRejection timestamp (epoch milliseconds)
reject_reasonstring or nullHuman-readable rejection or retry reason
The _links object includes a start-verification link when the status is pending or retry, guiding the client to begin or restart the verification flow.

KYC Lifecycle

Status transitions happen asynchronously. The API receives webhook events from the verification provider and updates the user’s KYC state. Your client discovers the new state by polling the status endpoint with ?refresh=true. GET /v0/kyc/status — Poll current status.

Endpoints

Generate SDK Access Token

POST /v0/kyc/access-token
Returns a short-lived access token for initializing the verification SDK in your frontend. Query parameters:
ParameterTypeDefaultDescription
levelstringnoneSet to "liveness" to generate a token scoped to the liveness-only verification step (hybrid mode)
Response: 200 OK
{
  "access_token": "sbx:abc123def456...",
  "expires_in": 600,
  "user_id": "usr_abc123",
  "_links": {
    "self": { "href": "/v0/kyc/access-token", "method": "POST" },
    "status": { "href": "/v0/kyc/status" }
  }
}
FieldTypeDescription
access_tokenstringToken to pass to the verification SDK
expires_inintegerToken validity in seconds (default 600)
user_idstringExternal user ID associated with this token
When level=liveness, the returned token restricts the SDK to the liveness verification step only. Use this in hybrid mode after documents have been uploaded via the API.

Check KYC Status

GET /v0/kyc/status
In React, useKycStatus() from our query patterns handles auth gating and shares status across components — pair it with a short refetchInterval while polling for approval.
Query parameters:
ParameterTypeDefaultDescription
refreshbooleanfalseFetch latest status from the verification provider
Response: 200 OK — see Response Shape above. When refresh=true:
  • If the user has no applicant record, returns 404 Not Found
  • If the provider is unreachable, returns 502 Bad Gateway
  • If KYC is approved and the user is on the kyc_verification onboarding step, onboarding auto-advances

Get KYC Details

GET /v0/kyc/details
Returns GDPR-compliant verification details including personal information (name only), document submission status, and rejection details. Query parameters:
ParameterTypeDefaultDescription
refreshbooleanfalseBypass the 5-minute cache and fetch from the provider
Response: 200 OK
{
  "status": "approved",
  "verified_at": 1704067200000,
  "rejected_at": null,
  "started_at": 1704060000000,
  "personal_info": {
    "first_name": "Jane",
    "last_name": "Smith",
    "date_of_birth": "1990-05-15",
    "nationality": "GBR",
    "country_of_residence": "GBR"
  },
  "documents": [
    {
      "document_type": "PASSPORT",
      "country": "GBR",
      "status": "verified",
      "submitted_at": 1704061000000
    }
  ],
  "verification_complete": true,
  "verification_passed": true,
  "rejection": null,
  "_links": {
    "self": { "href": "/v0/kyc/details" },
    "refresh": { "href": "/v0/kyc/details?refresh=true" },
    "status": { "href": "/v0/kyc/status" },
    "user": { "href": "/v0/user/me" }
  }
}
FieldTypeDescription
statusstringCurrent KYC status
verified_atinteger or nullApproval timestamp (epoch milliseconds)
rejected_atinteger or nullRejection timestamp (epoch milliseconds)
started_atinteger or nullWhen verification was started (epoch milliseconds)
personal_infoobject or nullName and nationality extracted from documents
documentsarraySubmitted documents with type, country, and status
verification_completebooleanWhether all required documents have been submitted
verification_passedbooleanWhether verification passed all checks
rejectionobject or nullRejection details (see below)
Rejection object:
FieldTypeDescription
can_retrybooleantrue if the user can resubmit, false for terminal rejections
messagestringHuman-readable rejection reason
The personal_info fields other than name (date_of_birth, nationality, country_of_residence) are fetched from the provider on each request and are not stored locally. They are only available while the provider retains the applicant data.

Create Applicant

POST /v0/kyc/applicant
Creates a verification provider applicant for the user. Required before uploading documents in hybrid and document-only modes. This endpoint is idempotent. If an applicant already exists, it returns the existing applicant with a 200 OK response and already_existed: true. A new applicant returns 201 Created. Response: 200 OK / 201 Created
{
  "applicant_id": "65abc123def456",
  "external_user_id": "usr_abc123",
  "already_existed": false,
  "_links": {
    "self": { "href": "/v0/kyc/applicant", "method": "POST" },
    "documents": { "href": "/v0/kyc/documents", "method": "POST" },
    "required-docs": { "href": "/v0/kyc/documents/required" },
    "status": { "href": "/v0/kyc/status" }
  }
}
FieldTypeDescription
applicant_idstringProvider applicant ID
external_user_idstringExternal user ID associated with this applicant
already_existedbooleantrue if the applicant was already created

Upload Document

POST /v0/kyc/documents
Uploads an identity document for the user’s applicant. Used in hybrid and document-only modes to submit documents via the API. An applicant must exist before uploading documents — create one first. POST /v0/kyc/applicant — Create a KYC applicant.
Building a React UI? useUploadKycDocument() from our mutation patterns wraps the multipart upload and invalidates useKycRequiredDocs on success so your UI re-checks all_uploaded automatically.
Request: multipart/form-data
FieldTypeRequiredDescription
document_typestringYesDocument type (see Document Types)
countrystringYesIssuing country (ISO 3166-1 alpha-3, e.g. GBR)
sidestringNoDocument side: FRONT_SIDE or BACK_SIDE (see Document Sides)
filefileYesDocument image or PDF. Max 10MB. Accepts JPEG, PNG, WebP, PDF
Response: 201 Created
{
  "document_type": "ID_CARD",
  "country": "GBR",
  "side": "FRONT_SIDE",
  "image_id": "img_abc123",
  "_links": {
    "self": { "href": "/v0/kyc/documents", "method": "POST" },
    "required-docs": { "href": "/v0/kyc/documents/required" },
    "applicant": { "href": "/v0/kyc/applicant", "method": "POST" },
    "status": { "href": "/v0/kyc/status" }
  }
}
FieldTypeDescription
document_typestringType of document uploaded
countrystringIssuing country (ISO 3166-1 alpha-3)
sidestring or nullDocument side, if provided
image_idstring or nullProvider image ID for the uploaded document

Check Required Documents

GET /v0/kyc/documents/required
Returns the required document steps for the user’s verification level and whether each step has documents uploaded. Use this to determine which documents still need to be uploaded before submission. An applicant must exist before checking required documents. POST /v0/kyc/applicant — Create a KYC applicant.
In React, useKycRequiredDocs() from our query patterns is the canonical “what’s left to upload?” hook — it auto-revalidates after useUploadKycDocument mutations and exposes all_uploaded for gating the submit button.
Response: 200 OK
{
  "steps": [
    {
      "step_type": "IDENTITY",
      "has_documents": true,
      "review_answer": null,
      "moderation_comment": null
    },
    {
      "step_type": "SELFIE",
      "has_documents": false,
      "review_answer": null,
      "moderation_comment": null
    }
  ],
  "all_uploaded": false,
  "_links": {
    "self": { "href": "/v0/kyc/documents/required" },
    "documents": { "href": "/v0/kyc/documents", "method": "POST" },
    "status": { "href": "/v0/kyc/status" }
  }
}
FieldTypeDescription
stepsarrayList of required document steps
steps[].step_typestringStep type (e.g. IDENTITY, SELFIE, PROOF_OF_ADDRESS)
steps[].has_documentsbooleanWhether documents have been uploaded for this step
steps[].review_answerstring or nullReview answer if reviewed (GREEN, RED, or null)
steps[].moderation_commentstring or nullModeration comment if any
all_uploadedbooleantrue when all required steps have documents — ready for submission
When all_uploaded is true, the _links object includes a submit link:
{
  "_links": {
    "submit": { "href": "/v0/kyc/submit", "method": "POST" }
  }
}

Submit for Review

POST /v0/kyc/submit
Submits the applicant for review after all required documents have been uploaded. Used in hybrid mode (after document upload and liveness completion) and document-only mode (after document upload). An applicant must exist and all required documents must be uploaded. If documents are missing, returns 409 Conflict. Response: 200 OK
{
  "status": "pending",
  "applicant_id": "65abc123def456",
  "_links": {
    "self": { "href": "/v0/kyc/submit", "method": "POST" },
    "status": { "href": "/v0/kyc/status" },
    "details": { "href": "/v0/kyc/details" }
  }
}
FieldTypeDescription
statusstringSubmission status ("pending")
applicant_idstringProvider applicant ID
After submission, poll the status endpoint with ?refresh=true for the verification result. GET /v0/kyc/status — Poll current status.

Integration Patterns

WebSDK Flow

A typical frontend integration using WebSDK mode follows this sequence:
  1. Check current status.
  2. If pending or retry, generate an SDK access token.
  3. Initialize the verification SDK with the token.
  4. When the SDK signals completion, poll status with ?refresh=true.
  5. If approved, proceed to the next step. If retry, show the rejection message and let the user try again.
Endpoints used in this flow:
status = GET /v0/kyc/status

if status == "pending" or status == "retry":
    token = POST /v0/kyc/access-token
    launch_sdk(token.access_token)
    await sdk_completion()

    while status not in ["approved", "rejected"]:
        wait(3 seconds)
        status = GET /v0/kyc/status?refresh=true

if status == "approved":
    proceed_to_next_step()
elif status == "rejected":
    show_rejection_message(status.reject_reason)
elif status == "retry":
    show_retry_prompt(status.reject_reason)

Hybrid Flow

A frontend integration using hybrid mode follows this sequence:
  1. Check current status.
  2. If pending or retry, create an applicant.
  3. Upload each required document.
  4. Check required documents to confirm all_uploaded is true.
  5. Generate a liveness-scoped access token (?level=liveness).
  6. Initialize the SDK with the liveness token — the SDK opens directly to the liveness step.
  7. When the SDK signals completion, submit for review.
  8. Poll status with ?refresh=true for the final result.
Endpoints used in this flow:
status = GET /v0/kyc/status

if status == "pending" or status == "retry":
    applicant = POST /v0/kyc/applicant

    for each document:
        POST /v0/kyc/documents (document_type, country, side, file)

    required = GET /v0/kyc/documents/required
    assert required.all_uploaded == true

    token = POST /v0/kyc/access-token?level=liveness
    launch_sdk(token.access_token)
    await sdk_completion()

    POST /v0/kyc/submit

    while status not in ["approved", "rejected"]:
        wait(3 seconds)
        status = GET /v0/kyc/status?refresh=true

if status == "approved":
    proceed_to_next_step()
elif status == "rejected":
    show_rejection_message(status.reject_reason)
elif status == "retry":
    show_retry_prompt(status.reject_reason)

Document-Only Flow

A frontend integration using document-only mode follows this sequence:
  1. Check current status.
  2. If pending or retry, create an applicant.
  3. Upload each required document.
  4. Check required documents to confirm all_uploaded is true.
  5. Submit for review.
  6. Poll status with ?refresh=true for the final result.
Endpoints used in this flow:
status = GET /v0/kyc/status

if status == "pending" or status == "retry":
    applicant = POST /v0/kyc/applicant

    for each document:
        POST /v0/kyc/documents (document_type, country, side, file)

    required = GET /v0/kyc/documents/required
    assert required.all_uploaded == true

    POST /v0/kyc/submit

    while status not in ["approved", "rejected"]:
        wait(3 seconds)
        status = GET /v0/kyc/status?refresh=true

if status == "approved":
    proceed_to_next_step()
elif status == "rejected":
    show_rejection_message(status.reject_reason)
elif status == "retry":
    show_retry_prompt(status.reject_reason)

Polling for Approval

After the user completes the verification flow, the verification result arrives asynchronously via webhook. Poll the status endpoint with refresh=true at a reasonable interval until a terminal state is reached:
# First poll — may still be in_progress
curl /v0/kyc/status?refresh=true \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"

# Response: { "status": "in_progress", ... }

# Wait 3-5 seconds, then poll again
curl /v0/kyc/status?refresh=true \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"

# Response: { "status": "approved", "verified_at": 1704067200000, ... }
Avoid polling more frequently than every 3 seconds. Most verifications complete within 30 seconds to 2 minutes, but some require manual review and may take longer.

Handling Rejection

There are two rejection outcomes with different behaviours: Retry (status: "retry") — the user can fix and resubmit. The reject_reason field explains what needs correction. In WebSDK or hybrid mode, generate a new access token and let the user re-enter the SDK flow. In document-only mode, re-upload corrected documents and submit again. Rejected (status: "rejected") — terminal. The user cannot retry with the same identity documents. Display the reject_reason and direct them to contact support.
# Retry scenario
curl /v0/kyc/status \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"
{
  "status": "retry",
  "applicant_id": "65abc123def456",
  "verified_at": null,
  "rejected_at": null,
  "reject_reason": "Document image is blurry. Please resubmit a clear photo.",
  "_links": {
    "self": { "href": "/v0/kyc/status" },
    "user": { "href": "/v0/user/me" },
    "refresh": { "href": "/v0/kyc/status?refresh=true" },
    "start-verification": { "href": "/v0/kyc/access-token", "method": "POST" }
  }
}
When the status is retry, the _links object includes start-verification so your client can initiate a new verification attempt.

Onboarding Integration

KYC verification is one step in the onboarding flow. When KYC is approved and the user is on the kyc_verification onboarding step, the step auto-advances without requiring an explicit submit call. This happens in two scenarios:
  1. Webhook-driven: The verification provider sends a webhook with an approval, and the API advances onboarding automatically.
  2. Poll-driven: Your client polls the status endpoint with ?refresh=true, the API detects approval, and advances onboarding.
GET /v0/kyc/status — Poll current status. In both cases, re-fetch onboarding state afterward to render the next step:
curl /v0/user/me?expand=onboarding \
  -H "x-juno-jwt: <token>" \
  -H "x-juno-orgid: <your-org-id>"

Error Handling

All error responses follow the RFC 7807 Problem Details format:
{
  "type": "https://api.sumvin.com/errors/kyc-500-002",
  "title": "KYC Token Generation Failed",
  "status": 500,
  "detail": "Failed to generate KYC access token",
  "instance": "/v0/kyc/access-token",
  "error_code": "KYC-500-002"
}
StatusError CodeMeaningAction
400 Bad RequestKYC-400-001Invalid document file typeUse a supported format: JPEG, PNG, WebP, or PDF
400 Bad RequestKYC-400-002File exceeds 10MB size limitReduce file size and retry
401 UnauthorizedKYC-401-001Webhook signature validation failedInternal: check webhook secret configuration
403 ForbiddenKYC-403-001KYC verification required for this actionComplete KYC verification first
404 Not FoundKYC-404-001No KYC applicant record foundStart verification (generate an access token) or create an applicant — see endpoints below
409 ConflictKYC-409-001Not all required documents uploadedUpload remaining documents and re-check required status — see endpoints below
500 Internal Server ErrorKYC-500-001Internal error processing KYCRetry; contact support if persistent
500 Internal Server ErrorKYC-500-002Failed to generate SDK access tokenRetry; contact support if persistent
500 Internal Server ErrorKYC-500-003Document upload to provider failedRetry the upload; contact support if persistent
500 Internal Server ErrorKYC-500-004Submission for review failedRetry; contact support if persistent
502 Bad GatewayKYC-502-001Verification provider unavailableRetry after a short delay
Recovery endpoints referenced above:

Reference Tables

KYC Statuses

ValueDescriptionTerminal?
pendingUser has not started verificationNo
in_progressVerification started, awaiting resultNo
retryAdditional information requested — user can resubmitNo
approvedVerification passedYes
rejectedVerification failed — user cannot retryYes

Document Types

ValueDescription
PASSPORTInternational passport
ID_CARDNational identity card
DRIVERSDriver’s license
RESIDENCE_PERMITResidence permit

Document Sides

Some document types require both sides to be uploaded separately.
ValueDescription
FRONT_SIDEFront of the document
BACK_SIDEBack of the document
Passports typically require only a single upload (no side). ID cards and driver’s licenses typically require both FRONT_SIDE and BACK_SIDE.

Allowed File Types

MIME TypeExtension
image/jpeg.jpg, .jpeg
image/png.png
image/webp.webp
application/pdf.pdf
Maximum file size: 10MB. These fields are populated on the user object after KYC verification:
FieldTypeDescription
first_namestring or nullFirst name from identity document (set on approval)
last_namesstring or nullLast name(s) from identity document (set on approval)
kyc_verified_atinteger or nullApproval timestamp (epoch milliseconds)
kyc_rejected_atinteger or nullRejection timestamp (epoch milliseconds)

Next Steps

KYC is one step in the broader onboarding flow — read that guide for how the state machine advances when a verification lands, and the authentication reference for the JWT every KYC endpoint expects. KYC is the boundary between the identity and the attestation. Everything downstream — scopes, cards, spend — reads off what this step writes.