Web Validation
curl --request POST \
--url https://api.example.com/kyc/web-validationimport requests
url = "https://api.example.com/kyc/web-validation"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/kyc/web-validation', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/kyc/web-validation",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/kyc/web-validation"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/kyc/web-validation")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/kyc/web-validation")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyCompliance Screening
Web Validation
POST
/
kyc
/
web-validation
Web Validation
curl --request POST \
--url https://api.example.com/kyc/web-validationimport requests
url = "https://api.example.com/kyc/web-validation"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/kyc/web-validation', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/kyc/web-validation",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/kyc/web-validation"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/kyc/web-validation")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/kyc/web-validation")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyComprehensive website validation and risk assessment. Analyzes domains for legitimacy, security, content quality, sanctions, adverse media, and policy compliance.
When
Endpoint
POST /kyc/web-validation
Authentication
Requiresweb-validation:create permission. Include your Bearer token in the Authorization header.
Description
The web validation endpoint performs comprehensive analysis of websites including:- URL & Domain Analysis: Validates URL structure, redirects, and domain registration (WHOIS)
- SSL/TLS Verification: Checks certificate validity, expiration, and security
- Industry Classification: AI-powered industry detection and risk classification
- Name/Industry Matching: Compares declared business info against website content
- Sanction List Check: Screens domain against global sanctions lists
- Adverse Media Analysis: Searches for negative news and fraud reports
- Social Media Validation: Verifies social media links are functional
- Tranco Rank Check: Evaluates website popularity/reputation
- Policy Compliance: Analyzes Terms, Return, Refund, and AML policies
Request Body Parameters
Request Examples
Basic Validation
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
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"
}'
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
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
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
{
"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
{
"error": "Validation error: URL must start with http:// or https://"
}
400 Bad Request - Missing URL
{
"error": "Validation error: \"url\" parameter is required"
}
403 Forbidden - Permission Denied
{
"error": "Permission denied: web-validation:create required"
}
500 Internal Server Error
{
"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
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
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: trueonly 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
-
For E-commerce/Merchant Onboarding:
- Enable all checks including policies
- Verify
declared_nameanddeclared_industry - Check
include_adverse_media: true
-
For Quick Screening:
- Use basic validation without policies
- Focus on SSL, sanction check, and Tranco rank
-
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
Related References
- Adverse Media API - Dedicated adverse media analysis
- CUA Website Scan - AI-powered deep website analysis
- List Check - Entity sanctions screening