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

# Web Validation

Comprehensive website validation and risk assessment. Analyzes domains for legitimacy, security, content quality, sanctions, adverse media, and policy compliance.

## Endpoint

```
POST /kyc/web-validation
```

## Authentication

Requires `web-validation:create` permission. Include your Bearer token in the Authorization header.

## Description

The web validation endpoint performs comprehensive analysis of websites including:

1. **URL & Domain Analysis**: Validates URL structure, redirects, and domain registration (WHOIS)
2. **SSL/TLS Verification**: Checks certificate validity, expiration, and security
3. **Industry Classification**: AI-powered industry detection and risk classification
4. **Name/Industry Matching**: Compares declared business info against website content
5. **Sanction List Check**: Screens domain against global sanctions lists
6. **Adverse Media Analysis**: Searches for negative news and fraud reports
7. **Social Media Validation**: Verifies social media links are functional
8. **Tranco Rank Check**: Evaluates website popularity/reputation
9. **Policy Compliance**: Analyzes Terms, Return, Refund, and AML policies

## Request Body Parameters

<ParamFields>
  <ParamField name="url" type="string" required description="The website URL to validate (must include http:// or https://)" />

  <ParamField name="client_id" type="string" description="External client identifier provided by your system. Links this validation to a customer record — if the customer doesn't exist yet, one is created automatically. Use the same client_id across web validations, adverse media checks, and sessions to see all results for a customer in one place." />

  <ParamField name="declared_name" type="string" description="Declared business name to verify against website content" />

  <ParamField name="declared_industry" type="string" description="Declared industry to verify against detected industry" />

  <ParamField name="merchant_id" type="integer" description="Merchant ID for tracking (optional)" />

  <ParamField name="weights" type="array" description="Custom weights for reliability score calculation (must sum to 1.0)" />

  <ParamField name="enable_js_render" type="boolean" description="Enable JavaScript rendering for dynamic websites (default: false)" />

  <ParamField name="include_adverse_media" type="boolean" description="Include adverse media analysis (default: from tenant config)" />

  <ParamField name="include_sanction_check" type="boolean" description="Include sanction list screening (default: true)" />

  <ParamField name="include_social_links" type="boolean" description="Validate social media links (default: true)" />

  <ParamField name="include_tranco_rank" type="boolean" description="Include Tranco popularity ranking (default: true)" />

  <ParamField name="include_policies" type="boolean" description="Analyze website policies (Terms, Return, Refund, AML) (default: false)" />

  <ParamField name="policies_use_proxy" type="boolean" description="Use proxy for policy fetching (default: false)" />

  <ParamField name="policy_types" type="array" description="Specific policies to check: 'terms', 'return', 'refund', 'aml'" />
</ParamFields>

## Request Examples

### Basic Validation

```bash theme={null}
curl -X POST https://stg.kyc.legaltalent.ai/kyc/web-validation \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com"
  }'
```

### Validation Linked to a Customer

```bash theme={null}
curl -X POST https://stg.kyc.legaltalent.ai/kyc/web-validation \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example-shop.com",
    "client_id": "your-external-id-123"
  }'
```

When `client_id` is provided, the response includes a `customer_id` field. If no customer with that `client_id` exists, one is auto-created. Use the same `client_id` across services (web validation, adverse media, sessions) to aggregate all results for one customer.

### Full Validation with Business Details

```bash theme={null}
curl -X POST https://stg.kyc.legaltalent.ai/kyc/web-validation \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example-shop.com",
    "client_id": "merchant-456",
    "declared_name": "Example Shop Inc",
    "declared_industry": "E-commerce",
    "include_adverse_media": true,
    "include_sanction_check": true,
    "include_social_links": true,
    "include_tranco_rank": true,
    "include_policies": true,
    "policy_types": ["terms", "return", "refund"]
  }'
```

### With JavaScript Rendering

```bash theme={null}
curl -X POST https://stg.kyc.legaltalent.ai/kyc/web-validation \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://spa-website.com",
    "enable_js_render": true
  }'
```

## Response Format

### Success Response

