Authentication
All API requests require authentication via a Bearer token. Create API keys from your dashboard.Keys use the format wpss_<32 hex chars>.
Authorization: Bearer wpss_your_api_key_here
curl -H "Authorization: Bearer wpss_abc123..." \ "https://your-domain.com/api/scan/stream?url=https://example.com"
const response = await fetch(
"https://your-domain.com/api/scan/stream?url=https://example.com",
{
headers: {
Authorization: "Bearer wpss_abc123..."
}
}
);
// Parse Server-Sent Events
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
console.log(text);
}Key Format
wpss_<32 hex>
Keys start with wpss_ prefix
Rate Limiting
Per-key limits
Configurable daily limits
Revocation
Instant
Revoke anytime from dashboard
Rate Limits
Rate limits depend on authentication. Guests have per-IP limits; authenticated users have per-key daily limits.
| Type | Per-Minute | Per-Day |
|---|---|---|
| Guest | 10 req/min | Plan-based |
| Session User | 10 req/min | Plan limit |
| API Key | 10 req/min | Key-specific |
Rate Limit Headers
X-RateLimit-Limit: 10 X-RateLimit-Remaining: 7 X-RateLimit-Reset: 1709827200 Retry-After: 42 # Only on 429 responses
/api/scan/streamStream Scan
Stream a full security scan via Server-Sent Events. Returns real-time progress as each of 36 scanners completes. This is the recommended endpoint for most integrations.
Parameters
| Parameter | Type | Description |
|---|---|---|
url | string | Full URL to scan (https://example.com). Must be root domain, no path. |
SSE Events
| Event | Description |
|---|---|
start | Scan initialized — includes totalScanners and scanner list |
scanner:start | Individual scanner starting — includes scanner name |
scanner:complete | Scanner finished — includes name and issueCount |
complete | Full scan result with score, issues, plugins, SSL, and durationMs |
Example Response
event: start
data: {"totalScanners":33,"scanners":[{"name":"ssl","displayName":"SSL/TLS Certificate","category":"network"},...]}
event: scanner:start
data: {"name":"ssl"}
event: scanner:complete
data: {"name":"ssl","issueCount":0}
event: scanner:start
data: {"name":"headers"}
event: scanner:complete
data: {"name":"headers","issueCount":2}
...
event: complete
data: {"url":"https://example.com","score":72,"issues":[...],"passedChecks":[...],"durationMs":4521}/api/scanSingle Scan
Run a full scan and return results as a single JSON response. Simpler than streaming but no progress updates.
Request Body
| Parameter | Type | Description |
|---|---|---|
url | string | Full URL to scan (https://example.com). Must be root domain. |
curl -X POST \
-H "Authorization: Bearer wpss_abc123..." \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}' \
"https://your-domain.com/api/scan"{
"url": "https://example.com",
"score": 72,
"issues": [
{
"id": "missing-csp",
"severity": "high",
"title": "Missing Content-Security-Policy",
"description": "No CSP header detected...",
"recommendation": "Add a Content-Security-Policy header..."
}
],
"passedChecks": [...],
"detectedPlugins": [...],
"detectedTheme": {...},
"sslAnalysis": {...},
"scannedAt": "2026-03-07T12:00:00.000Z"
}/api/scan/bulkBulk Scan
Scan up to 20 sites concurrently with streaming progress. Runs 3 concurrent scans at a time.
Parameters
| Parameter | Type | Description |
|---|---|---|
urls | string | Comma-separated URLs (max 20 or plan limit) |
SSE Events
| Event | Description |
|---|---|
start | Bulk scan initialized — totalUrls and urlList |
url:start | Individual URL scan starting — urlIndex and url |
url:scanner:complete | Scanner finished for a URL — urlIndex, scannerName, issueCount |
url:complete | URL scan complete — urlIndex, url, and full result |
allComplete | All scans done — totalUrls, completedUrls, averageScore, results[] |
curl -H "Authorization: Bearer wpss_abc123..." \ "https://your-domain.com/api/scan/bulk?urls=https://site1.com,https://site2.com,https://site3.com"
/api/scan/compareCompare Two Sites
Compare two sites side-by-side. Both URLs are scanned in parallel and results include score diffs and unique issues.
Parameters
| Parameter | Type | Description |
|---|---|---|
url1 | string | First URL to compare |
url2 | string | Second URL to compare |
SSE Events
| Event | Description |
|---|---|
start | Comparison initialized — scanner list |
scanner:start | Scanner starting for one site — name and scanIndex (0 or 1) |
scanner:complete | Scanner finished — name, issueCount, scanIndex |
complete | One site scan complete — scanIndex and full result with durationMs |
allComplete | Both scans complete — comparison ready |
curl -H "Authorization: Bearer wpss_abc123..." \ "https://your-domain.com/api/scan/compare?url1=https://site1.com&url2=https://site2.com"
/api/scan/check-limitCheck Scan Quota
Check remaining scan quota for the current API key, session, or guest IP. Does not consume a scan.
curl -H "Authorization: Bearer wpss_abc123..." \ "https://your-domain.com/api/scan/check-limit"
{
"allowed": true,
"remaining": 87,
"isGuest": false
}Note: remaining: -1 means unlimited scans. remaining: 0 means the daily limit has been reached.
API Key Management
Admin endpoints for creating, listing, updating, and revoking API keys. All require admin session authentication.
/api/keys— Create a new API key| Parameter | Type | Description |
|---|---|---|
name | string | Key name (max 200 chars) |
dailyLimit | number | Max scans per day (default: 100) |
expiresAt | ISO 8601 | Optional expiration datetime |
{
"id": "clx1abc...",
"name": "CI Pipeline Key",
"keyPrefix": "wpss_a1b",
"dailyLimit": 100,
"expiresAt": null,
"createdAt": "2026-03-07T12:00:00.000Z",
"key": "wpss_a1b2c3d4e5f6..." // Returned ONCE — save it!
}/api/keys— List all API keysReturns all keys with usage stats. Full key hash is never returned — only the prefix is shown.
/api/keys/[id]— Update key name or limit| Parameter | Type | Description |
|---|---|---|
name | string | New key name (max 200 chars) |
dailyLimit | number | New daily scan limit (positive integer) |
/api/keys/[id]— Revoke an API key (soft delete)Sets the key to inactive. The key cannot be reactivated — create a new one instead.
Response Format
The complete event (streaming) or JSON response (POST) returns this structure.
{
"url": "https://example.com",
"score": 72,
"issues": [
{
"id": "missing-csp",
"severity": "high",
"title": "Missing Content-Security-Policy",
"description": "No CSP header detected...",
"recommendation": "Add a Content-Security-Policy header...",
"scanner": "headers",
"category": "headers"
}
],
"passedChecks": [
{
"scanner": "ssl",
"displayName": "SSL/TLS Certificate",
"description": "Valid certificate with 240 days remaining"
}
],
"detectedPlugins": [
{
"name": "Contact Form 7",
"version": "5.8.4",
"vulnerable": false,
"vulnerabilities": []
}
],
"detectedTheme": {
"name": "Flavflavor flavor flavored",
"slug": "flavor",
"version": "3.2.1",
"isChild": false,
"parentTheme": null
},
"sslAnalysis": {
"isValid": true,
"issuer": "Let's Encrypt",
"daysUntilExpiry": 240,
"protocol": "TLSv1.3",
"selfSigned": false
},
"scannedAt": "2026-03-07T12:00:00.000Z",
"durationMs": 4521
}Issue Severity Levels
critical
Immediate action needed
high
Should fix soon
medium
Moderate risk
low
Informational
Error Codes
All errors return a JSON object with an error field.
{
"error": "Missing or invalid URL"
}| Code | Meaning |
|---|---|
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
422 | Validation Error |
429 | Rate Limited |
500 | Server Error |
URL Validation Rules
All scan endpoints validate URLs against these rules:
- Protocol must be http:// or https://
- Must be root domain only (no path, e.g. https://example.com not https://example.com/page)
- Private IP ranges blocked (10.x, 172.16-31.x, 192.168.x, 127.x, ::1, etc.)
- Localhost scanning is forbidden
- URL must resolve to a valid domain