Skip to content

API Reference

The CloviScan API lets you integrate CloviScan into your own applications and automations. All endpoints are served over HTTPS from https://cloviscan.com.

Authentication

Authenticate every request with a Bearer token in the Authorization header. Generate a token from your account settings on the CloviScan dashboard.

curl https://cloviscan.com/api/me \
  -H "Authorization: Bearer $CLOVI_TOKEN"
import requests

resp = requests.get(
    "https://cloviscan.com/api/me",
    headers={"Authorization": f"Bearer {token}"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://cloviscan.com/api/me", {
  headers: { Authorization: `Bearer ${token}` },
});
const data = await resp.json();
console.log(data);
import axios from "axios";

const { data } = await axios.get(
  "https://cloviscan.com/api/me",
  { headers: { Authorization: `Bearer ${token}` } }
);
console.log(data);

Keep your token secret

Treat your API token like a password. Send it only over HTTPS and never commit it to source control — load it from an environment variable instead.

Responses & errors

All responses are JSON. Successful calls return 2xx; client errors return 4xx with a JSON body describing the problem. Common status codes:

Status Meaning
200 Success
400 Bad request — check your parameters
401 Missing or invalid token
404 Resource not found
429 Rate limit exceeded — slow down and retry
500 Server error — retry or contact support

CloviScan API Reference

Service: CloviScan — website security and audit scanner
Base URL: https://cloviscan.com
Port: 8983 (internal)
Source: /root/cloviscan/server.js + /root/cloviscan/clovitrust.js
Last audited: 2026-06-16


Authentication Overview

CloviScan has two auth layers that work together:

Method Cookie/Header Scope
cl_session SSO cl_session (JWT cookie, set by clovitek-sso :8991) Web app pages + API routes
CloviScan_token CloviScan_token (JWT cookie, set by /api/auth/login or /api/auth/register) Product-native login
Bearer token Authorization: Bearer <token> Same JWT from either source

requireAuth (used on web pages) redirects to /login on failure.
requireAuthApi (used on JSON routes) returns 401 { error: "authentication required" } on failure.
Both accept any of the three credential forms above.

Most scan routes are public (no auth required) with IP-based rate limiting (10 scans/hour). Only /api/scan/detonate, /api/scan/history, and /dashboard require auth.

Note: A public developer API with scoped API keys is not yet available. All routes below are the same application routes used by the web UI. A csk_* API key is generated at registration but is not yet enforced on any route (the column exists, enforcement is planned).

Plans, Trial & Per-Tier Rate Limits

CloviScan has no free plan. There are four paid plans — Lite, Starter, Pro, and Agency — and each includes a 7-day free trial with full access to that plan's limits. During the trial your subscription status is in_trial and limits behave exactly like an active plan; on trial expiry it auto-converts to active and limits enforce normally.

Public scan routes (the lead-magnet instant scan) keep a flat IP-based limit of 10 scans/hour. Programmatic API access is gated to the higher plans, with per-tier request rates:

PlanAPI accessAPI rate limitDeep scans / moMonitored domainsReport retention
Lite ($19)1330 days
Starter ($39)31090 days
Pro ($99)Yes60 req/minUnlimitedUnlimited365 days
Agency ($299)Yes120 req/minUnlimitedUnlimited (client pool)365 days

GET /api/billing/limits

Returns the live per-tier limits ladder (drives pricing/docs UI). Auth: None.

{ "trial_days": 7, "tiers": [ { "tier": "lite", "deep_scans_month": 1, "monitored_domains": 3, ... }, ... ] }

POST /api/billing/quota-check

Checks whether a subscription may run a metered action. Body: { customer_id, plan, status, unit, amount } where status is the Chargebee subscription status (in_trial grants full plan limits). Returns 200 when allowed, 402 when the subscription is not entitled or over quota (with an upgrade_url). Usage is tracked via CloviMeter.


Application Routes (Web Pages)

GET /

Serves the landing page (landing.html).

Auth: None


GET /landing.html

Alias for /; serves the landing page.

Auth: None


GET /login

Serves the login page (login.html).

Auth: None


GET /dashboard

Serves the dashboard page (dashboard.html).

Auth: requireAuth — redirects to /login if not authenticated


GET /scan-github

Serves the GitHub repository scan UI page (inline HTML).

Auth: None


GET /report

Runs a live website security scan and renders the full HTML report page.

Auth: None (public)
Query Parameters: - src or url (string, required) — URL or domain to scan (e.g. example.com) - fresh (string, optional) — "1" to bypass the result cache and force a fresh scan - detonate (string, optional) — "1" to trigger a live detonation via CloviBrowse before rendering