```json theme={null}
{
  "customer_id": "38ff436f-851e-4f9a-89fd-6eb19a02ba75",
  "result": {
    "reliability_score": 78.5,
    "validation_results": {
      "url_checker": true,
      "code_response": 200,
      "last_page_url": "https://example.com/",
      "social_media": false,
      "social_media_allowed": true,
      
      "ssl_checker": {
        "has_ssl": true,
        "is_valid": true,
        "issuer": "Let's Encrypt",
        "expiry_date": "2025-03-15",
        "days_until_expiry": 90
      },
      
      "whois_info": {
        "domain": "example.com",
        "registrar": "GoDaddy",
        "creation_date": "2010-01-15",
        "expiration_date": "2026-01-15",
        "domain_age_days": 5478,
        "registrant_country": "US"
      },
      
      "classifier": {
        "industry_name": "E-commerce",
        "industry_id": 12,
        "allowed_industry": true,
        "flagged_industry": false,
        "criterion": "Online retail store selling electronics"
      },
      
      "kyc_matches": {
        "declared_name_match": true,
        "declared_industry_match": true,
        "name_confidence": 0.92,
        "industry_confidence": 0.88
      },
      
      "page_status_classifier": {
        "page_status": "AVAILABLE",
        "has_content": true
      },
      
      "sanction_check": {
        "has_matches": false,
        "matches": [],
        "checked_lists": ["OFAC", "EU", "UN"]
      },
      
      "adverse_media": {
        "has_adverse_media": false,
        "risk_score": 15,
        "decision": "CLEAR",
        "sources": []
      },
      
      "social_media_links": {
        "social_links_found": true,
        "valid_count": 3,
        "invalid_count": 0,
        "links": [
          {"platform": "facebook", "url": "https://facebook.com/example", "valid": true},
          {"platform": "twitter", "url": "https://twitter.com/example", "valid": true},
          {"platform": "instagram", "url": "https://instagram.com/example", "valid": true}
        ]
      },
      
      "tranco_rank": {
        "rank": 45000,
        "in_top_million": true,
        "band": "top_100k",
        "score": 0.8
      },
      
      "policies": {
        "terms": {
          "status": "policy_analyzed",
          "url": "https://example.com/terms",
          "generic_format": false,
          "includes_is_of_age": true,
          "has_applicable_law": true,
          "dos_clause": true,
          "termination_clause": true,
          "privacy_policy": true
        },
        "return": {
          "status": "policy_analyzed",
          "url": "https://example.com/return-policy",
          "generic_format": false
        },
        "refund": {
          "status": "policy_analyzed",
          "url": "https://example.com/refund-policy",
          "generic_format": false
        },
        "execution_time_ms": 3200
      },
      
      "total_cost": 0.0023,
      "concurrent_time_taken_ms": 4500
    }
  }
}
```

## Response Fields

### Top Level

| Field                       | Type   | Description                                                                        |
| --------------------------- | ------ | ---------------------------------------------------------------------------------- |
| `customer_id`               | string | Internal customer UUID. Only present when `client_id` was provided in the request. |
| `result.reliability_score`  | number | Overall reliability score (0-100)                                                  |
| `result.validation_results` | object | Detailed results from all validators                                               |

### URL & Basic Checks

| Field                  | Type    | Description                           |
| ---------------------- | ------- | ------------------------------------- |
| `url_checker`          | boolean | Whether URL is valid and accessible   |
| `code_response`        | integer | HTTP response code                    |
| `last_page_url`        | string  | Final URL after redirects             |
| `social_media`         | boolean | Whether URL is a social media site    |
| `social_media_allowed` | boolean | Whether social media URLs are allowed |

### SSL Certificate

| Field               | Type    | Description                    |
| ------------------- | ------- | ------------------------------ |
| `has_ssl`           | boolean | Whether site has SSL           |
| `is_valid`          | boolean | Whether certificate is valid   |
| `issuer`            | string  | Certificate issuer             |
| `expiry_date`       | string  | Certificate expiration date    |
| `days_until_expiry` | integer | Days until certificate expires |

### WHOIS Information

| Field                | Type    | Description            |
| -------------------- | ------- | ---------------------- |
| `domain`             | string  | Domain name            |
| `registrar`          | string  | Domain registrar       |
| `creation_date`      | string  | Domain creation date   |
| `expiration_date`    | string  | Domain expiration date |
| `domain_age_days`    | integer | Age of domain in days  |
| `registrant_country` | string  | Country of registrant  |

### Industry Classification

