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

# CUA Website Scan

AI-powered website analysis using Computer Use Agent (CUA) technology. The agent autonomously navigates and analyzes websites to identify potential fraud, scams, compliance issues, and suspicious content.

## Overview

The CUA Scan API uses Claude's Computer Use capabilities to:

* **Navigate websites** like a human user
* **Analyze content** for fraud indicators, scams, and compliance issues
* **Capture screenshots** at each step for audit trails
* **Generate detailed reports** with findings and recommendations

<Note>
  CUA scans are asynchronous. Submit a job, then poll for status and results.
</Note>

## Endpoints

| Endpoint                | Method | Description                |
| ----------------------- | ------ | -------------------------- |
| `/kyc/cua-scan`         | POST   | Submit a new scan job      |
| `/kyc/cua-scan`         | GET    | List jobs with pagination  |
| `/kyc/cua-scan/{jobId}` | GET    | Get job status and results |

## Authentication

| Endpoint   | Required Permission |
| ---------- | ------------------- |
| Submit Job | `cua:create`        |
| List Jobs  | `cua:read`          |
| Get Status | `cua:read`          |

***

## Submit Scan Job

Create a new CUA scan job to analyze a website.

```
POST /kyc/cua-scan
```

### Request Parameters

<ParamFields>
  <ParamField name="url" type="string" required description="Target URL to scan (must start with http:// or https://)" />

  <ParamField name="effort" type="string" description="Analysis depth: 'low' (20 steps), 'medium' (50 steps, default), or 'high' (80 steps)" />
</ParamFields>

### Effort Levels

| Level    | Steps | Use Case                              |
| -------- | ----- | ------------------------------------- |
| `low`    | 20    | Quick surface-level scan              |
| `medium` | 50    | Standard comprehensive scan (default) |
| `high`   | 80    | Deep analysis for complex sites       |

### Request Example

```bash theme={null}
curl -X POST https://stg.kyc.legaltalent.ai/kyc/cua-scan \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://suspicious-shop.com",
    "effort": "medium"
  }'
```

### Response

```json theme={null}
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "PENDING",
  "target_url": "https://suspicious-shop.com",
  "effort": "medium",
  "created_at": "2024-12-15T10:30:00.000000+00:00",
  "message": "Job submitted successfully"
}
```

### Response Fields

| Field        | Type   | Description                  |
| ------------ | ------ | ---------------------------- |
| `job_id`     | string | Unique job identifier (UUID) |
| `status`     | string | Initial status: `PENDING`    |
| `target_url` | string | URL being scanned            |
| `effort`     | string | Effort level selected        |
| `created_at` | string | ISO 8601 timestamp           |
| `message`    | string | Confirmation message         |

***

## List Jobs

Get a paginated list of scan jobs for your tenant.

```
GET /kyc/cua-scan
```

### Query Parameters

| Parameter | Type    | Default | Description                                                      |
| --------- | ------- | ------- | ---------------------------------------------------------------- |
| `limit`   | integer | 20      | Number of items per page (max 100)                               |
| `cursor`  | string  | -       | Pagination cursor from previous response                         |
| `status`  | string  | -       | Filter by status: `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED` |
| `sort`    | string  | `desc`  | Sort order: `desc` (newest first) or `asc` (oldest first)        |

### Request Example

