API Documentation

REST API for programmatic scanning. The same sk-sec- key works across curl, Python, JavaScript, MCP, ChatGPT Actions, and GitHub Copilot.
Don't have a key yet? Sign up (free) and generate one in under 30 seconds. Your first scan is on us.
Get an API key →

Contents

Quickstart — scan to fix in 3 calls

The common flow: trigger a scan, poll for completion, download the fix file. Takes 2-5 minutes end to end.

# 1. Trigger a scan
curl -X POST https://securityscanner.dev/v1/scan \
  -H "Authorization: Bearer sk-sec-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"host": "https://myapp.com"}'

# Response:
# {"run_id": "abc12345", "status": "started", ...}

# 2. Poll for results (scan takes 2-5 minutes)
curl https://securityscanner.dev/v1/scan/abc12345 \
  -H "Authorization: Bearer sk-sec-YOUR_KEY"

# 3. Download the fix file
curl https://securityscanner.dev/v1/scan/abc12345/fix \
  -H "Authorization: Bearer sk-sec-YOUR_KEY" \
  -o SECURITY-FIX.md
import httpx, time

API = "https://securityscanner.dev"
KEY = "sk-sec-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {KEY}"}

# 1. Trigger
r = httpx.post(f"{API}/v1/scan", headers=HEADERS,
               json={"host": "https://myapp.com"})
run_id = r.json()["run_id"]

# 2. Poll
while True:
    status = httpx.get(f"{API}/v1/scan/{run_id}", headers=HEADERS).json()
    if status["status"] == "completed":
        break
    time.sleep(10)

print(f"Found {status['summary']['total']} findings")
for f in status["findings"]:
    print(f"  [{f['severity']}] {f['title']}")

# 3. Fix file
fix = httpx.get(f"{API}/v1/scan/{run_id}/fix", headers=HEADERS).text
open("SECURITY-FIX.md", "w").write(fix)
const API = "https://securityscanner.dev";
const KEY = "sk-sec-YOUR_KEY";
const H = { Authorization: `Bearer ${KEY}` };

// 1. Trigger
const started = await fetch(`${API}/v1/scan`, {
  method: "POST",
  headers: { ...H, "Content-Type": "application/json" },
  body: JSON.stringify({ host: "https://myapp.com" }),
}).then(r => r.json());

const runId = started.run_id;

// 2. Poll
let status;
while (true) {
  status = await fetch(`${API}/v1/scan/${runId}`, { headers: H }).then(r => r.json());
  if (status.status === "completed") break;
  await new Promise(r => setTimeout(r, 10000));
}

console.log(`Found ${status.summary.total} findings`);
for (const f of status.findings) {
  console.log(`  [${f.severity}] ${f.title}`);
}

// 3. Fix file
const fix = await fetch(`${API}/v1/scan/${runId}/fix`, { headers: H }).then(r => r.text());
require("fs").writeFileSync("SECURITY-FIX.md", fix);
The response of GET /v1/scan/{run_id} streams partial results while the scan runs — you can start displaying findings before the scan finishes.

Authentication

All endpoints require a bearer token in the Authorization header. Generate keys at /keys.

Authorization: Bearer sk-sec-YOUR_KEY

Keys are scoped to your account. Every scan you trigger counts against your plan. You can have multiple keys; revoke individually at /keys.

The same key works for: REST API, MCP server, ChatGPT Custom Actions. (GitHub Copilot Extension and Vercel Integration are coming once the marketplace listings are approved.)

Start a scan

POST/v1/scan Async · returns immediately

Scans a single URL or IP. Auto-creates the target if it doesn't exist. Returns a run_id; the scan runs in the background (2-5 minutes).

Request body

FieldTypeRequiredDescription
hoststringyesURL or hostname (with or without scheme)
labelstringnoFree-text label (e.g. "production")

Example

curl -X POST https://securityscanner.dev/v1/scan \
  -H "Authorization: Bearer sk-sec-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"host": "https://myapp.com", "label": "production"}'