| Field              | Type    | Description                                                                                         |
| ------------------ | ------- | --------------------------------------------------------------------------------------------------- |
| `industry_name`    | string  | Detected industry name                                                                              |
| `industry_id`      | integer | Industry identifier                                                                                 |
| `allowed_industry` | boolean | Whether industry is allowed                                                                         |
| `flagged_industry` | boolean | Whether industry is flagged for extra scrutiny (only `true` when `allowed_industry` is also `true`) |
| `criterion`        | string  | Classification reasoning                                                                            |

### KYC Matches

| Field                     | Type    | Description                       |
| ------------------------- | ------- | --------------------------------- |
| `declared_name_match`     | boolean | Whether declared name matches     |
| `declared_industry_match` | boolean | Whether declared industry matches |
| `name_confidence`         | number  | Name match confidence (0-1)       |
| `industry_confidence`     | number  | Industry match confidence (0-1)   |

### Sanction Check

| Field           | Type    | Description                     |
| --------------- | ------- | ------------------------------- |
| `has_matches`   | boolean | Whether sanctions matches found |
| `matches`       | array   | List of sanction matches        |
| `checked_lists` | array   | Lists that were checked         |

### Adverse Media

| Field               | Type    | Description                                |
| ------------------- | ------- | ------------------------------------------ |
| `has_adverse_media` | boolean | Whether adverse media found                |
| `risk_score`        | integer | Risk score (0-100)                         |
| `decision`          | string  | CLEAR, LOW\_RISK, MEDIUM\_RISK, HIGH\_RISK |
| `sources`           | array   | Adverse media sources found                |

### Social Media Links

| Field                | Type    | Description                |
| -------------------- | ------- | -------------------------- |
| `social_links_found` | boolean | Whether social links found |
| `valid_count`        | integer | Number of valid links      |
| `invalid_count`      | integer | Number of broken links     |
| `links`              | array   | Detailed link information  |

### Tranco Rank

| Field            | Type    | Description                            |
| ---------------- | ------- | -------------------------------------- |
| `rank`           | integer | Tranco ranking position                |
| `in_top_million` | boolean | Whether in top 1M sites                |
| `band`           | string  | Ranking band (top\_1k, top\_10k, etc.) |
| `score`          | number  | Popularity score (0-1)                 |

### Policies

| Field               | Type    | Description                 |
| ------------------- | ------- | --------------------------- |
| `terms`             | object  | Terms & Conditions analysis |
| `return`            | object  | Return policy analysis      |
| `refund`            | object  | Refund policy analysis      |
| `aml`               | object  | AML policy analysis         |
| `execution_time_ms` | integer | Policy analysis time        |

## Reliability Score Calculation

The reliability score is calculated as a weighted average of individual validation scores:

| Criterion      | Description                                                               | Score Range  |
| -------------- | ------------------------------------------------------------------------- | ------------ |
| SSL            | Certificate validity                                                      | 0-1          |
| Industry       | Allowed industry classification (0 = blocked, 0.5 = flagged, 1 = allowed) | 0, 0.5, or 1 |
| Name Match     | Declared name verification                                                | 0 or 1       |
| Industry Match | Declared industry verification                                            | 0 or 1       |
| Sanction Check | No sanctions matches                                                      | 0 or 1       |
| Adverse Media  | Inverse of risk score                                                     | 0-1          |
| Social Links   | Ratio of valid links                                                      | 0-1          |
| Tranco Rank    | Popularity score by band                                                  | 0.2-1        |
| Terms Policy   | Policy quality score                                                      | 0-1          |
| Return Policy  | Policy presence score                                                     | 0-1          |
| Refund Policy  | Policy presence score                                                     | 0-1          |
| AML Policy     | Policy quality score                                                      | 0-1          |

### Tranco Rank Bands

| Band        | Rank Range          | Score |
| ----------- | ------------------- | ----- |
| top\_1k     | 1 - 1,000           | 1.0   |
| top\_10k    | 1,001 - 10,000      | 0.9   |
| top\_100k   | 10,001 - 100,000    | 0.8   |
| top\_500k   | 100,001 - 500,000   | 0.6   |
| top\_1m     | 500,001 - 1,000,000 | 0.4   |
| not\_ranked | > 1,000,000         | 0.2   |

## Error Responses

### 400 Bad Request - Invalid URL

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