```bash theme={null}
# List recent jobs
curl -X GET "https://stg.kyc.legaltalent.ai/kyc/cua-scan?limit=10&sort=desc" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Filter by status
curl -X GET "https://stg.kyc.legaltalent.ai/kyc/cua-scan?status=COMPLETED&limit=20" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Paginate through results
curl -X GET "https://stg.kyc.legaltalent.ai/kyc/cua-scan?cursor=eyJqb2JfaWQiOiAiLi4uIn0=" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Response

```json theme={null}
{
  "items": [
    {
      "job_id": "550e8400-e29b-41d4-a716-446655440000",
      "target_url": "https://suspicious-shop.com",
      "status": "COMPLETED",
      "effort": "medium",
      "created_at": "2024-12-15T10:30:00.000000+00:00",
      "updated_at": "2024-12-15T10:35:00.000000+00:00"
    },
    {
      "job_id": "660e8400-e29b-41d4-a716-446655440001",
      "target_url": "https://another-site.com",
      "status": "PROCESSING",
      "effort": "high",
      "created_at": "2024-12-15T10:32:00.000000+00:00",
      "updated_at": "2024-12-15T10:33:00.000000+00:00"
    }
  ],
  "count": 2,
  "cursor": "eyJqb2JfaWQiOiAiNjYwZTg0MDAuLi4ifQ==",
  "has_more": true
}
```

### Response Fields

| Field      | Type    | Description                                       |
| ---------- | ------- | ------------------------------------------------- |
| `items`    | array   | List of job summaries                             |
| `count`    | integer | Number of items in this response                  |
| `cursor`   | string  | Pagination cursor for next page (null if no more) |
| `has_more` | boolean | Whether more pages exist                          |

***

## Get Job Status

Get detailed status, progress, and results of a scan job.

```
GET /kyc/cua-scan/{jobId}
```

### Path Parameters

| Parameter | Type   | Description   |
| --------- | ------ | ------------- |
| `jobId`   | string | Job ID (UUID) |

### Query Parameters

| Parameter   | Type    | Default | Description                                                     |
| ----------- | ------- | ------- | --------------------------------------------------------------- |
| `from_step` | integer | 0       | Return only steps with `sequence_id >= from_step` (for polling) |

### Request Example

```bash theme={null}
# Get full status
curl -X GET "https://stg.kyc.legaltalent.ai/kyc/cua-scan/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Poll for new steps (efficient for real-time updates)
curl -X GET "https://stg.kyc.legaltalent.ai/kyc/cua-scan/550e8400-e29b-41d4-a716-446655440000?from_step=15" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Response - Processing

```json theme={null}
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "target_url": "https://suspicious-shop.com",
  "status": "PROCESSING",
  "effort": "medium",
  "created_at": "2024-12-15T10:30:00.000000+00:00",
  "updated_at": "2024-12-15T10:31:30.000000+00:00",
  "current_step": 5,
  "total_sequences": 12,
  "last_sequence_id": 12,
  "from_sequence": 0,
  "steps": [
    {
      "sequence_id": 1,
      "agent_step": 1,
      "type": "text",
      "content": "I'll analyze this website for potential fraud indicators..."
    },
    {
      "sequence_id": 2,
      "agent_step": 1,
      "type": "tool_use",
      "tool": "computer",
      "action": "screenshot"
    },
    {
      "sequence_id": 3,
      "agent_step": 1,
      "type": "tool_result",
      "success": true,
      "screenshot_url": "https://bucket.s3.amazonaws.com/screenshots/step1.png?..."
    },
    {
      "sequence_id": 4,
      "agent_step": 2,
      "type": "text",
      "content": "The homepage shows several red flags: no company address..."
    }
  ]
}
```

### Response - Completed

```json theme={null}
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "target_url": "https://suspicious-shop.com",
  "status": "COMPLETED",
  "effort": "medium",
  "created_at": "2024-12-15T10:30:00.000000+00:00",
  "updated_at": "2024-12-15T10:35:00.000000+00:00",
  "current_step": 50,
  "total_sequences": 145,
  "last_sequence_id": 145,
  "from_sequence": 0,
  "steps": [...],
  "results": {
    "files_count": 3,
    "files": [
      {
        "file_name": "report.json",
        "file_key": "results/550e8400.../report.json",
        "size": 15234,
        "last_modified": "2024-12-15T10:35:00.000000+00:00",
        "download_url": "https://bucket.s3.amazonaws.com/results/550e8400.../report.json?..."
      },
      {
        "file_name": "summary.md",
        "file_key": "results/550e8400.../summary.md",
        "size": 4521,
        "last_modified": "2024-12-15T10:35:00.000000+00:00",
        "download_url": "https://bucket.s3.amazonaws.com/results/550e8400.../summary.md?..."
      },
      {
        "file_name": "screenshots.zip",
        "file_key": "results/550e8400.../screenshots.zip",
        "size": 2548000,
        "last_modified": "2024-12-15T10:35:00.000000+00:00",
        "download_url": "https://bucket.s3.amazonaws.com/results/550e8400.../screenshots.zip?..."
      }
    ],
    "s3_location": "s3://bucket-name/results/550e8400.../"
  }
}
```

### Response Fields

