Public Sessions
curl --request GET \
--url https://api.example.com/public/sessions/{access_token}import requests
url = "https://api.example.com/public/sessions/{access_token}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/public/sessions/{access_token}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/public/sessions/{access_token}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/public/sessions/{access_token}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/public/sessions/{access_token}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/public/sessions/{access_token}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyKYC Workflows & Sessions
Public Sessions
GET
/
public
/
sessions
/
{access_token}
Public Sessions
curl --request GET \
--url https://api.example.com/public/sessions/{access_token}import requests
url = "https://api.example.com/public/sessions/{access_token}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/public/sessions/{access_token}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/public/sessions/{access_token}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/public/sessions/{access_token}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/public/sessions/{access_token}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/public/sessions/{access_token}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyPublic endpoints for end users to access and interact with KYC onboarding sessions. These endpoints require no authentication - access is controlled via secure access tokens.
Each step can also include optional display and behavior fields such as
Do not send this request to the KYC API host.
After confirmation, use
After a corrected step is completed, its open correction requests are marked
Add
Overview
Public session endpoints allow end users to:- Access Sessions: View session state and workflow definition
- Upload Documents: Upload required documents, selfies, and file fields directly to S3
- Extract Data: Extract data from uploaded documents
- Complete Steps: Mark steps as completed with form data
- Complete Liveness: Start and verify AWS Rekognition Face Liveness checks
- Manage Subsessions: Create and track related-party verification sessions
- Resolve Corrections: Fix reviewer-requested remediation items using the same access token
Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /public/sessions/{access_token} | Get session state |
GET | /public/sessions/{access_token}/workflow | Get workflow definition |
POST | /public/sessions/{access_token}/upload/init | Initialize direct S3 upload |
PUT | {upload_url} | Upload file bytes directly to S3 |
POST | /public/sessions/{access_token}/upload/confirm | Confirm direct S3 upload |
POST | /public/sessions/{access_token}/upload | Legacy base64 upload |
POST | /public/sessions/{access_token}/documents | Legacy base64 upload alias |
POST | /public/sessions/{access_token}/extract | Extract document data |
POST | /public/sessions/{access_token}/step/{step_id}/complete | Complete a step |
POST | /public/sessions/{access_token}/liveness/start | Start a liveness check |
POST | /public/sessions/{access_token}/liveness/complete | Complete a liveness check |
POST | /public/sessions/{access_token}/subsessions | Create a subsession |
GET | /public/sessions/{access_token}/subsessions | List subsessions, optionally filtered by step_id |
POST | /public/sessions/{access_token}/subsessions/{subsession_id}/regenerate | Regenerate subsession link |
DELETE | /public/sessions/{access_token}/subsessions/{subsession_id} | Delete an unfinished subsession |
Authentication
No authentication required. Access is controlled via theaccess_token path parameter.
Get Session State
Retrieve the current state of a session including progress and uploaded documents.Endpoint
GET /public/sessions/{access_token}
Request Example
curl https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002
Response Format
{
"status": "success",
"data": {
"session_id": "660e8400-e29b-41d4-a716-446655440001",
"workflow_name": "Basic KYC",
"status": "active",
"current_step_index": 1,
"total_steps": 3,
"steps_data": [
{
"step_id": "step_1",
"status": "completed",
"started_at": "2024-11-22T10:31:00Z",
"completed_at": "2024-11-22T10:32:00Z",
"documents": [
{
"doc_id": "doc_abc123",
"type": "id",
"file_name": "passport.jpg",
"mime_type": "image/jpeg",
"uploaded_at": "2024-11-22T10:32:00Z"
}
]
},
{
"step_id": "step_2",
"status": "in_progress",
"started_at": "2024-11-22T10:33:00Z",
"liveness_sessions": [
{
"liveness_session_id": "liv_abc123",
"status": "pending",
"created_at": "2024-11-22T10:33:00Z"
}
]
},
{
"step_id": "step_3",
"status": "pending"
}
],
"expires_at": 1732278600,
"created_at": "2024-11-22T10:30:00Z",
"branding": {
"logo_url": "https://...",
"primary_color": "#10B981",
"secondary_color": "#064E3B",
"background_color": "#FFFFFF",
"text_color": "#1F2937",
"accent_color": "#34D399"
}
}
}
attributes (omitted above) holds the session attributes the tenant supplied at creation, limited to the keys declared in the workflow’s session_attributes catalog. Combine them with the catalog returned by the workflow endpoint to evaluate attribute conditions client-side.
steps_data is sanitized for public clients. Internal storage keys such as S3 object paths, extraction profiles, and internal validation metadata are not returned.
When a session is in awaiting_client_correction, only the steps with open correction_requests can be edited. Correction requests expose customer-facing guidance such as message, field_ids, and document_types; reviewer identity and internal audit fields are not returned.
Get Workflow Definition
Retrieve the workflow definition to know what steps and fields are required.Endpoint
GET /public/sessions/{access_token}/workflow
Request Example
curl https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/workflow
Response Format
{
"status": "success",
"data": {
"workflow_name": "Basic KYC",
"description": "Standard KYC onboarding workflow",
"total_steps": 3,
"steps": [
{
"step_id": "step_1",
"order": 1,
"name": "ID Document",
"type": "document_upload",
"required": true,
"required_documents": ["id"],
"document_requirements": [
{
"document_type": "id",
"label": "Government ID",
"required": true,
"allow_multiple": false
}
],
"custom_fields": [],
"fields": []
},
{
"step_id": "step_2",
"order": 2,
"name": "Liveness",
"type": "liveness",
"required": true,
"required_documents": [],
"document_requirements": [],
"custom_fields": [],
"fields": [],
"liveness_config": {
"min_confidence": 90
}
},
{
"step_id": "step_3",
"order": 3,
"name": "Personal Information",
"type": "form_fill",
"required": true,
"required_documents": [],
"document_requirements": [],
"custom_fields": [
{
"field_id": "full_name",
"name": "Full Name",
"type": "text",
"required": true
},
{
"field_id": "date_of_birth",
"name": "Date of Birth",
"type": "date",
"required": true
},
{
"field_id": "email",
"name": "Email",
"type": "email",
"required": true
}
],
"fields": []
}
],
"completion_redirect_url": "https://example.com/kyc-complete"
}
}
description, instructions, tooltip, document_types, conditional_logic, and subsession_config when configured on the workflow.
The response also carries session_attributes: the catalog projection (key, type, options, default, fallback_field_id) needed to resolve conditions written as { "attribute": "industry", ... }. Each attribute resolves to the value in the session’s attributes, else the value of its fallback_field_id form field, else default. A fallback field whose attribute was already supplied is omitted from custom_fields.
Upload Files
The current public-session frontend uploads files directly to S3 using presigned URLs. This is the recommended and default flow for documents, selfies, and file fields because the file bytes never pass through the API Gateway/Lambda JSON body. The upload has three steps:- Call
/upload/initwith file metadata and the workflow step context. - Upload the raw file bytes to the returned
upload_urlusing HTTPPUT. - Call
/upload/confirmso the API registers the uploaded S3 object in the session.
Initialize Upload
Endpoint
POST /public/sessions/{access_token}/upload/init
Request Body Parameters
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/upload/init \
-H "Content-Type: application/json" \
-d '{
"step_id": "step_1",
"document_type": "id",
"file_name": "passport.pdf",
"mime_type": "application/pdf"
}'
Response Format
{
"status": "success",
"data": {
"doc_id": "doc_abc123",
"upload_url": "https://...",
"s3_key": "tenant123/660e8400-e29b-41d4-a716-446655440001/doc_abc123.pdf",
"expires_at": 1732278600
}
}
Upload To S3
Upload the file bytes toupload_url with an HTTP PUT. Use the same Content-Type passed to /upload/init.
Request Example
curl -X PUT "<upload_url>" \
-H "Content-Type: application/pdf" \
--data-binary "@passport.pdf"
upload_url is a presigned S3 URL returned by /upload/init.
Confirm Upload
Endpoint
POST /public/sessions/{access_token}/upload/confirm
Request Body Parameters
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/upload/confirm \
-H "Content-Type: application/json" \
-d '{
"doc_id": "doc_abc123",
"s3_key": "tenant123/660e8400-e29b-41d4-a716-446655440001/doc_abc123.pdf",
"step_id": "step_1",
"document_type": "id",
"file_name": "passport.pdf",
"mime_type": "application/pdf"
}'
Response Format
{
"status": "success",
"data": {
"doc_id": "doc_abc123",
"s3_key": "tenant123/660e8400-e29b-41d4-a716-446655440001/doc_abc123.pdf",
"uploaded_at": "2024-11-22T10:32:00Z",
"confirmed": true
}
}
doc_id when extracting data or completing form file fields.
Legacy Base64 Upload
The base64 upload endpoint remains available for backward compatibility, but new integrations should use direct S3 uploads via/upload/init and /upload/confirm.
Endpoint
POST /public/sessions/{access_token}/upload
POST /public/sessions/{access_token}/documents is also supported as a legacy alias.
Request Body Parameters
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/upload \
-H "Content-Type: application/json" \
-d '{
"step_id": "step_1",
"document_type": "id",
"file_data": "base64_encoded_file_data...",
"file_name": "passport.jpg",
"mime_type": "image/jpeg"
}'
Response Format
{
"status": "success",
"data": {
"doc_id": "doc_abc123",
"s3_key": "tenant123/660e8400-e29b-41d4-a716-446655440001/doc_abc123.jpg",
"uploaded_at": "2024-11-22T10:32:00Z"
}
}
Extract Document Data
Extract structured data from an uploaded document using OCR/AI.Endpoint
POST /public/sessions/{access_token}/extract
Request Body Parameters
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/extract \
-H "Content-Type: application/json" \
-d '{
"doc_id": "doc_abc123",
"document_type": "passport"
}'
Response Format
{
"status": "success",
"data": {
"doc_id": "doc_abc123",
"extracted_data": {
"full_name": "JOHN DOE",
"document_number": "AB1234567",
"date_of_birth": "1985-03-15",
"nationality": "US",
"expiry_date": "2030-01-15",
"gender": "M"
},
"confidence_scores": {
"overall": 0.95,
"full_name": 0.98,
"document_number": 0.99,
"date_of_birth": 0.92
}
}
}
Complete Step
Mark a step as completed, optionally with form data.Endpoint
POST /public/sessions/{access_token}/step/{step_id}/complete
Path Parameters
Request Body Parameters
Request Example - Document Step
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/step/step_1/complete \
-H "Content-Type: application/json" \
-d '{}'
Request Example - Form Step
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/step/step_3/complete \
-H "Content-Type: application/json" \
-d '{
"form_data": {
"full_name": "John Doe",
"date_of_birth": "1985-03-15",
"email": "john@example.com"
}
}'
Response Format
{
"status": "success",
"data": {
"step_id": "step_3",
"status": "completed",
"next_step_id": null,
"session_completed": true
}
}
Response Fields
| Field | Description |
|---|---|
step_id | The completed step ID |
status | Step status after completion |
next_step_id | ID of the next step, or null if this was the last step |
session_completed | True if all steps are now completed |
Correction Mode
If a reviewer requests corrections, the session status becomesawaiting_client_correction and one or more steps become needs_correction. The end user keeps using the original public link; no new token is required.
Public State Example
{
"status": "success",
"data": {
"session_id": "660e8400-e29b-41d4-a716-446655440001",
"status": "awaiting_client_correction",
"current_step_index": 1,
"steps_data": [
{
"step_id": "id_document",
"status": "completed"
},
{
"step_id": "personal_info",
"status": "needs_correction",
"form_data": {
"full_name": "Jon Doe"
},
"correction_requests": [
{
"id": "corr_abc123",
"step_id": "personal_info",
"field_ids": ["full_name"],
"document_types": [],
"message": "Please correct your legal name so it matches your ID.",
"status": "open",
"created_at": "2026-05-22T14:30:00Z"
}
]
}
]
}
}
Resolving Corrections
To resolve a correction, update the requested fields or documents and call the regular step completion endpoint:curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/step/personal_info/complete \
-H "Content-Type: application/json" \
-d '{
"form_data": {
"full_name": "John Doe"
}
}'
resolved. If other steps still have open corrections, the session remains awaiting_client_correction; otherwise the next status depends on the workflow’s validation_config.correction_resolution setting:
| Mode | Result |
|---|---|
manual_review | Session returns to manual_review for a reviewer decision |
auto_process | Session moves to completed and processing is triggered |
completed | Session moves to completed without automatic processing |
Liveness Checks
Liveness steps use AWS Rekognition Face Liveness. The frontend starts a liveness session, passes the returnedliveness_session_id to the AWS Amplify Liveness SDK, then confirms the result.
Start Liveness
Endpoint
POST /public/sessions/{access_token}/liveness/start
Request Body Parameters
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/liveness/start \
-H "Content-Type: application/json" \
-d '{
"step_id": "step_2"
}'
Response Format
{
"status": "success",
"data": {
"liveness_session_id": "liv_abc123",
"step_id": "step_2",
"message": "Use this session ID with the AWS Amplify Liveness SDK"
}
}
Complete Liveness
Endpoint
POST /public/sessions/{access_token}/liveness/complete
Request Body Parameters
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/liveness/complete \
-H "Content-Type: application/json" \
-d '{
"liveness_session_id": "liv_abc123"
}'
Response Format
{
"status": "success",
"data": {
"passed": true,
"confidence": 99.2,
"reference_image_s3_key": "tenant123/660e8400-e29b-41d4-a716-446655440001/liveness.jpg",
"step_completed": true,
"session_completed": false,
"next_step_id": "step_3",
"message": "Liveness check passed"
}
}
Error Responses
404 Not Found - Invalid Access Token
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "Session not found or expired"
},
"meta": {
"request_id": "req_123",
"timestamp": "2026-03-22T12:00:00Z"
}
}
400 Bad Request - Invalid Step
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Step not found"
},
"meta": {
"request_id": "req_123",
"timestamp": "2026-03-22T12:00:00Z"
}
}
400 Bad Request - Invalid Document Type
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Document type not allowed for this step"
},
"meta": {
"request_id": "req_123",
"timestamp": "2026-03-22T12:00:00Z"
}
}
400 Bad Request - Missing Required Fields
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Campos requeridos: missing required field"
},
"meta": {
"request_id": "req_123",
"timestamp": "2026-03-22T12:00:00Z"
}
}
Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created - presigned upload initialized, liveness session started, or subsession created |
| 400 | Bad Request - Invalid parameters or validation failed |
| 403 | Forbidden - operation not allowed |
| 404 | Not Found - Invalid access token or step not found |
| 500 | Internal Server Error |
JavaScript Integration Example
const accessToken = "770e8400-e29b-41d4-a716-446655440002";
const baseUrl = "https://kyc.legaltalent.ai/public/sessions";
// Get session state
async function getSessionState() {
const response = await fetch(`${baseUrl}/${accessToken}`);
const data = await response.json();
return data.data;
}
// Get workflow definition
async function getWorkflow() {
const response = await fetch(`${baseUrl}/${accessToken}/workflow`);
const data = await response.json();
return data.data;
}
// Upload document directly to S3 using the current presigned upload flow
async function uploadDocument(file, stepId, documentType) {
const initResponse = await fetch(`${baseUrl}/${accessToken}/upload/init`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
step_id: stepId,
document_type: documentType,
file_name: file.name,
mime_type: file.type
})
});
const initData = await initResponse.json();
await fetch(initData.data.upload_url, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file
});
const confirmResponse = await fetch(`${baseUrl}/${accessToken}/upload/confirm`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
doc_id: initData.data.doc_id,
s3_key: initData.data.s3_key,
step_id: stepId,
document_type: documentType,
file_name: file.name,
mime_type: file.type
})
});
const confirmData = await confirmResponse.json();
return confirmData.data;
}
// Legacy base64 upload. Prefer uploadDocument() above for new integrations.
async function uploadDocumentLegacyBase64(file, stepId, documentType) {
const base64Data = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(",")[1]);
reader.onerror = reject;
reader.readAsDataURL(file);
});
const response = await fetch(`${baseUrl}/${accessToken}/upload`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
step_id: stepId,
document_type: documentType,
file_data: base64Data,
file_name: file.name,
mime_type: file.type
})
});
const data = await response.json();
return data.data;
}
// Extract data from document
async function extractDocument(docId, documentType) {
const response = await fetch(`${baseUrl}/${accessToken}/extract`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
doc_id: docId,
document_type: documentType
})
});
const data = await response.json();
return data.data;
}
// Complete a step
async function completeStep(stepId, formData = null) {
const response = await fetch(
`${baseUrl}/${accessToken}/step/${stepId}/complete`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ form_data: formData })
}
);
const data = await response.json();
return data.data;
}
// Example: Complete onboarding flow
async function runOnboardingFlow() {
// 1. Get workflow to know the steps
const workflow = await getWorkflow();
console.log("Workflow:", workflow.workflow_name);
// 2. For each step, complete it
for (const step of workflow.steps) {
console.log(`Processing step: ${step.name}`);
if (step.type === 'document_upload') {
// Upload required documents
// In real app: get file from user input
} else if (step.type === 'selfie') {
// Capture and upload selfie
} else if (step.type === 'form_fill') {
// Collect form data from user
const formData = {
full_name: "John Doe",
date_of_birth: "1985-03-15"
};
await completeStep(step.step_id, formData);
}
}
// 3. Check final state
const session = await getSessionState();
console.log("Session status:", session.status);
}
Subsessions
Subsessions allow end users to create verification sessions for related parties (UBOs, submerchants, directors) during the main onboarding process. These are available when the workflow includes asubsession_collection step type.
Create Subsession
Create a new subsession for a related party (e.g., UBO, submerchant).Endpoint
POST /public/sessions/{access_token}/subsessions
Request Body Parameters
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/subsessions \
-H "Content-Type: application/json" \
-d '{
"step_id": "step_ubos",
"label": "Juan Garcia - UBO",
"metadata": {
"ownership_percentage": 25
}
}'
Response Format
{
"status": "success",
"data": {
"subsession_id": "880e8400-e29b-41d4-a716-446655440003",
"access_link": "https://kyc.legaltalent.ai/public/sessions/990e8400-e29b-41d4-a716-446655440004",
"workflow_name": "UBO Verification",
"label": "Juan Garcia - UBO",
"expires_at": 1732278600,
"created_at": "2024-11-22T10:30:00Z"
}
}
List Subsessions
Get all subsessions created from the current session.Endpoint
GET /public/sessions/{access_token}/subsessions
Request Example
curl https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/subsessions
Response Format
{
"status": "success",
"data": {
"subsessions": [
{
"subsession_id": "880e8400-e29b-41d4-a716-446655440003",
"access_link": "https://kyc.legaltalent.ai/public/sessions/990e8400-...",
"status": "completed",
"label": "Juan Garcia - UBO",
"tag": "ubo",
"workflow_id": "ubo-verification-workflow",
"expires_at": 1732278600,
"created_at": "2024-11-22T10:30:00Z",
"completed_at": "2024-11-22T11:00:00Z"
},
{
"subsession_id": "880e8400-e29b-41d4-a716-446655440005",
"access_link": "https://kyc.legaltalent.ai/public/sessions/990e8400-...",
"status": "active",
"label": "Maria Lopez - UBO",
"tag": "ubo",
"workflow_id": "ubo-verification-workflow",
"expires_at": 1732278600,
"created_at": "2024-11-22T10:35:00Z"
}
],
"total": 2,
"completed": 1,
"pending": 1,
"min_required": 1,
"max_allowed": 5,
"can_proceed": true,
"category": "ubo"
}
}
?step_id={step_id} to list only subsessions created for one subsession_collection step.
Regenerate Subsession Link
Generate a new access link for a subsession if the original was lost or needs to be refreshed.Endpoint
POST /public/sessions/{access_token}/subsessions/{subsession_id}/regenerate
Request Example
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/subsessions/880e8400-e29b-41d4-a716-446655440003/regenerate
Response Format
{
"status": "success",
"data": {
"subsession_id": "880e8400-e29b-41d4-a716-446655440003",
"access_link": "https://kyc.legaltalent.ai/public/sessions/aa0e8400-e29b-41d4-a716-446655440008",
"expires_at": 1732278600
}
}
Delete Subsession
Delete an unfinished subsession from the parent session. Completed, processed, or approved subsessions cannot be deleted.Endpoint
DELETE /public/sessions/{access_token}/subsessions/{subsession_id}
Request Example
curl -X DELETE https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/subsessions/880e8400-e29b-41d4-a716-446655440003
Response Format
{
"status": "success",
"data": {
"subsession_id": "880e8400-e29b-41d4-a716-446655440003",
"deleted": true
}
}
Subsession Errors
400 Bad Request - Maximum Subsessions Reached
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Maximum number of subsessions (5) reached"
},
"meta": {
"request_id": "req_123",
"timestamp": "2026-03-22T12:00:00Z"
}
}
400 Bad Request - Invalid Step Type
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Subsession collection step step_1 not found"
},
"meta": {
"request_id": "req_123",
"timestamp": "2026-03-22T12:00:00Z"
}
}
404 Not Found - Subsession Not Found
{
"status": "error",
"error": {
"code": "NOT_FOUND",
"message": "Subsession not found"
},
"meta": {
"request_id": "req_123",
"timestamp": "2026-03-22T12:00:00Z"
}
}
JavaScript Integration Example - Subsessions
const accessToken = "770e8400-e29b-41d4-a716-446655440002";
const baseUrl = "https://kyc.legaltalent.ai/public/sessions";
// Create a subsession for a UBO
async function createSubsession(stepId, label, metadata = {}) {
const response = await fetch(`${baseUrl}/${accessToken}/subsessions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
step_id: stepId,
label: label,
metadata: metadata
})
});
const data = await response.json();
if (data.status === 'success') {
console.log("Subsession link:", data.data.access_link);
return data.data;
} else {
throw new Error(data.error);
}
}
// List all subsessions for the current session
async function listSubsessions(stepId = null) {
const query = stepId ? `?step_id=${encodeURIComponent(stepId)}` : "";
const response = await fetch(`${baseUrl}/${accessToken}/subsessions${query}`);
const data = await response.json();
return data.data;
}
// Regenerate subsession link
async function regenerateSubsessionLink(subsessionId) {
const response = await fetch(
`${baseUrl}/${accessToken}/subsessions/${subsessionId}/regenerate`,
{ method: "POST" }
);
const data = await response.json();
return data.data;
}
// Example: Create subsessions for UBOs
async function handleUBOStep(stepId) {
const ubos = [
{ name: "Juan Garcia", ownership: 25 },
{ name: "Maria Lopez", ownership: 35 },
{ name: "Carlos Ruiz", ownership: 40 }
];
const subsessionLinks = [];
for (const ubo of ubos) {
const subsession = await createSubsession(
stepId,
`${ubo.name} - UBO`,
{ ownership_percentage: ubo.ownership }
);
subsessionLinks.push({
name: ubo.name,
link: subsession.access_link
});
console.log(`Created subsession for ${ubo.name}: ${subsession.access_link}`);
}
// Return links to share with UBOs
return subsessionLinks;
}
Best Practices
- Security: Access tokens are UUIDs - share them securely with end users
- Expiration: Check
expires_atbefore allowing user actions - Error Handling: Handle expired sessions gracefully with user-friendly messages
- File Validation: Validate file types and sizes client-side before upload
- File Uploads: Use direct S3 uploads (
/upload/init→ S3PUT→/upload/confirm) for new integrations - Legacy Uploads: Use base64
/uploadonly for old clients that cannot upload directly to S3 - Progress Tracking: Use
current_step_indexandtotal_stepsto show progress - Step Validation: The API validates required fields - handle validation errors in UI
- Correction Mode: When status is
awaiting_client_correction, show each step’s opencorrection_requestsand allow edits only for those steps - Subsession Management: For subsession_collection steps, check
max_allowedbefore creating new ones - Subsession Completion: If
require_completion_before_proceedis enabled on the step, all subsessions must be completed before the parent session can proceed