### 400 Bad Request - Missing URL

```json theme={null}
{
  "error": "Validation error: \"url\" parameter is required"
}
```

### 403 Forbidden - Permission Denied

```json theme={null}
{
  "error": "Permission denied: web-validation:create required"
}
```

### 500 Internal Server Error

```json theme={null}
{
  "error": "Internal server error"
}
```

## Status Codes

| Code | Description                             |
| ---- | --------------------------------------- |
| 200  | Success - Validation completed          |
| 400  | Bad Request - Invalid parameters        |
| 401  | Unauthorized - Missing or invalid token |
| 403  | Forbidden - Insufficient permissions    |
| 500  | Internal Server Error                   |

## Usage Examples

### Python - Full Validation

```python theme={null}
import requests

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

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

def validate_website(url, declared_name=None, declared_industry=None, full_check=True):
    """Perform comprehensive website validation."""
    payload = {
        "url": url,
        "include_sanction_check": True,
        "include_social_links": True,
        "include_tranco_rank": True
    }
    
    if declared_name:
        payload["declared_name"] = declared_name
    if declared_industry:
        payload["declared_industry"] = declared_industry
    
    if full_check:
        payload["include_adverse_media"] = True
        payload["include_policies"] = True
        payload["policy_types"] = ["terms", "return", "refund", "aml"]
    
    response = requests.post(
        f"{BASE_URL}/kyc/web-validation",
        headers=headers,
        json=payload,
        timeout=90
    )
    response.raise_for_status()
    return response.json()

def analyze_result(result):
    """Analyze validation results."""
    data = result.get("result", {})
    score = data.get("reliability_score", 0)
    validation = data.get("validation_results", {})
    
    print(f"Reliability Score: {score:.1f}/100")
    
    # SSL Check
    ssl = validation.get("ssl_checker", {})
    print(f"\n🔒 SSL: {'Valid' if ssl.get('is_valid') else 'Invalid'}")
    if ssl.get('days_until_expiry'):
        print(f"   Expires in {ssl['days_until_expiry']} days")
    
    # Sanction Check
    sanction = validation.get("sanction_check", {})
    if sanction.get("has_matches"):
        print(f"\n⚠️ SANCTION MATCHES FOUND!")
    else:
        print(f"\n✅ No sanction matches")
    
    # Adverse Media
    adverse = validation.get("adverse_media", {})
    decision = adverse.get("decision", "N/A")
    print(f"\n📰 Adverse Media: {decision}")
    if adverse.get("risk_score"):
        print(f"   Risk Score: {adverse['risk_score']}/100")
    
    # Social Links
    social = validation.get("social_media_links", {})
    valid = social.get("valid_count", 0)
    invalid = social.get("invalid_count", 0)
    print(f"\n🔗 Social Links: {valid} valid, {invalid} broken")
    
    # Tranco Rank
    tranco = validation.get("tranco_rank", {})
    rank = tranco.get("rank")
    band = tranco.get("band", "not_ranked")
    print(f"\n📊 Tranco Rank: #{rank} ({band})" if rank else "\n📊 Tranco: Not ranked")
    
    # Policies
    policies = validation.get("policies", {})
    if policies:
        print("\n📋 Policies:")
        for policy_type in ["terms", "return", "refund", "aml"]:
            policy = policies.get(policy_type, {})
            status = policy.get("status", "not_found")
            print(f"   - {policy_type.title()}: {status}")
    
    return score >= 70

# Usage
result = validate_website(
    url="https://example-shop.com",
    declared_name="Example Shop Inc",
    declared_industry="E-commerce",
    full_check=True
)

is_reliable = analyze_result(result)
print(f"\n{'✅ Website appears reliable' if is_reliable else '⚠️ Website requires review'}")
```

### JavaScript - With Risk Assessment

