Node.js SDK
The official TypeScript SDK for ComplianceLayer. Zero runtime dependencies, built on native fetch, with full CommonJS and ESM support. Ships with comprehensive type definitions for autocompletion and compile-time safety.
Overview
The compliancelayer npm package provides a single ComplianceLayer client class that covers the entire API surface. Designed for modern Node.js (18+) and edge runtimes.
- Zero runtime dependencies — uses native
fetch(Node 18+) or accepts a custom fetch implementation - TypeScript first — full type definitions with generics, discriminated unions, and JSDoc comments
- CJS + ESM — dual package exports for CommonJS (
require) and ES Modules (import) - Auto-retry — exponential backoff on transient failures (429, 500, 502, 503, 504)
- Rate limiting — automatic request throttling to stay within API limits
- Tree-shakeable — only import what you need for minimal bundle size
Installation
npm install compliancelayerfetch support. For older Node.js versions, pass a custom fetch implementation via the fetch option (e.g., node-fetch).Quick Start
Run your first scan in just a few lines:
import { ComplianceLayer } from 'compliancelayer';
const client = new ComplianceLayer({ apiKey: 'cl_your_api_key' });
// Run a scan and wait for results
const report = await client.scan('example.com');
console.log(`${report.domain}: ${report.grade} (${report.score}/100)`);
console.log(`Risk level: ${report.riskLevel}`);
console.log(`Issues found: ${report.issuesCount}`);
// Iterate over findings
for (const issue of report.issues) {
console.log(` [${issue.severity}] ${issue.finding}`);
if (issue.remediation) {
console.log(` Fix: ${issue.remediation}`);
}
}The scan() method submits the domain, polls for completion, and returns the full report. By default it polls every 2 seconds and times out after 120 seconds.
Scanning
The SDK provides several methods for running security scans, each suited to a different workflow.
client.scan(domain, options?)Submit a domain for scanning and await the completed report. This is the simplest way to run a scan. Internally, it calls scanAsync() and polls until the scan finishes.
// Simple scan with default options
const report = await client.scan('example.com');
console.log(`Score: ${report.score}, Grade: ${report.grade}`);
// With custom timeout
const report2 = await client.scan('example.com', { timeout: 180_000 });
// Access module-level results
for (const [name, module] of Object.entries(report.modules)) {
console.log(` ${name}: ${module.score}/100 (${module.grade})`);
}client.scanAsync(domain)Submit a domain for scanning and return immediately with a ScanJob object. Use this when you want to manage polling yourself, fire-and-forget, or track multiple concurrent scans.
// Submit scan without waiting
const job = await client.scanAsync('example.com');
console.log(`Job ID: ${job.jobId}`);
console.log(`Status: ${job.status}`); // "queued" or "running"
// Check status manually
await job.refresh();
console.log(`Status: ${job.status}`);
// Wait for completion with custom options
await job.wait({ pollInterval: 3000, timeout: 120_000 });
// Get the report once complete
if (job.status === 'completed') {
const report = await job.getReport();
console.log(`Score: ${report.score}`);
} else if (job.status === 'failed') {
console.log(`Scan failed: ${job.failureReason}`);
}client.freeScan(domain)Run a free scan that does not require authentication or count against your quota. Free scans have reduced module coverage and are rate limited to 5 per hour per IP. No API key is required.
import { ComplianceLayer } from 'compliancelayer';
// Free scan - no API key needed
const client = new ComplianceLayer();
const report = await client.freeScan('example.com');
console.log(`${report.domain}: ${report.grade} (${report.score}/100)`);
console.log(`Modules scanned: ${Object.keys(report.modules).length}`);ScanJob Methods
The ScanJob object returned by scanAsync() provides methods for tracking scan progress:
| Method | Returns | Description |
|---|---|---|
job.refresh() | Promise<ScanJob> | Fetch the latest status from the API and update the job object |
job.wait(options?) | Promise<ScanJob> | Poll until complete or timeout. Options: pollInterval, timeout (ms) |
job.getReport() | Promise<ScanReport> | Fetch the full report. Throws ScanNotCompleteError if still running |
job.isComplete | boolean | Getter that returns true if the scan has finished (completed or failed) |
Batch Operations
Scan multiple domains in a single API call. Batch operations support up to 50 domains per request and are more efficient than individual scans.
// Scan multiple domains at once (up to 50)
const domains = [
'example.com',
'acme.org',
'globex.net',
'initech.io',
'umbrella.co'
];
// Submit batch scan
const batch = await client.batchScan(domains);
console.log(`Batch ID: ${batch.batchId}`);
console.log(`Total domains: ${batch.total}`);
// Wait for all scans to complete
await batch.wait();
// Access individual results
for (const result of batch.results) {
console.log(`${result.domain}: ${result.grade} (${result.score}/100)`);
}
// Get summary statistics
console.log(`Average score: ${batch.averageScore}`);
console.log(`Completed: ${batch.completedCount}/${batch.total}`);
console.log(`Failed: ${batch.failedCount}`);// Compare security posture across domains
const comparison = await client.compare([
'example.com',
'competitor1.com',
'competitor2.com'
]);
// Overall comparison
console.log(`Best: ${comparison.best.domain} (${comparison.best.score})`);
console.log(`Worst: ${comparison.worst.domain} (${comparison.worst.score})`);
console.log(`Average: ${comparison.averageScore}`);
// Module-level comparison
for (const [moduleName, scores] of Object.entries(comparison.byModule)) {
console.log(`\n${moduleName}:`);
for (const [domain, score] of Object.entries(scores)) {
console.log(` ${domain}: ${score}/100`);
}
}Webhooks
Manage webhook endpoints programmatically. Create, list, test, and inspect webhook deliveries.
// Create a new webhook endpoint
const webhook = await client.webhooks.create({
url: 'https://api.yourapp.com/webhooks/compliancelayer',
events: ['scan.completed', 'alert.triggered'],
description: 'Production webhook for scan results'
});
console.log(`Webhook ID: ${webhook.id}`);
console.log(`Secret: ${webhook.secret}`); // Shown only once!
console.log(`Status: ${webhook.status}`); // "active"Domain Monitoring
Add domains to your monitoring list for scheduled scans and automatic alerts when security posture changes.
// Add a domain to monitoring
const domain = await client.domains.add({
domain: 'example.com',
scanInterval: 'weekly', // "daily", "weekly", "monthly"
alertThreshold: 70 // Alert if score drops below this
});
console.log(`Domain ID: ${domain.id}`);
console.log(`Monitoring: ${domain.domain}`);
console.log(`Scan interval: ${domain.scanInterval}`);
console.log(`Next scan: ${domain.nextScanAt}`);Badges
Generate embeddable security badges for your domains. Badges display the current security grade and update automatically with each scan.
// Get the badge URL for a domain
const url = client.badgeUrl('example.com');
console.log(`Badge URL: ${url}`);
// https://api.compliancelayer.net/v1/badge/example.com
// Use in Markdown
const markdown = ``;
// Get the raw SVG content
const svg = await client.badgeSvg('example.com');
await fs.writeFile('badge.svg', svg);
// Customize badge style
const customUrl = client.badgeUrl('example.com', {
style: 'flat', // "flat", "flat-square", "plastic"
label: 'Security' // Custom left-side label
});Configuration
The ComplianceLayer constructor accepts a ComplianceLayerOptions object with the following properties:
| Parameter | Type | Default | Description |
|---|---|---|---|
apiKey | string | undefined | Your ComplianceLayer API key (starts with cl_). Falls back to COMPLIANCELAYER_API_KEY env var. |
baseUrl | string | "https://api.compliancelayer.net/v1" | Base URL for the API. Override for self-hosted instances or local development. |
timeout | number | 30000 | HTTP request timeout in milliseconds for individual API calls. |
pollInterval | number | 2000 | Milliseconds between status polls when using blocking scan() or job.wait(). |
pollTimeout | number | 120000 | Maximum milliseconds to wait for a scan to complete when polling. |
maxRetries | number | 3 | Number of automatic retries on transient errors (429, 5xx). Uses exponential backoff. |
fetch | typeof fetch | globalThis.fetch | Custom fetch implementation. Useful for Node <18, testing, or custom transports. |
import { ComplianceLayer } from 'compliancelayer';
// Fully customized client
const client = new ComplianceLayer({
apiKey: 'cl_your_api_key',
baseUrl: 'https://api.compliancelayer.net/v1',
timeout: 60_000, // 60s HTTP timeout
pollInterval: 3000, // Poll every 3 seconds
pollTimeout: 180_000, // Wait up to 3 minutes for scans
maxRetries: 5 // Retry up to 5 times on failures
});
// Or use environment variable for API key
// export COMPLIANCELAYER_API_KEY=cl_your_api_key
const envClient = new ComplianceLayer(); // Reads from envapiKey is not passed to the constructor, the SDK reads from the COMPLIANCELAYER_API_KEY environment variable. This is the recommended approach for production deployments.Error Handling
The SDK throws typed errors for different failure conditions. All errors extend ComplianceLayerError, so you can catch all SDK errors with a single catch block or handle specific types individually.
import { ComplianceLayer } from 'compliancelayer';
import {
ComplianceLayerError, // Base error for all SDK errors
APIError, // Generic API error (non-2xx response)
AuthenticationError, // Invalid or missing API key (401)
ForbiddenError, // Insufficient permissions (403)
NotFoundError, // Resource not found (404)
RateLimitError, // Rate limit exceeded (429)
QuotaExceededError, // Scan quota exhausted
ValidationError, // Invalid request parameters (422)
ScanTimeoutError, // Scan polling timed out
ScanFailedError, // Scan execution failed
ServerError, // Server-side error (5xx)
ConnectionError, // Network connectivity issue
} from 'compliancelayer';
const client = new ComplianceLayer({ apiKey: 'cl_your_api_key' });
try {
const report = await client.scan('example.com');
console.log(`Score: ${report.score}`);
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid API key. Check your credentials.');
} else if (error instanceof QuotaExceededError) {
console.error(`Scan quota exhausted. Resets at: ${error.resetAt}`);
console.error(`Current usage: ${error.used}/${error.limit}`);
} else if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${error.retryAfter}s.`);
} else if (error instanceof ScanTimeoutError) {
console.error('Scan took too long. Increase pollTimeout.');
} else if (error instanceof ScanFailedError) {
console.error(`Scan failed: ${error.reason}`);
console.error(`Domain: ${error.domain}`);
} else if (error instanceof ValidationError) {
console.error(`Invalid input: ${error.detail}`);
for (const err of error.errors) {
console.error(` Field '${err.field}': ${err.message}`);
}
} else if (error instanceof ServerError) {
console.error('ComplianceLayer server error. Try again later.');
} else if (error instanceof ConnectionError) {
console.error('Network error. Check your internet connection.');
} else if (error instanceof ComplianceLayerError) {
// Catch-all for any SDK error
console.error(`Unexpected error: ${error.message}`);
}
}Exception Hierarchy
| Error Class | HTTP Status | Description |
|---|---|---|
ComplianceLayerError | - | Base class for all SDK errors |
APIError | Any non-2xx | Generic API error with status code and message |
AuthenticationError | 401 | Invalid, missing, or expired API key |
ForbiddenError | 403 | Valid key but insufficient plan permissions |
NotFoundError | 404 | Requested resource does not exist |
ValidationError | 422 | Request failed validation (invalid domain, missing fields) |
RateLimitError | 429 | Too many requests; includes retryAfter in seconds |
QuotaExceededError | 429 | Monthly scan quota exhausted; includes resetAt date |
ServerError | 500-599 | Server-side error; safe to retry |
ScanTimeoutError | - | Polling timed out before scan completed |
ScanFailedError | - | Scan execution failed (invalid domain, DNS error) |
ConnectionError | - | Network error (DNS resolution, timeout, connection refused) |
TypeScript Types
The SDK exports comprehensive TypeScript interfaces for all request and response objects. Here are the key types you will work with most often:
interface ScanReport {
/** The scanned domain */
domain: string;
/** Numeric score from 0 to 100 */
score: number;
/** Letter grade: A, B, C, D, or F (with optional +/-) */
grade: string;
/** Risk classification */
riskLevel: 'low' | 'low_medium' | 'medium' | 'high' | 'critical';
/** Unique report identifier */
reportId: string;
/** ISO 8601 timestamp of scan completion */
scannedAt: string;
/** Total number of issues found */
issuesCount: number;
/** Issue counts by severity */
criticalIssues: number;
highIssues: number;
mediumIssues: number;
lowIssues: number;
/** Individual module results keyed by module name */
modules: Record<string, ModuleResult>;
/** All issues sorted by severity */
issues: ScanIssue[];
/** Compliance framework mappings */
compliance: ComplianceMapping;
/** Prioritized remediation recommendations */
recommendations: string[];
/** Caveats about the assessment methodology */
caveats: string[];
}Advanced Usage
Webhook Signature Verification
The SDK includes a utility for verifying incoming webhook signatures in your server:
import { verifyWebhookSignature } from 'compliancelayer';
import express from 'express';
const app = express();
app.post(
'/webhooks/compliancelayer',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-compliancelayer-signature'] as string;
const payload = req.body.toString('utf8');
const secret = process.env.WEBHOOK_SECRET!;
// Verify the signature (throws if invalid)
try {
verifyWebhookSignature(payload, signature, secret);
} catch {
return res.status(401).send('Invalid signature');
}
// Safe to process the event
const event = JSON.parse(payload);
console.log(`Event: ${event.type}`);
res.status(200).send('OK');
}
);Pagination
List endpoints return paginated results. The SDK provides async iterables for automatic pagination:
// Automatic pagination with async iteration
for await (const domain of client.domains.list()) {
console.log(`${domain.domain}: ${domain.latestScore}`);
}
// Manual pagination with page size control
const page = await client.domains.list({ limit: 25 });
console.log(`Total domains: ${page.total}`);
console.log(`Page 1 of ${page.totalPages}`);
for (const domain of page.items) {
console.log(` ${domain.domain}`);
}
// Get next page
if (page.hasNext) {
const nextPage = await page.next();
}AbortController Support
Cancel in-flight requests using standard AbortController:
const controller = new AbortController();
// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10_000);
try {
const report = await client.scan('example.com', {
signal: controller.signal
});
console.log(`Score: ${report.score}`);
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
console.log('Scan request was cancelled');
}
}Logging and Debugging
Enable debug logging to see all HTTP requests and responses:
import { ComplianceLayer } from 'compliancelayer';
// Enable debug mode via environment variable
// DEBUG=compliancelayer* node app.js
// Or programmatically
const client = new ComplianceLayer({
apiKey: 'cl_your_api_key',
debug: true // Logs all requests to console
});
// Output:
// [compliancelayer] POST /v1/scan {"domain":"example.com"}
// [compliancelayer] 200 OK (234ms)
// [compliancelayer] GET /v1/scan/jobs/12345
// [compliancelayer] 200 OK {"status":"completed",...}Framework Examples
Express.js Middleware
import express from 'express';
import { ComplianceLayer } from 'compliancelayer';
const app = express();
const cl = new ComplianceLayer({ apiKey: process.env.CL_API_KEY });
app.get('/api/scan/:domain', async (req, res) => {
try {
const report = await cl.scan(req.params.domain);
res.json({
domain: report.domain,
score: report.score,
grade: report.grade,
issues: report.issuesCount
});
} catch (error) {
if (error.statusCode === 429) {
res.status(429).json({ error: 'Rate limited' });
} else {
res.status(500).json({ error: 'Scan failed' });
}
}
});
app.listen(3000);Next.js Server Action
// app/actions/scan.ts
'use server';
import { ComplianceLayer } from 'compliancelayer';
const client = new ComplianceLayer({
apiKey: process.env.COMPLIANCELAYER_API_KEY
});
export async function scanDomain(domain: string) {
const report = await client.scan(domain);
return {
domain: report.domain,
score: report.score,
grade: report.grade,
riskLevel: report.riskLevel,
issues: report.issues.map(i => ({
severity: i.severity,
finding: i.finding,
remediation: i.remediation
}))
};
}