Running Scans
Learn how to scan domains, poll for results, and interpret security findings. ComplianceLayer's scanning engine runs 15 security modules to produce a comprehensive risk assessment.
How Scanning Works
When you submit a scan request, ComplianceLayer:
- Queues the job — Responds
202 Acceptedwith an integerjob_id - Validates the domain — Resolves DNS and rejects private or internal targets
- Runs 15 scan modules in parallel — DNS/email, SSL, headers, ports, WHOIS, DNSSEC, blacklists, cookies, subdomains, tech, WAF, breach, reputation, JS, trackers
- Aggregates results — Calculates weighted score and grade
- Maps to compliance — SOC 2, ISO 27001, NIST, PCI DSS, HIPAA, CIS
Scans typically complete in 30-60 seconds. Poll the job status endpoint to check progress.
Basic Scan
Submit a domain for scanning:
curl -X POST "https://api.compliancelayer.net/v1/scan" \
-H "Authorization: Bearer cl_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "example.com"
}'job_id is an integer. legacy_scan_id points at the underlying scan record used by older endpoints. Both /v1/scan and /v1/scan/ are accepted; the examples here use /v1/scan.
A malformed domain is rejected before the job is queued, with 422 Unprocessable Entity:
{
"detail": [
{
"field": "domain",
"message": "Value error, Invalid domain format"
}
]
}Polling for Results
Poll the job status endpoint every 2-3 seconds until status is completed:
async function waitForCompletion(jobId, maxWait = 120) {
const startTime = Date.now();
while (Date.now() - startTime < maxWait * 1000) {
const response = await fetch(
`https://api.compliancelayer.net/v1/scan/jobs/${jobId}`,
{
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`
}
}
);
const status = await response.json();
switch (status.status) {
case 'completed':
return status.result;
case 'failed':
throw new Error(`Scan failed: ${status.failure_reason}`);
case 'queued':
case 'running':
await new Promise(r => setTimeout(r, 2000)); // Wait 2s
break;
}
}
throw new Error('Timeout waiting for scan');
}The job snapshot carries a summary only. Fetch the findings themselves from GET /v1/scan/jobs/{job_id}/report once status is completed.
Understanding Results
Score & Grade
The overall score is a weighted aggregate of all module scores, using risk bands informed by common cyber insurance underwriting practice:
- 90-100 (A): Low Risk — Excellent security posture
- 75-89 (B): Low-Medium Risk — Good, minor improvements needed
- 55-74 (C): Medium Risk — Material gaps present, several issues to address
- 35-54 (D): High Risk — Significant vulnerabilities, urgent action required
- 0-34 (F): Critical Risk — Multiple critical security gaps
Grades are whole letters only — A, B, C, D, F. There are no plus or minus variants.
Risk Levels
Each scan receives a risk_level classification as directional context:
- low (90-100): Strong security controls, minimal risk exposure
- low_medium (75-89): Acceptable with minor issues, low-moderate risk
- medium (55-74): Material gaps present, moderate risk
- high (35-54): Significant exposure, high risk
- critical (0-34): Multiple critical issues, very high risk
Module Scores
Each security module has its own score and weight. Weights reflect how heavily each area tends to feature in external risk reviews:
Tier 1: Material Controls (85%)
| Module | Weight | Checks |
|---|---|---|
| SSL/TLS | 25% | Certificate validity, expiry, chain, protocols, cipher suites |
| HTTP Headers | 25% | HSTS, CSP (including weak directive detection), CORS misconfiguration, X-Frame-Options |
| DNS/Email | 20% | SPF, DMARC, DKIM (info only), CAA records, MX configuration |
| Open Ports | 15% | Exposed services, dangerous ports (Telnet, FTP, RDP), attack surface |
Tier 2: Secondary Controls (15%)
| Module | Weight | Checks |
|---|---|---|
| Cookie Security | 8% | Secure, HttpOnly, SameSite flags |
| DNSSEC | 5% | DNSSEC signing, chain of trust |
| WAF Detection | 4% | Web Application Firewall presence |
| JavaScript Audit | 3% | JS libraries with publicly reported vulnerabilities (version-based detection) |
Tier 3: Informational Only (0%)
The following modules are included for context but do not affect the score:
| Module | Weight | Purpose |
|---|---|---|
| Subdomains | 0% | Inventory and attack surface mapping |
| Blacklists | 0% | Reputation indicators (outcome not control) |
| Reputation | 0% | Historical reputation data |
| Breach History | 0% | Historical breach exposure |
| WHOIS | 0% | Domain registration details |
| Trackers | 0% | Privacy analysis (not security) |
| Tech Stack | 0% | Technology fingerprinting for OSINT |
Issue Severity
- Critical: Immediate action required — Catastrophic for operations (expired SSL, no SPF/DMARC, exposed admin ports)
- High: Urgent fix needed — Important security gaps (weak TLS, missing HSTS on HTTPS sites)
- Medium: Should address soon — Best practice violations (weak CSP directives, CORS misconfigurations)
- Low: Minor improvement — Defense-in-depth enhancements (X-Frame-Options, cookie settings)
- Info: Informational only — No penalty to score (DKIM auto-detection limitations, SSH on port 22 with key auth)
Full Report Structure
Retrieve the complete report for a finished job:
curl "https://api.compliancelayer.net/v1/scan/jobs/4068/report" \
-H "Authorization: Bearer cl_YOUR_API_KEY"Notes on the shape:
modulesis an object keyed by module name, with one entry per module that returned data. Each entry hasscore,grade,weight, anissuesarray, and a module-specificdetailobject.- Entries in the top-level
issuesarray carry onlyseverity,finding, andremediation. There is no module or title field, so usemodules[name].issueswhen you need to attribute a finding to a module. complianceholds asummaryof pass/fail/partial counts per framework, acategoriesbreakdown, and a per-framework array forsoc2,pci_dss,hipaa,nist,iso27001, andcis.raw_resultsholds unprocessed scanner output keyed by module, for callers that need more than the scored summary.
Best Practices
1. Cache Results
Don't scan the same domain repeatedly. Cache results for at least 1 hour to reduce API usage.
2. Use Webhooks
Instead of polling, configure webhooks to receive results when scans complete. This reduces API calls and improves efficiency. Webhooks are available on every plan.
3. Handle Errors Gracefully
A 429 means one of two different things, and they need different handling. A rate limit carries a Retry-After header and is worth retrying. An exhausted monthly scan quota also returns 429, but with a plain detail message and no Retry-After — retrying that will never succeed.
const response = await fetch('https://api.compliancelayer.net/v1/scan', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ domain: 'example.com' })
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
if (retryAfter) {
// Rate limited - safe to retry after the given delay
await new Promise(r => setTimeout(r, parseInt(retryAfter) * 1000));
} else {
// Monthly scan quota exhausted - retrying will not help
const { detail } = await response.json();
throw new Error(`Quota exhausted: ${detail}`);
}
} else if (response.status === 422) {
// Domain failed validation
const { detail } = await response.json();
console.error('Invalid domain:', detail[0].message);
} else if (!response.ok) {
throw new Error(`Scan failed with status ${response.status}`);
}4. Monitor Historical Trends
Track score changes over time to identify security drift. Set up monitoring for critical domains.