import httpx
r = httpx.post(
    "https://securityscanner.dev/v1/scan",
    headers={"Authorization": "Bearer sk-sec-YOUR_KEY"},
    json={"host": "https://myapp.com", "label": "production"},
)
print(r.json())
const r = await fetch("https://securityscanner.dev/v1/scan", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk-sec-YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ host: "https://myapp.com", label: "production" }),
});
console.log(await r.json());

Response (201)

{
  "run_id": "abc12345",
  "status": "started",
  "target": "myapp.com",
  "check_status_url": "https://securityscanner.dev/v1/scan/abc12345"
}

Get scan status & findings

GET/v1/scan/{run_id}

Returns status (running, completed, aborted, failed), summary counts, and every finding emitted so far.

Example

curl https://securityscanner.dev/v1/scan/abc12345 \
  -H "Authorization: Bearer sk-sec-YOUR_KEY" 
import httpx
r = httpx.get(
    "https://securityscanner.dev/v1/scan/abc12345",
    headers={"Authorization": "Bearer sk-sec-YOUR_KEY"},
)
print(r.json())
const r = await fetch("https://securityscanner.dev/v1/scan/abc12345", {
  headers: { "Authorization": "Bearer sk-sec-YOUR_KEY" },
});
console.log(await r.json());

Response (while running)

{
  "run_id": "abc12345",
  "status": "running",
  "started_at": "2026-04-12T10:00:00+00:00",
  "summary": {"total": 5, "critical": 1, "high": 2, "medium": 2},
  "findings": [ ... partial results ... ]
}

Response (completed)

{
  "run_id": "abc12345",
  "status": "completed",
  "started_at": "2026-04-12T10:00:00+00:00",
  "finished_at": "2026-04-12T10:03:42+00:00",
  "summary": {"total": 12, "critical": 1, "high": 3, "medium": 5, "low": 3, "info": 0},
  "findings": [
    {
      "target": "myapp.com",
      "severity": "CRITICAL",
      "category": "secrets",
      "title": "Anthropic API key exposed at /main.js",
      "description": "Secret pattern matched. Rotate immediately.",
      "evidence": "Found: sk-ant-api03-...",
      "tool": "secret-scan"
    }
  ],
  "fix_url": "https://securityscanner.dev/v1/scan/abc12345/fix"
}

Finding fields

FieldDescription
severityCRITICAL / HIGH / MEDIUM / LOW / INFO
categorysecrets, auth, baas, api, cloud, network, tls, dns, edge-infra, privacy, disclosure, ai-safety
toolModule that produced the finding — useful for filtering and deduplication
evidenceRaw evidence snippet. Truncated to 200-500 bytes per finding.

Download fix file

GET/v1/scan/{run_id}/fix

Returns a SECURITY-FIX.md with YAML frontmatter and numbered fix instructions. Drop it into your repo; Claude Code, Cursor, and Cline will read it and apply fixes.

Query params

NameTypeDescription
targetstringFilter to one target's findings (multi-target runs)
formatauto | legacyDefault is auto (security-fix/v1 frontmatter)

Response format

---
format: security-fix/v1
scanner: securityscanner.dev
scan_id: abc12345
scan_date: "2026-04-12"
targets:
  - host: myapp.com
    risk_grade: F
    severity_counts: {critical: 1, high: 3}
---

# Security Fixes: myapp.com

## FIX-1: Rotate exposed Anthropic API key [CRITICAL]
...

AI analysis

POST/v1/scan/{run_id}/analyze

Triggers a structured Sonnet analysis over the run: executive summary, attack chains, risk score (0-100), and prioritized remediation. Cached per run.

{
  "content": "# Security Assessment\n\n## Executive Summary\n...",
  "model": "claude-sonnet-4-6",
  "risk_score": 74,
  "cached": false
}

Manage targets

# List all targets
curl https://securityscanner.dev/v1/targets \
  -H "Authorization: Bearer sk-sec-YOUR_KEY"