| Field              | Type    | Description                                    |
| ------------------ | ------- | ---------------------------------------------- |
| `job_id`           | string  | Job ID                                         |
| `target_url`       | string  | URL being scanned                              |
| `status`           | string  | Current status                                 |
| `effort`           | string  | Effort level                                   |
| `created_at`       | string  | Job creation timestamp                         |
| `updated_at`       | string  | Last update timestamp                          |
| `current_step`     | integer | Current agent step (1 to max\_steps)           |
| `total_sequences`  | integer | Total content blocks generated                 |
| `last_sequence_id` | integer | ID of last sequence (for pagination)           |
| `from_sequence`    | integer | Requested from\_step value                     |
| `steps`            | array   | Content blocks (text, tool\_use, tool\_result) |
| `results`          | object  | Result files (only when COMPLETED)             |
| `error_message`    | string  | Error details (only when FAILED)               |

### Step Types

| Type          | Description                                     |
| ------------- | ----------------------------------------------- |
| `text`        | Agent's reasoning/analysis text                 |
| `tool_use`    | Tool invocation (screenshot, click, type, etc.) |
| `tool_result` | Result of tool execution                        |
| `thinking`    | Internal reasoning (when available)             |

***

## Job Statuses

| Status       | Description                                   |
| ------------ | --------------------------------------------- |
| `PENDING`    | Job submitted, waiting in queue               |
| `PROCESSING` | Agent is actively scanning the website        |
| `COMPLETED`  | Scan finished successfully, results available |
| `FAILED`     | Scan failed (see `error_message` for details) |

***

## Error Responses

### 400 Bad Request - Missing URL

```json theme={null}
{
  "error": "Missing required field: url"
}
```

### 400 Bad Request - Invalid URL

```json theme={null}
{
  "error": "URL must start with http:// or https://"
}
```

### 400 Bad Request - Invalid Effort

```json theme={null}
{
  "error": "Invalid effort level: extreme. Valid options are: low, medium, high"
}
```

### 403 Forbidden - Permission Denied

```json theme={null}
{
  "error": "Permission denied",
  "message": "Required permission: cua:create"
}
```

### 404 Not Found

```json theme={null}
{
  "error": "Job not found"
}
```

***

## Usage Examples

### Python - Complete Workflow

```python theme={null}
import requests
import time

BASE_URL = "https://stg.kyc.legaltalent.ai"
TOKEN = "YOUR_TOKEN"

headers = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json"
}

def submit_scan(url: str, effort: str = "medium") -> dict:
    """Submit a new CUA scan job."""
    response = requests.post(
        f"{BASE_URL}/kyc/cua-scan",
        headers=headers,
        json={"url": url, "effort": effort}
    )
    response.raise_for_status()
    return response.json()

def get_status(job_id: str, from_step: int = 0) -> dict:
    """Get job status with optional pagination."""
    params = {"from_step": from_step} if from_step > 0 else {}
    response = requests.get(
        f"{BASE_URL}/kyc/cua-scan/{job_id}",
        headers=headers,
        params=params
    )
    response.raise_for_status()
    return response.json()

def poll_until_complete(job_id: str, interval: int = 5) -> dict:
    """Poll job status until completed or failed."""
    last_sequence = 0
    
    while True:
        status = get_status(job_id, from_step=last_sequence)
        
        print(f"Status: {status['status']}, Step: {status['current_step']}")
        
        # Print new steps
        for step in status.get('steps', []):
            if step['type'] == 'text':
                print(f"  [{step['agent_step']}] {step['content'][:100]}...")
        
        if status['status'] == 'COMPLETED':
            print(f"\n✅ Scan completed! {status['results']['files_count']} result files available.")
            return status
        elif status['status'] == 'FAILED':
            print(f"\n❌ Scan failed: {status.get('error_message', 'Unknown error')}")
            return status
        
        # Update pagination cursor
        last_sequence = status.get('last_sequence_id', 0) + 1
        
        time.sleep(interval)

# Example usage
if __name__ == "__main__":
    # Submit scan
    job = submit_scan("https://suspicious-shop.com", effort="medium")
    print(f"Job submitted: {job['job_id']}")
    
    # Poll until complete
    result = poll_until_complete(job['job_id'])
    
    # Download results
    if result['status'] == 'COMPLETED':
        for file in result['results']['files']:
            print(f"Download: {file['file_name']} -> {file['download_url']}")
```