Behavior: Serves from cache if available; runs a fresh scan with a 13-second total budget otherwise. Embeddable in iframes (no X-Frame-Options restriction).

Errors: - 400 — missing or invalid URL - 500 — scan failed


GET /widget/

Embeddable security badge or scorecard widget (iframe-friendly HTML).

Auth: None (CORS: *)
Query Parameters: - url (string) — URL or domain to display a score for - theme (string, optional) — "light" (default) or "dark" - style (string, optional) — "badge" (default) or "card" (full scorecard with category chips)


GET /embed/cloviscan.js

One-line embed loader script. Drop a <script> tag on any page to auto-mount a security badge.

Auth: None (CORS: *)
Cache-Control: public, max-age=300

Usage:

<script src="https://cloviscan.com/embed/cloviscan.js"
        data-url="example.com"
        data-key="csk_..."
        data-theme="dark"
        data-style="badge"></script>


GET /bookmarklet

Serves an HTML page with a drag-to-bookmarks-bar button for zero-install scanning.

Auth: None


GET /bookmarklet.txt

Returns the raw javascript: bookmarklet URL as plain text.

Auth: None
Content-Type: text/plain


Scan API Routes

POST /api/scan

Full website security scan. Runs SSL, HTTP headers, DNS, reputation, PageSpeed, exposure, and multi-page crawler checks in parallel.

Auth: None (public). Rate limit: 10 scans/hour per IP.
Content-Type: application/json
Body Parameters: - url (string, required) — URL or domain to scan

Example:

curl -X POST https://cloviscan.com/api/scan \
  -H "Content-Type: application/json" \
  -d '{"url": "example.com"}'

Response:

{
  "url": "https://example.com/",
  "scanned_at": "2026-06-16T12:00:00.000Z",
  "duration_ms": 4200,
  "score": 74,
  "grade": "C",
  "ssl": { "valid": true, "days_remaining": 87, "issuer": "Let's Encrypt", "expires": "2026-09-01T00:00:00.000Z" },
  "headers": { "score": 5, "max": 8, "found": ["X-Frame-Options"], "missing": ["Content-Security-Policy"] },
  "dns": { "a_records": ["93.184.216.34"], "mx_records": [], "spf_found": true, "dmarc_found": true, "dmarc_policy": "reject", "dnssec_ds": false },
  "reputation": { "status": "clean", "reason": "no threats detected" },
  "speed": { "mobile_score": 72, "desktop_score": 88 },
  "cert": { "expires": "2026-09-01T00:00:00.000Z", "days_remaining": 87, "valid": true, "issuer": "Let's Encrypt" },
  "exposure": { "score": 68, "max": 100, "findings": [{ "id": "dns_no_dnssec", "severity": "medium", "detail": "...", "cwe": "CWE-345" }], "cms": "WordPress" },
  "crawl": { "pages_crawled": 5, "truncated": false, "robots_respected": true }
}

Errors: - 400url missing or invalid - 429 — rate limit exceeded - 500 — scan failed


POST /api/scan/github

GitHub repository security scan. Checks for secrets in the file tree and git commit history, sensitive files, dependency vulnerabilities, light SAST, and repo hygiene.