# Add a new target
curl -X POST https://securityscanner.dev/v1/targets \
  -H "Authorization: Bearer sk-sec-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"host": "https://staging.myapp.com", "label": "staging"}' 
import httpx
h = {"Authorization": "Bearer sk-sec-YOUR_KEY"}

# List
targets = httpx.get("https://securityscanner.dev/v1/targets", headers=h).json()

# Add
httpx.post("https://securityscanner.dev/v1/targets", headers=h,
           json={"host": "https://staging.myapp.com", "label": "staging"})
const h = { "Authorization": "Bearer sk-sec-YOUR_KEY" };

// List
const targets = await fetch("https://securityscanner.dev/v1/targets", { headers: h }).then(r => r.json());

// Add
await fetch("https://securityscanner.dev/v1/targets", {
  method: "POST",
  headers: { ...h, "Content-Type": "application/json" },
  body: JSON.stringify({ host: "https://staging.myapp.com", label: "staging" }),
});

POST /v1/targets body: {"host": "...", "label": "..."}. DELETE /api/targets/{id} removes one.

List scan history

GET/v1/runs

Returns up to the last 50 runs, most recent first. Each item includes run_id, target, status, started_at, finished_at, and summary counts.

curl https://securityscanner.dev/v1/runs \
  -H "Authorization: Bearer sk-sec-YOUR_KEY" 
import httpx
runs = httpx.get(
    "https://securityscanner.dev/v1/runs",
    headers={"Authorization": "Bearer sk-sec-YOUR_KEY"},
).json()
for r in runs["runs"]:
    print(r["id"], r["target"], r["summary"]["total"])
const data = await fetch("https://securityscanner.dev/v1/runs", {
  headers: { "Authorization": "Bearer sk-sec-YOUR_KEY" },
}).then(r => r.json());
data.runs.forEach(r => console.log(r.id, r.target, r.summary.total));

Schedule recurring scans

POST/api/monitors

Automate scans on a schedule. Supports email + webhook alerts on CRITICAL/HIGH findings and certificate-expiry warnings.

{
  "target": "https://myapp.com",
  "frequency": "daily|weekly",
  "alert_email": "[email protected]",
  "alert_webhook": "https://hooks.slack.com/...",
  "alert_on_cert_expiry_days": 30
}

GitHub repo scan

POST/api/github/scan

Shallow-clones a repo and scans for secrets + dependency CVEs (npm-audit, pip-audit) + Terraform IaC misconfigs.

{"repo_url": "https://github.com/owner/repo", "github_token": "ghp_..."}

Mobile app scan

POST/api/mobile/scan

Upload an IPA or APK (max 200 MB, multipart/form-data). Scans for hardcoded secrets, cleartext traffic, ATS bypass, exposed API endpoints.

curl -H "Authorization: Bearer sk-sec-YOUR_KEY" \
     -F "[email protected]" \
     https://securityscanner.dev/api/mobile/scan

Error codes

StatusWhenResponse shape
200 / 201OKEndpoint-specific
400Invalid input (missing field, bad URL){"error": "..."}
401Missing or invalid API key{"error": "unauthorized"}
402Plan limit reached{"error": "...", "upgrade_url": "https://securityscanner.dev/billing"}
404Not found, or not your resource{"error": "not found"}
409Target already exists{"error": "target exists", "target_id": "..."}
429Rate limit{"error": "rate limit", "retry_after": 60}
500Scanner-internal error{"error": "internal"} — email [email protected] with the run_id

Plans & rate limits

PlanPriceTargetsScansAI analysisMonitors
Free$011 lifetime
Pay-as-you-go$9 / scan5per credit
Monthly$29 / month15 per week
Pro$99 / month1050 per day

Rate limit responses include a retry_after (seconds). The scanner batches gracefully — if you need higher throughput for a one-time backfill, email [email protected].

SDKs & integrations

Tip: For interactive exploration, import /v1/openapi.json into Postman, Insomnia, Bruno, or any OpenAPI viewer.