> ## Documentation Index
> Fetch the complete documentation index at: https://docs.compliance.legaltalent.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Public Sessions

Public endpoints for end users to access and interact with KYC onboarding sessions. These endpoints require no authentication - access is controlled via secure access tokens.

## 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

These endpoints are designed for frontend integration and require no authentication - access is controlled via the access token provided when creating the session.

## 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 the `access_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

```bash theme={null}
curl https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002
```

### Response Format

```json theme={null}
{
  "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"
    }
  }
}
```

`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

```bash theme={null}
curl https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/workflow
```

### Response Format

```json theme={null}
{
  "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"
  }
}
```

Each step can also include optional display and behavior fields such as `description`, `instructions`, `tooltip`, `document_types`, `conditional_logic`, and `subsession_config` when configured on the workflow.

## 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:

1. Call `/upload/init` with file metadata and the workflow step context.
2. Upload the raw file bytes to the returned `upload_url` using HTTP `PUT`.
3. Call `/upload/confirm` so the API registers the uploaded S3 object in the session.

Use the legacy base64 upload only if you are maintaining an older integration that cannot upload directly to S3.

### Initialize Upload

#### Endpoint

```
POST /public/sessions/{access_token}/upload/init
```

#### Request Body Parameters

<ParamFields>
  <ParamField name="step_id" type="string" description="ID of the workflow step this document belongs to. Omit to upload an additional document outside the workflow." />

  <ParamField name="document_type" type="string" description="Document type. Required when `step_id` is provided." />

  <ParamField name="file_name" type="string" required description="Original file name" />

  <ParamField name="mime_type" type="string" description="MIME type. Defaults to `application/octet-stream` if omitted." />
</ParamFields>

#### Request Example

```bash theme={null}
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

```json theme={null}
{
  "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 to `upload_url` with an HTTP `PUT`. Use the same `Content-Type` passed to `/upload/init`.

#### Request Example

```bash theme={null}
curl -X PUT "<upload_url>" \
  -H "Content-Type: application/pdf" \
  --data-binary "@passport.pdf"
```

Do not send this request to the KYC API host. `upload_url` is a presigned S3 URL returned by `/upload/init`.

### Confirm Upload

#### Endpoint

```
POST /public/sessions/{access_token}/upload/confirm
```

#### Request Body Parameters

<ParamFields>
  <ParamField name="doc_id" type="string" required description="Document ID returned by `/upload/init`" />

  <ParamField name="s3_key" type="string" required description="S3 key returned by `/upload/init`" />

  <ParamField name="step_id" type="string" description="ID of the workflow step this document belongs to. Omit for additional documents." />

  <ParamField name="document_type" type="string" description="Document type. Required when `step_id` is provided." />

  <ParamField name="file_name" type="string" required description="Original file name" />

  <ParamField name="mime_type" type="string" description="MIME type. Defaults to `application/octet-stream` if omitted." />
</ParamFields>

#### Request Example

```bash theme={null}
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

```json theme={null}
{
  "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
  }
}
```

After confirmation, use `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

<ParamFields>
  <ParamField name="step_id" type="string" description="ID of the workflow step this document belongs to. Omit to upload an additional document outside the workflow." />

  <ParamField name="document_type" type="string" description="Document type: 'id', 'proof_of_address', 'selfie', 'articles_of_incorporation', etc. Required when `step_id` is provided." />

  <ParamField name="file_data" type="string" description="Base64-encoded file data. Either `file_data` or `file_content` is required." />

  <ParamField name="file_content" type="string" description="Backward-compatible alias for `file_data`." />

  <ParamField name="file_name" type="string" required description="Original file name" />

  <ParamField name="mime_type" type="string" description="MIME type. Defaults to `application/octet-stream` if omitted." />
</ParamFields>

### Request Example

```bash theme={null}
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

```json theme={null}
{
  "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

<ParamFields>
  <ParamField name="doc_id" type="string" required description="ID of the uploaded document" />

  <ParamField name="document_type" type="string" required description="Document type hint for better extraction" />
</ParamFields>

### Request Example

```bash theme={null}
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

```json theme={null}
{
  "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

<ParamFields>
  <ParamField path="access_token" type="string" required description="The access token from the session" />

  <ParamField path="step_id" type="string" required description="The step ID to complete" />
</ParamFields>

### Request Body Parameters

<ParamFields>
  <ParamField name="form_data" type="object" description="Form data for form_fill steps (field_id: value pairs)" />
</ParamFields>

### Request Example - Document Step

```bash theme={null}
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

```bash theme={null}
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

```json theme={null}
{
  "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 becomes `awaiting_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

```json theme={null}
{
  "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:

```bash theme={null}
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"
    }
  }'
```

After a corrected step is completed, its open correction requests are marked `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 returned `liveness_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

<ParamFields>
  <ParamField name="step_id" type="string" required description="ID of the liveness step in the workflow" />
</ParamFields>

#### Request Example

```bash theme={null}
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

```json theme={null}
{
  "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

<ParamFields>
  <ParamField name="liveness_session_id" type="string" required description="Liveness session ID returned by `/liveness/start`" />
</ParamFields>

#### Request Example

```bash theme={null}
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

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```javascript theme={null}
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 a `subsession_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

<ParamFields>
  <ParamField name="step_id" type="string" required description="ID of the subsession_collection step in the workflow" />

  <ParamField name="label" type="string" description="Human-readable label for this subsession (e.g., 'Juan Garcia - UBO')" />

  <ParamField name="metadata" type="object" description="Additional metadata for the subsession" />
</ParamFields>

#### Request Example

```bash theme={null}
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

```json theme={null}
{
  "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

```bash theme={null}
curl https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/subsessions
```

#### Response Format

```json theme={null}
{
  "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"
  }
}
```

Add `?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

```bash theme={null}
curl -X POST https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/subsessions/880e8400-e29b-41d4-a716-446655440003/regenerate
```

#### Response Format

```json theme={null}
{
  "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

```bash theme={null}
curl -X DELETE https://kyc.legaltalent.ai/public/sessions/770e8400-e29b-41d4-a716-446655440002/subsessions/880e8400-e29b-41d4-a716-446655440003
```

#### Response Format

```json theme={null}
{
  "status": "success",
  "data": {
    "subsession_id": "880e8400-e29b-41d4-a716-446655440003",
    "deleted": true
  }
}
```

### Subsession Errors

#### 400 Bad Request - Maximum Subsessions Reached

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```json theme={null}
{
  "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

```javascript theme={null}
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_at` before 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` → S3 `PUT` → `/upload/confirm`) for new integrations
* **Legacy Uploads**: Use base64 `/upload` only for old clients that cannot upload directly to S3
* **Progress Tracking**: Use `current_step_index` and `total_steps` to 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 open `correction_requests` and allow edits only for those steps
* **Subsession Management**: For subsession\_collection steps, check `max_allowed` before creating new ones
* **Subsession Completion**: If `require_completion_before_proceed` is enabled on the step, all subsessions must be completed before the parent session can proceed
