Skip to main content
POST
/
kyc
/
cua-scan
CUA Website Scan
curl --request POST \
  --url https://api.example.com/kyc/cua-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
CUA scans are asynchronous. Submit a job, then poll for status and results.

Endpoints

EndpointMethodDescription
/kyc/cua-scanPOSTSubmit a new scan job
/kyc/cua-scanGETList jobs with pagination
/kyc/cua-scan/{jobId}GETGet job status and results

Authentication

EndpointRequired Permission
Submit Jobcua:create
List Jobscua:read
Get Statuscua:read

Submit Scan Job

Create a new CUA scan job to analyze a website.
POST /kyc/cua-scan

Request Parameters

Effort Levels

LevelStepsUse Case
low20Quick surface-level scan
medium50Standard comprehensive scan (default)
high80Deep analysis for complex sites

Request Example

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

{
  "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

FieldTypeDescription
job_idstringUnique job identifier (UUID)
statusstringInitial status: PENDING
target_urlstringURL being scanned
effortstringEffort level selected
created_atstringISO 8601 timestamp
messagestringConfirmation message

List Jobs

Get a paginated list of scan jobs for your tenant.
GET /kyc/cua-scan

Query Parameters

ParameterTypeDefaultDescription
limitinteger20Number of items per page (max 100)
cursorstring-Pagination cursor from previous response
statusstring-Filter by status: PENDING, PROCESSING, COMPLETED, FAILED
sortstringdescSort order: desc (newest first) or asc (oldest first)

Request Example

# 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

{
  "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

FieldTypeDescription
itemsarrayList of job summaries
countintegerNumber of items in this response
cursorstringPagination cursor for next page (null if no more)
has_morebooleanWhether more pages exist

Get Job Status

Get detailed status, progress, and results of a scan job.
GET /kyc/cua-scan/{jobId}

Path Parameters

ParameterTypeDescription
jobIdstringJob ID (UUID)

Query Parameters

ParameterTypeDefaultDescription
from_stepinteger0Return only steps with sequence_id >= from_step (for polling)

Request Example

# 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

{
  "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

{
  "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

FieldTypeDescription
job_idstringJob ID
target_urlstringURL being scanned
statusstringCurrent status
effortstringEffort level
created_atstringJob creation timestamp
updated_atstringLast update timestamp
current_stepintegerCurrent agent step (1 to max_steps)
total_sequencesintegerTotal content blocks generated
last_sequence_idintegerID of last sequence (for pagination)
from_sequenceintegerRequested from_step value
stepsarrayContent blocks (text, tool_use, tool_result)
resultsobjectResult files (only when COMPLETED)
error_messagestringError details (only when FAILED)

Step Types

TypeDescription
textAgent’s reasoning/analysis text
tool_useTool invocation (screenshot, click, type, etc.)
tool_resultResult of tool execution
thinkingInternal reasoning (when available)

Job Statuses

StatusDescription
PENDINGJob submitted, waiting in queue
PROCESSINGAgent is actively scanning the website
COMPLETEDScan finished successfully, results available
FAILEDScan failed (see error_message for details)

Error Responses

400 Bad Request - Missing URL

{
  "error": "Missing required field: url"
}

400 Bad Request - Invalid URL

{
  "error": "URL must start with http:// or https://"
}

400 Bad Request - Invalid Effort

{
  "error": "Invalid effort level: extreme. Valid options are: low, medium, high"
}

403 Forbidden - Permission Denied

{
  "error": "Permission denied",
  "message": "Required permission: cua:create"
}

404 Not Found

{
  "error": "Job not found"
}

Usage Examples

Python - Complete Workflow

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

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

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