### JavaScript - Real-time Updates

```javascript theme={null}
const BASE_URL = 'https://stg.kyc.legaltalent.ai';
const TOKEN = 'YOUR_TOKEN';

const headers = {
  'Authorization': `Bearer ${TOKEN}`,
  'Content-Type': 'application/json'
};

async function submitScan(url, effort = 'medium') {
  const response = await fetch(`${BASE_URL}/kyc/cua-scan`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ url, effort })
  });
  return response.json();
}

async function getStatus(jobId, fromStep = 0) {
  const url = fromStep > 0 
    ? `${BASE_URL}/kyc/cua-scan/${jobId}?from_step=${fromStep}`
    : `${BASE_URL}/kyc/cua-scan/${jobId}`;
  
  const response = await fetch(url, { headers });
  return response.json();
}

async function pollWithCallback(jobId, onUpdate, interval = 3000) {
  let lastSequence = 0;
  
  const poll = async () => {
    const status = await getStatus(jobId, lastSequence);
    
    // Notify callback with new data
    onUpdate(status);
    
    if (status.status === 'COMPLETED' || status.status === 'FAILED') {
      return status;
    }
    
    // Update cursor for next poll
    lastSequence = (status.last_sequence_id || 0) + 1;
    
    // Schedule next poll
    await new Promise(resolve => setTimeout(resolve, interval));
    return poll();
  };
  
  return poll();
}

// Example: Real-time UI updates
async function scanWithLiveUpdates(url) {
  const job = await submitScan(url, 'medium');
  console.log(`Job started: ${job.job_id}`);
  
  const result = await pollWithCallback(job.job_id, (status) => {
    console.log(`[${status.status}] Step ${status.current_step}`);
    
    // Update UI with new steps
    status.steps?.forEach(step => {
      if (step.type === 'text') {
        console.log(`  Agent: ${step.content.slice(0, 80)}...`);
      } else if (step.type === 'tool_result' && step.screenshot_url) {
        console.log(`  📸 Screenshot: ${step.screenshot_url}`);
      }
    });
  });
  
  if (result.status === 'COMPLETED') {
    console.log('\n✅ Scan complete!');
    console.log('Results:', result.results.files.map(f => f.file_name));
  }
  
  return result;
}
```

### List Jobs with Filtering

```python theme={null}
def list_jobs(status=None, limit=20, sort='desc'):
    """List scan jobs with optional filtering."""
    params = {'limit': limit, 'sort': sort}
    if status:
        params['status'] = status
    
    response = requests.get(
        f"{BASE_URL}/kyc/cua-scan",
        headers=headers,
        params=params
    )
    return response.json()

def list_all_jobs(status=None):
    """Iterate through all jobs using pagination."""
    cursor = None
    all_jobs = []
    
    while True:
        params = {'limit': 100}
        if status:
            params['status'] = status
        if cursor:
            params['cursor'] = cursor
        
        response = requests.get(
            f"{BASE_URL}/kyc/cua-scan",
            headers=headers,
            params=params
        )
        data = response.json()
        
        all_jobs.extend(data['items'])
        
        if not data['has_more']:
            break
        
        cursor = data['cursor']
    
    return all_jobs

# Get all completed jobs
completed_jobs = list_all_jobs(status='COMPLETED')
print(f"Total completed scans: {len(completed_jobs)}")
```

***

## Best Practices

### Choosing Effort Level

* **Low (20 steps)**: Quick checks, simple landing pages
* **Medium (50 steps)**: Standard analysis, e-commerce sites
* **High (80 steps)**: Deep investigation, complex sites with multiple pages

### Efficient Polling

* Use `from_step` parameter to only fetch new content
* Poll every 3-5 seconds during active processing
* Stop polling when status is `COMPLETED` or `FAILED`

### Rate Limiting

* Maximum 2 concurrent jobs per tenant
* Maximum 10 concurrent jobs globally
* Jobs exceeding limits are queued automatically

### Result Files

* **report.json**: Structured analysis data
* **summary.md**: Human-readable summary
* **screenshots.zip**: All captured screenshots
* Download URLs expire after 1 hour

### Error Handling

* Implement retry logic for transient failures
* Check `error_message` field when status is `FAILED`
* Store `job_id` for later retrieval of results
