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:

  1. Queues the job — Responds 202 Accepted with an integer job_id
  2. Validates the domain — Resolves DNS and rejects private or internal targets
  3. Runs 15 scan modules in parallel — DNS/email, SSL, headers, ports, WHOIS, DNSSEC, blacklists, cookies, subdomains, tech, WAF, breach, reputation, JS, trackers
  4. Aggregates results — Calculates weighted score and grade
  5. 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.

Quota: Each submitted scan consumes one scan from your monthly quota. Free accounts get 10 scans per month — check usage before automating.

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%)

ModuleWeightChecks
SSL/TLS25%Certificate validity, expiry, chain, protocols, cipher suites
HTTP Headers25%HSTS, CSP (including weak directive detection), CORS misconfiguration, X-Frame-Options
DNS/Email20%SPF, DMARC, DKIM (info only), CAA records, MX configuration
Open Ports15%Exposed services, dangerous ports (Telnet, FTP, RDP), attack surface

Tier 2: Secondary Controls (15%)

ModuleWeightChecks
Cookie Security8%Secure, HttpOnly, SameSite flags
DNSSEC5%DNSSEC signing, chain of trust
WAF Detection4%Web Application Firewall presence
JavaScript Audit3%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:

ModuleWeightPurpose
Subdomains0%Inventory and attack surface mapping
Blacklists0%Reputation indicators (outcome not control)
Reputation0%Historical reputation data
Breach History0%Historical breach exposure
WHOIS0%Domain registration details
Trackers0%Privacy analysis (not security)
Tech Stack0%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:

  • modules is an object keyed by module name, with one entry per module that returned data. Each entry has score, grade, weight, an issues array, and a module-specific detail object.
  • Entries in the top-level issues array carry only severity, finding, and remediation. There is no module or title field, so use modules[name].issues when you need to attribute a finding to a module.
  • compliance holds a summary of pass/fail/partial counts per framework, a categories breakdown, and a per-framework array for soc2, pci_dss, hipaa, nist, iso27001, and cis.
  • raw_results holds 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.