Auth: None (public). Rate limit: 10 scans/hour per IP.
Content-Type: application/json
Body Parameters: - repo_url (string, required) — GitHub repository URL (e.g. https://github.com/owner/repo) - token (string, optional) — GitHub personal access token for private repos (used in-memory only, never logged or persisted) - email (string, optional) — Email for optional lead capture

Example:

curl -X POST https://cloviscan.com/api/scan/github \
  -H "Content-Type: application/json" \
  -d '{"repo_url": "https://github.com/owner/repo", "email": "[email protected]"}'

Response: Repository scan report JSON (shape defined by ./services/githubScan; token is never included in the response).

Errors: - 400repo_url missing or invalid (code: "BAD_URL") - 401 — GitHub auth failed (code: "AUTH") - 404 — repository not found (code: "NOT_FOUND") - 429 — rate limit exceeded - 503 — GitHub scanner module unavailable - 500 — scan failed (code: "SCAN_FAIL")


POST /api/scan/detonate

Opens a suspicious URL in a disposable isolated browser (via the CloviBrowse internal service). Returns behavioral analysis including screenshot ID.

Auth: requireAuthApi — requires cl_session cookie, CloviScan_token cookie, or Authorization: Bearer <token>
Content-Type: application/json
Body Parameters: - url (string, required) — URL to detonate in the isolated browser

Example:

curl -X POST https://cloviscan.com/api/scan/detonate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"url": "https://suspicious-site.example.com"}'

Response:

{ "ok": true, "screenshot_id": "abc123", "behaviors": [...] }

Errors: - 400url missing or invalid - 401 — authentication required - 502 — detonation error - 503 — detonation capability unavailable (CloviBrowse not loaded)


POST /api/scan/detonate/report

Public (no auth) detonation endpoint used by the "Run Live Detonation" button inside the /report page.

Auth: None (public)
Content-Type: application/json
Body Parameters: - url (string, required) — URL to detonate

Example:

curl -X POST https://cloviscan.com/api/scan/detonate/report \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'

Response:

{ "ok": true, "screenshot_id": "abc123", "behaviors": [...] }

On failure (detonation unavailable or any error): { "ok": false, "error": "unavailable" } — always HTTP 200.


POST /api/scan/verify

Re-scans a URL with cache bypass and returns a before/after delta comparing the new result against the previously cached scan.

Auth: None (public). Rate limit: 10 scans/hour per IP.
Content-Type: application/json
Body Parameters: - url (string, required) — URL or domain to re-scan

Example:

curl -X POST https://cloviscan.com/api/scan/verify \
  -H "Content-Type: application/json" \
  -d '{"url": "example.com"}'

Response:

{
  "domain": "example.com",
  "prevScore": 68,
  "newScore": 74,
  "prevGrade": "C",
  "newGrade": "C",
  "scoreDelta": 6,
  "resolved": ["ssl_weak_cipher"],
  "newFindings": [],
  "noChange": false,
  "scanned_at": "2026-06-16T12:05:00.000Z",
  "prev_scanned_at": "2026-06-15T09:00:00.000Z"
}

Errors: - 400url missing or invalid - 429 — rate limit exceeded - 500 — verify scan failed - 504 — re-scan timed out (40-second hard limit)


GET /api/scan/history

Returns the 10 most recent scans for the authenticated user (in-memory, not persisted across restarts).

Auth: requireAuth — requires cl_session cookie
Query Parameters: None

Example:

curl https://cloviscan.com/api/scan/history \
  -H "Cookie: cl_session=<token>"

Response:

{
  "history": [
    { "url": "https://example.com/", "scanned_at": "...", "score": 74, "grade": "C", ... }
  ]
}

Errors: - 302 redirect to /login if not authenticated


Widget API Routes

GET /api/widget/public/score

Returns a lightweight score summary for a URL. Used by the badge widget iframe.

Auth: None (CORS: *)
Query Parameters: - url (string, required) — URL or domain to score

Example:

curl "https://cloviscan.com/api/widget/public/score?url=example.com"

Response:

{
  "url": "https://example.com/",
  "score": 74,
  "grade": "C",
  "ssl_ok": true,
  "ssl_days": 87,
  "headers_score": 5,
  "headers_max": 8
}

Errors: 400url missing or invalid


GET /api/widget/public/card

Returns a richer scorecard roll-up for the card-style widget, including per-category pass/fail status.

Auth: None (CORS: *)
Query Parameters: - url (string, required) — URL or domain

Example:

curl "https://cloviscan.com/api/widget/public/card?url=example.com"

Response:

{
  "url": "https://example.com/",
  "score": 74,
  "grade": "C",
  "verdict": "Needs Attention",
  "counts": { "critical": 0, "high": 1, "medium": 3, "low": 2 },
  "controls_tested": 18,
  "controls_total": 22,
  "categories": [
    { "label": "TLS / SSL", "status": "pass", "severity": null },
    { "label": "HTTP Headers", "status": "fail", "severity": null }
  ]
}

Pending response (scan still running): { "url": "...", "pending": true, "message": "re-scanning" } — HTTP 200.

Errors: 400url missing or invalid; 500 — card roll-up failed


POST /api/widget/install

Registers or heartbeats a widget embed install. Called automatically by embed/cloviscan.js on page load. Idempotent (upserts).

Auth: None (CORS: *)
Content-Type: application/json
Body Parameters: - domain (string, required) — Installing domain (e.g. example.com) - key or api_key (string, optional) — CloviScan API key for plan-level features

Example:

curl -X POST https://cloviscan.com/api/widget/install \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com", "key": "csk_..."}'

Response:

{ "ok": true, "tracked": true }

Errors: 400domain missing


POST /api/widget/feedback

Records a user feedback report about a scan result. Feeds the training ledger.

Auth: None (CORS: *)
Content-Type: application/json
Body Parameters: - domain (string) — Domain the feedback is about - url (string, optional) — Full page URL (max 2048 chars) - issue (string) — One of: wrong_score, false_positive, missing_check, bad_layout, other (defaults to other) - note (string, optional) — Free-text explanation (max 1000 chars)

Example:

curl -X POST https://cloviscan.com/api/widget/feedback \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com", "issue": "wrong_score", "note": "SSL cert is valid but shown as invalid"}'

Response:

{ "ok": true, "recorded": true }


Report API Routes

POST /api/report/pdf

Renders the security report for a URL as a PDF (requires the report_pdf module to be loaded).

Auth: None
Content-Type: application/json
Body Parameters: - url or src (string, required) — URL or domain to generate the PDF for

Example:

curl -X POST https://cloviscan.com/api/report/pdf \
  -H "Content-Type: application/json" \
  -d '{"url": "example.com"}'

Response:

{ "ok": true, "pdf_url": "...", "filename": "..." }
(exact shape defined by ./report_pdf.renderReportPdf)

Errors: - 400url missing or invalid - 500 — PDF render failed - 501 — PDF engine not available (report_pdf module missing)


Auth API Routes

POST /api/auth/register

Self-serve user registration. Creates a new account with a free plan and a csk_* API key. Sets a CloviScan_token cookie on success.

Auth: None
Content-Type: application/json
Body Parameters: - email (string, required) — Valid email address - password (string, required) — Minimum 6 characters - full_name or name (string, optional) — Display name

Example:

curl -X POST https://cloviscan.com/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "password": "securepass", "full_name": "Jane Doe"}'

Response (201):

{
  "ok": true,
  "token": "<jwt>",
  "user": { "id": 1, "email": "[email protected]", "name": "Jane Doe", "role": "user", "plan": "free" }
}

Errors: - 400 — invalid email or password too short - 409 — email already registered - 500 — registration failed (DB error)


POST /api/auth/login

Authenticates an existing user. Sets a CloviScan_token cookie (30-day expiry).

Auth: None
Content-Type: application/json
Body Parameters: - email (string, required) - password (string, required)

Example:

curl -X POST https://cloviscan.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "password": "securepass"}'

Response:

{
  "ok": true,
  "token": "<jwt>",
  "user": { "id": 1, "email": "[email protected]", "name": "Jane Doe", "role": "user", "plan": "free" }
}

Errors: - 400 — email or password missing - 401 — invalid credentials


GET /api/auth/me

Returns the current authenticated user. Accepts cl_session SSO cookie, CloviScan_token cookie, or Bearer token.

Auth: Any valid token (returns 401 if none)
Query Parameters: None

Example:

curl https://cloviscan.com/api/auth/me \
  -H "Authorization: Bearer <token>"

Response:

{ "ok": true, "user": { "id": 1, "email": "[email protected]", "role": "user", "plan": "free" } }

Errors: 401 — Unauthorized


GET /api/me

Alias for GET /api/auth/me. Returns the same user object without the ok wrapper key.

Auth: Any valid token (returns 401 if none)

Response:

{ "user": { "id": 1, "email": "[email protected]", "role": "user", "plan": "free" } }

Errors: 401 — Unauthorized


POST /api/auth/logout

Clears the CloviScan_token cookie.

Auth: None required (silently ignores missing cookie)

Example:

curl -X POST https://cloviscan.com/api/auth/logout \
  -H "Cookie: CloviScan_token=<token>"

Response:

{ "ok": true }


Lead Capture Route

POST /api/lead

Gated email lead capture from the report page. Stores the lead in SQLite (cloviscan.db) and a JSONL backup file. Triggers best-effort CRM capture.

Auth: None
Content-Type: application/json
Body Parameters: - email (string, required) — Valid email address (max 254 chars) - url (string, optional) — URL that was scanned (max 2048 chars) - score (number, optional) — Scan score at time of capture - grade (string, optional) — Scan grade (max 8 chars)

Example:

curl -X POST https://cloviscan.com/api/lead \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "url": "example.com", "score": 74, "grade": "C"}'

Response:

{ "ok": true, "double_opt_in": true }

Errors: 400 — invalid or missing email


Monitoring Routes

POST /api/monitor/subscribe

Subscribes an email address to weekly monitoring alerts for a domain. Idempotent — re-subscribing the same email/domain pair returns success without creating a duplicate.

Auth: None. Rate limit: 5 subscribes/hour per IP (separate bucket from scan rate limit).
Content-Type: application/json
Body Parameters: - url (string, required) — URL or domain to monitor - email (string, required) — Alert destination email

Example:

curl -X POST https://cloviscan.com/api/monitor/subscribe \
  -H "Content-Type: application/json" \
  -d '{"url": "example.com", "email": "[email protected]"}'

Response (new subscription):

{ "ok": true, "domain": "example.com", "email": "[email protected]", "message": "Subscribed to weekly monitoring alerts." }

Response (already subscribed):

{ "ok": true, "domain": "example.com", "email": "[email protected]", "message": "Already subscribed to monitoring alerts.", "already_subscribed": true }

Errors: - 400url or email missing, or invalid values - 429 — rate limit exceeded - 500 — database error


GET /api/monitor/subscriptions

Returns all active monitoring subscriptions (internal/admin use — no auth gate currently enforced).

Auth: None (no auth gate in current code)
Query Parameters: None

Example:

curl https://cloviscan.com/api/monitor/subscriptions

Response:

{
  "subscriptions": [
    { "domain": "example.com", "email": "[email protected]", "created_at": "2026-06-16T12:00:00.000Z" }
  ]
}


Detonation Screenshot Proxy

GET /api/detonation/screenshot/:id

Proxies a screenshot PNG from the internal CloviBrowse service so report <img> tags can reference it on-domain. Validates the :id path parameter against [a-zA-Z0-9_-]{8,64} to prevent path traversal.

Auth: None (public — uses internal X-Clovi-Secret header for the upstream CloviBrowse request)
Path Parameters: - id (string) — Screenshot ID (8–64 chars, alphanumeric + _-)

Example:

curl https://cloviscan.com/api/detonation/screenshot/abc12345 --output screenshot.png

Response: PNG image (Content-Type: image/png; Cache-Control: public, max-age=3600)

Errors: - 404 — invalid ID format or screenshot not found in CloviBrowse - 504 — upstream timeout (10 seconds)


Health Check

GET /api/health

Health check endpoint.

Auth: None

Example:

curl https://cloviscan.com/api/health

Response:

{ "status": "ok", "service": "cloviscan", "port": 8983, "ts": "2026-06-16T12:00:00.000Z" }


CloviTrust Badge Routes

Mounted via clovitrust.js router at the application root.

GET /badge/:check

Returns a live status badge for a domain, powered by CloviScan scan data. Supports HTML (default), JSON, SVG, and redirect formats.

Auth: None (CORS: *)
Cache-Control: public, max-age=300
Path Parameters: - check (string) — Badge check type (see CHECKS registry in clovitrust.js)

Query Parameters: - domain or src or url (string, required) — Domain to badge - format (string, optional) — "html" (default), "json", "svg", or "redirect"

Example:

# JSON format
curl "https://cloviscan.com/badge/security?domain=example.com&format=json"

Response (format=json):

{
  "check": "security",
  "domain": "example.com",
  "color": "#22c55e",
  "label": "Security",
  "by": "CloviScan",
  "report": "https://cloviscan.com/report?src=example.com",
  "state": "pass",
  "text": "A+"
}

Errors: - 400domain missing - 404 — unknown or disabled check type


GET /clovitrust.js

Embeddable trust badge widget JavaScript.

Auth: None (CORS: *)
Cache-Control: public, max-age=600
Content-Type: application/javascript


GET /clovitrust.css

Shared badge CSS for standalone inclusion.

Auth: None (CORS: *)
Cache-Control: public, max-age=600
Content-Type: text/css


GET /clovitrust/demo

Standalone design demo page for the CloviTrust badge widget.

Auth: None (CORS: *)
Content-Type: text/html


Rate Limiting Summary

Route group Limit Bucket
POST /api/scan, POST /api/scan/github, POST /api/scan/verify 10/hour per IP shared scan bucket
POST /api/monitor/subscribe 5/hour per IP separate subscribe bucket
All other routes No rate limit

Privacy & Egress Architecture

All outbound requests to scanned target sites are routed through an egress proxy (configured via CLOVISCAN_EGRESS_PROXY env var) so the target sees a VPN IP, not the server's real IP. This is fail-closed: if the proxy is configured but broken, target fetches refuse rather than falling back to direct egress.

Google API calls (PageSpeed, Safe Browsing) are NOT proxied — they are calls from CloviScan to Google, not to the scanned target, so proxying them provides no privacy benefit.