```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 validateWebsite(url, options = {}) {
  const payload = {
    url,
    include_sanction_check: true,
    include_social_links: true,
    include_tranco_rank: true,
    ...options
  };

  const response = await fetch(`${BASE_URL}/kyc/web-validation`, {
    method: 'POST',
    headers,
    body: JSON.stringify(payload)
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || 'Validation failed');
  }

  return response.json();
}

function assessRisk(result) {
  const data = result.result || {};
  const score = data.reliability_score || 0;
  const validation = data.validation_results || {};
  
  const risks = [];
  
  // Check SSL
  const ssl = validation.ssl_checker || {};
  if (!ssl.is_valid) {
    risks.push({ level: 'high', issue: 'Invalid SSL certificate' });
  } else if (ssl.days_until_expiry < 30) {
    risks.push({ level: 'medium', issue: 'SSL expires soon' });
  }
  
  // Check sanctions
  const sanction = validation.sanction_check || {};
  if (sanction.has_matches) {
    risks.push({ level: 'critical', issue: 'Sanction matches found' });
  }
  
  // Check adverse media
  const adverse = validation.adverse_media || {};
  if (adverse.decision === 'HIGH_RISK') {
    risks.push({ level: 'high', issue: 'High risk adverse media' });
  } else if (adverse.decision === 'MEDIUM_RISK') {
    risks.push({ level: 'medium', issue: 'Medium risk adverse media' });
  }
  
  // Check domain age (via WHOIS)
  const whois = validation.whois_info || {};
  if (whois.domain_age_days < 180) {
    risks.push({ level: 'medium', issue: 'Domain less than 6 months old' });
  }
  
  // Check Tranco rank
  const tranco = validation.tranco_rank || {};
  if (!tranco.in_top_million) {
    risks.push({ level: 'low', issue: 'Low website popularity' });
  }
  
  // Check social links
  const social = validation.social_media_links || {};
  if (social.invalid_count > social.valid_count) {
    risks.push({ level: 'medium', issue: 'Broken social media links' });
  }
  
  // Determine overall risk level
  const hasCritical = risks.some(r => r.level === 'critical');
  const hasHigh = risks.some(r => r.level === 'high');
  const hasMedium = risks.some(r => r.level === 'medium');
  
  let overallRisk = 'low';
  if (hasCritical) overallRisk = 'critical';
  else if (hasHigh) overallRisk = 'high';
  else if (hasMedium) overallRisk = 'medium';
  
  return {
    score,
    overallRisk,
    risks,
    recommendation: score >= 70 && !hasCritical && !hasHigh ? 'approve' : 'review'
  };
}

// Usage
async function main() {
  const result = await validateWebsite('https://example-shop.com', {
    declared_name: 'Example Shop Inc',
    declared_industry: 'E-commerce',
    include_adverse_media: true,
    include_policies: true
  });
  
  const assessment = assessRisk(result);
  
  console.log(`Score: ${assessment.score.toFixed(1)}/100`);
  console.log(`Overall Risk: ${assessment.overallRisk.toUpperCase()}`);
  console.log(`Recommendation: ${assessment.recommendation.toUpperCase()}`);
  
  if (assessment.risks.length > 0) {
    console.log('\nRisk Factors:');
    assessment.risks.forEach(risk => {
      console.log(`  [${risk.level}] ${risk.issue}`);
    });
  }
}
```

## Best Practices

### Performance

* **Basic validation** (without policies/adverse media): \~3-5 seconds
* **Full validation** (all checks enabled): \~8-15 seconds
* Use `enable_js_render: true` only for JavaScript-heavy sites (adds \~2-3 seconds)

### Reliability Score Thresholds

| Score Range | Recommendation                          |
| ----------- | --------------------------------------- |
| 80-100      | Low risk, safe to proceed               |
| 60-79       | Medium risk, review recommended         |
| 40-59       | Higher risk, manual verification needed |
| 0-39        | High risk, exercise caution             |

### Configuration Tips

1. **For E-commerce/Merchant Onboarding**:
   * Enable all checks including policies
   * Verify `declared_name` and `declared_industry`
   * Check `include_adverse_media: true`

2. **For Quick Screening**:
   * Use basic validation without policies
   * Focus on SSL, sanction check, and Tranco rank

3. **For High-Risk Industries**:
   * Enable AML policy check
   * Use tenant-configured risk matrix weights

### Tenant Configuration

Settings can be pre-configured per tenant:

* Default validation options
* Risk matrix weights
* Allowed/blocked industries
* Policy check requirements

Contact support to configure tenant-specific settings.

## Related References

* [Adverse Media API](/api-reference/adverse-media) - Dedicated adverse media analysis
* [CUA Website Scan](/api-reference/cua-scan) - AI-powered deep website analysis
* [List Check](/api-reference/list-check) - Entity sanctions screening
