Authentication
All API requests must include your API key in the X-Shieldome-Key header. Generate keys in Account → API Keys.
curl -H "X-Shieldome-Key: sk_live_xxxxxxxxxxxx" \
https://app.shieldome.com/api/scans
Complete example (copy, paste, change the two values)
End-to-end against the stable /api/v1/ surface: start a scan, poll until it finishes, download the PDF, and fail CI on High+ findings. Change only API_KEY and TARGET.
#!/usr/bin/env bash
set -euo pipefail
API_KEY="sk_live_xxxxxxxxxxxxxxxx" # ← your key from Account → API Keys
TARGET="https://example.com" # ← the site to scan
BASE="https://app.shieldome.com"
# 1. Start the scan
SCAN_ID=$(curl -sf -X POST "$BASE/api/v1/scan" \
-H "X-Shieldome-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"target\":\"$TARGET\",\"scan_type\":\"vuln\"}" | jq -r .scan_id)
echo "scan: $SCAN_ID"
# 2. Poll until completed or failed
while true; do
STATUS=$(curl -sf "$BASE/api/v1/scan/$SCAN_ID" -H "X-Shieldome-Key: $API_KEY" | jq -r .status)
echo "status: $STATUS"
[ "$STATUS" = "completed" ] && break
[ "$STATUS" = "failed" ] && { echo "scan failed"; exit 1; }
sleep 10
done
# 3. Download the PDF report (includes the AI narratives)
curl -sf "$BASE/api/scan/$SCAN_ID/report" -H "X-Shieldome-Key: $API_KEY" -o report.pdf
echo "saved report.pdf"
# 4. CI gate: exit 1 if any High+ finding exists
PASSED=$(curl -sf "$BASE/api/v1/scan/$SCAN_ID/gate?fail_on=high" -H "X-Shieldome-Key: $API_KEY" | jq -r .passed)
[ "$PASSED" = "true" ] || { echo "gate failed"; exit 1; }
echo "gate passed"
import time, requests
API_KEY = "sk_live_xxxxxxxxxxxxxxxx" # ← your key
TARGET = "https://example.com" # ← the site to scan
BASE = "https://app.shieldome.com"
H = {"X-Shieldome-Key": API_KEY}
# 1. Start the scan
scan_id = requests.post(f"{BASE}/api/v1/scan", headers=H,
json={"target": TARGET, "scan_type": "vuln"}).json()["scan_id"]
# 2. Poll until done
while True:
status = requests.get(f"{BASE}/api/v1/scan/{scan_id}", headers=H).json()["status"]
if status == "completed":
break
if status == "failed":
raise SystemExit("scan failed")
time.sleep(10)
# 3. Download the PDF report (includes AI narratives)
pdf = requests.get(f"{BASE}/api/scan/{scan_id}/report", headers=H)
open("report.pdf", "wb").write(pdf.content)
# 4. Read the results JSON (findings nested at results.vulnerabilities)
data = requests.get(f"{BASE}/api/v1/scan/{scan_id}", headers=H).json()
print(len(data.get("vulnerabilities", [])), "findings")
The interactive, always-current reference is at /api/docs (Swagger UI), generated from /api/openapi.json.
Batch scanning (multiple sites in one flow)
Submit up to 50 URLs in one call to POST /api/scan/bulk, poll the job, then pull each PDF. Change only API_KEY and the URL list.
#!/usr/bin/env bash
set -euo pipefail
API_KEY="sk_live_xxxxxxxxxxxxxxxx" # ← your key
BASE="https://app.shieldome.com"
# 1. Start a batch of up to 50 URLs
JOB=$(curl -sf -X POST "$BASE/api/scan/bulk" \
-H "X-Shieldome-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"urls":["https://a.example.com","https://b.example.com","https://c.example.com"]}' | jq -r .job_id)
echo "batch job: $JOB"
# 2. Poll the batch until it is done
while true; do
S=$(curl -sf "$BASE/api/scan/bulk/$JOB" -H "X-Shieldome-Key: $API_KEY")
echo "$S" | jq -r '"\(.status) \(.completed)/\(.total)"'
[ "$(echo "$S" | jq -r .status)" = "done" ] && break
sleep 15
done
# 3. Download every scan's PDF
for SID in $(curl -sf "$BASE/api/scan/bulk/$JOB" -H "X-Shieldome-Key: $API_KEY" | jq -r '.scan_ids[]'); do
curl -sf "$BASE/api/scan/$SID/report" -H "X-Shieldome-Key: $API_KEY" -o "report-$SID.pdf"
done
Scheduled scans (recurring)
Create a recurring scan with POST /api/scheduled-scans. interval_hours ranges from 1 to 8760 (1 hour to 1 year); results can be pushed to a webhook and/or emailed. Scheduling requires a Gold-tier plan.
#!/usr/bin/env bash
set -euo pipefail
API_KEY="sk_live_xxxxxxxxxxxxxxxx" # ← your key
BASE="https://app.shieldome.com"
# Create a weekly scan (168h) with webhook + email notification
curl -sf -X POST "$BASE/api/scheduled-scans" \
-H "X-Shieldome-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"target_url": "https://example.com",
"scan_type": "both",
"interval_hours": 168,
"notify_email": "[email protected]",
"webhook_url": "https://hooks.example.com/shieldome"
}' | jq .
# List your schedules
curl -sf "$BASE/api/scheduled-scans" -H "X-Shieldome-Key: $API_KEY" | jq .
# Delete one (uncomment and set the id)
# curl -sf -X DELETE "$BASE/api/scheduled-scans/SCHED_ID" -H "X-Shieldome-Key: $API_KEY"
Idempotency & deduplication
If you POST /api/scan for a target that was already scanned within the last 15 minutes, the API does not start a new scan - it returns the existing one so retries and overlapping jobs do not burn quota. The response then carries a message field and the prior scan_id/status:
{
"scan_id": "a1b2c3d4-...",
"status": "completed",
"started_at": "2026-10-10T08:00:00Z",
"message": "This target was already scanned within the last 15 minutes."
}
If the response has a message, treat the scan_id as an existing result rather than a fresh scan. To force a new scan, wait out the 15-minute window.
Rate limits & quota
| Endpoint | Limit |
|---|---|
POST /api/scan | 10 requests / minute |
POST /api/scan/bulk | 3 requests / minute |
POST /api/scan/from-openapi | 10 requests / minute |
Exceeding a limit returns HTTP 429. Separately, your plan caps how many scans and concurrent jobs you may run; when a cap is hit the API returns a 4xx with a machine-readable code (see below). Always check the HTTP status and the code field before retrying, and back off on 429.
Error reference
Errors return a JSON body {"error": "...", "code": "..."}. Branch on code, not on the human error text.
| HTTP | code | Meaning & what to do |
|---|---|---|
| 400 | target_not_allowed | Target failed validation or is not an authorized domain. Verify/authorize the domain first. |
| 403 | no_active_plan | No active subscription. Activate a plan. |
| 403 | authorization_required | Scan authorization for this target is not confirmed. Confirm you are authorized to scan it. |
| 403 | asset_limit_reached | Your plan's asset/URL limit is reached. Remove assets or upgrade. |
| 403 | ticket_required | This action needs a support ticket. Open one from the dashboard. |
| 409 | replace_locked | URL replacement is temporarily locked. Wait for the cooldown. |
| 429 | concurrent_limit | Too many scans running at once. Wait for one to finish or run fewer in parallel. |
| 429 | domain_cooldown | This domain was scanned too recently. Wait for the cooldown window. |
Webhooks (get notified instead of polling)
Pass webhook_url when you start a scan and Shieldome POSTs to it on completion - no polling needed. Slack, Discord and Microsoft Teams URLs are auto-detected and formatted; any other URL receives this generic JSON:
{
"event": "scan_completed",
"scan_id": "a1b2c3d4-...",
"target_url": "https://example.com",
"risk_score": 72,
"vulnerability_counts": { "critical": 0, "high": 1, "medium": 2, "low": 7, "info": 30 },
"total_findings": 10,
"timestamp": "2026-10-10T08:05:00Z"
}
A minimal receiver that fetches the full report when the webhook arrives:
from flask import Flask, request
import requests
app = Flask(__name__)
API_KEY = "sk_live_xxxxxxxxxxxxxxxx"
BASE = "https://app.shieldome.com"
@app.post("/shieldome-webhook")
def hook():
e = request.json
if e.get("event") == "scan_completed":
pdf = requests.get(f"{BASE}/api/scan/{e['scan_id']}/report",
headers={"X-Shieldome-Key": API_KEY})
open(f"report-{e['scan_id']}.pdf", "wb").write(pdf.content)
return "", 204
The finding object
In scan results, findings are an array at results.vulnerabilities. Each finding looks like this - branch on severity and status, and read the AI fields when present:
{
"name": "Security Headers Grade: D",
"severity": "high", // critical | high | medium | low | info
"status": "vulnerable", // vulnerable | warning | secure | info
"confidence": "confirmed", // confirmed | likely | possible
"owasp_id": "A05:2021",
"cwe_id": "CWE-693",
"cvss_score": 7.5,
"description": "...",
"evidence": "...",
"remediation": "...",
"ai_triage_score": 100, // 0-100 exploitability rank
"ai_remediation": "...", // present on actionable findings
"ai_pentest_narrative": "...",
"due_date": "2026-11-09T...",
"days_remaining": 29
}
status == "vulnerable" (or severity), not by array length - the array also contains secure and info rows that document passed checks.CI/CD pipelines
Use the stable /api/v1/ surface and the gate endpoint to fail a build on High+ findings. Store your key as a secret named SHIELDOME_API_KEY.
name: Shieldome Scan
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Scan and gate on High+
env:
KEY: ${{ secrets.SHIELDOME_API_KEY }}
BASE: https://app.shieldome.com
TARGET: https://example.com
run: |
SID=$(curl -sf -X POST "$BASE/api/v1/scan" -H "X-Shieldome-Key: $KEY" \
-H "Content-Type: application/json" -d "{\"target\":\"$TARGET\"}" | jq -r .scan_id)
until [ "$(curl -sf "$BASE/api/v1/scan/$SID" -H "X-Shieldome-Key: $KEY" | jq -r .status)" != "running" ]; do sleep 10; done
test "$(curl -sf "$BASE/api/v1/scan/$SID/gate?fail_on=high" -H "X-Shieldome-Key: $KEY" | jq -r .passed)" = "true"
shieldome_scan:
image: alpine
variables:
BASE: "https://app.shieldome.com"
TARGET: "https://example.com"
before_script:
- apk add --no-cache curl jq
script:
- SID=$(curl -sf -X POST "$BASE/api/v1/scan" -H "X-Shieldome-Key: $SHIELDOME_API_KEY" -H "Content-Type: application/json" -d "{\"target\":\"$TARGET\"}" | jq -r .scan_id)
- until [ "$(curl -sf "$BASE/api/v1/scan/$SID" -H "X-Shieldome-Key: $SHIELDOME_API_KEY" | jq -r .status)" != "running" ]; do sleep 10; done
- test "$(curl -sf "$BASE/api/v1/scan/$SID/gate?fail_on=high" -H "X-Shieldome-Key: $SHIELDOME_API_KEY" | jq -r .passed)" = "true"
withCredentials([string(credentialsId: 'shieldome-api-key', variable: 'KEY')]) {
sh '''
BASE=https://app.shieldome.com; TARGET=https://example.com
SID=$(curl -sf -X POST "$BASE/api/v1/scan" -H "X-Shieldome-Key: $KEY" -H "Content-Type: application/json" -d "{\\"target\\":\\"$TARGET\\"}" | jq -r .scan_id)
until [ "$(curl -sf "$BASE/api/v1/scan/$SID" -H "X-Shieldome-Key: $KEY" | jq -r .status)" != "running" ]; do sleep 10; done
test "$(curl -sf "$BASE/api/v1/scan/$SID/gate?fail_on=high" -H "X-Shieldome-Key: $KEY" | jq -r .passed)" = "true"
'''
}
Authenticated scanning
To scan behind a login, pass auth_config in the POST /api/scan body. Three types:
// Cookie
{ "target_url": "https://example.com", "auth_config": { "type": "cookie", "cookie_string": "session=abc; csrftoken=def" } }
// Bearer token
{ "target_url": "https://example.com", "auth_config": { "type": "token", "token": "eyJhbGciOi..." } }
// Form login (headless browser)
{ "target_url": "https://example.com",
"auth_config": { "type": "form", "login_url": "https://example.com/login",
"username": "[email protected]", "password": "secret",
"success_url_contains": "/dashboard" } }
Exports for other tools
After a scan completes, pull it in the format your tooling expects. All require the X-Shieldome-Key header.
| Endpoint | Use with |
|---|---|
GET /api/scan/{scan_id}/sarif | GitHub Code Scanning (upload the SARIF) |
GET /api/scan/{scan_id}/export/junit.xml | CI test-report viewers (Jenkins, GitLab) |
GET /api/scan/{scan_id}/export/burp | Burp Suite import |
GET /api/scan/{scan_id}/export/stix | Threat-intel platforms (STIX) |
GET /api/scan/{scan_id}/report / /docx | PDF / Word report |
Pagination
GET /api/scans is paginated. Use page (default 1) and per_page (default 20, max 100), optionally with status, search or tag filters.
curl -sf "$BASE/api/scans?status=completed&page=1&per_page=100" \ -H "X-Shieldome-Key: $API_KEY" | jq '.[].id'
Scans
Start a new scan.
{
"target_url": "https://example.com", // required
"scan_type": "both", // vuln | perf | both | api
"target_ip": "10.0.1.42", // optional IP override
"excluded_paths": "/admin\n/logout" // optional, newline-delimited string
}
{ "scan_id": "uuid", "status": "running" }
Authentication & access control fields
| Field | Type | Description |
|---|---|---|
auth_config | object | Authentication configuration. Requires a type field. Cookie: {"type":"cookie","cookie_string":"..."}. Bearer token: {"type":"token","token":"..."}. Playwright form login: {"type":"form","login_url":"...","username":"...","password":"...","success_url_contains":"..."}. See Authenticated Scanning. |
custom_headers | object | Additional HTTP headers sent with every request during the scan. Keys are header names, values are header values. |
excluded_paths | string | URL path prefixes to skip during crawling and scanning, one per line. E.g. "/logout\n/admin/delete". |
Scan behaviour fields
| Field | Type | Default | Description |
|---|---|---|---|
scan_speed | string | "normal" | Scan speed mode: "normal", "cautious", or "stealth". See Scan Speed & Stealth Mode. |
request_delay_ms | integer | 0 | Fixed delay in milliseconds between requests, 0-5000. Overrides the speed mode's default delay. |
tags | array of strings | [] | Labels attached to this scan for filtering. E.g. ["production", "sprint-42"]. |
webhook_url | string | null | URL to POST scan results to when the scan completes. See Webhooks. |
Get the status and full results of a scan. Poll this endpoint until status is completed or failed. While a scan is running, the results field is null.
{
"id": "a1b2c3d4-e5f6-...",
"target_url": "https://example.com",
"target_ip": null, // IP override if set
"scan_type": "both", // vuln | perf | both | api
"status": "completed", // running | completed | failed
"tags": ["production"],
"notes": "",
"scan_authorized": true,
"started_at": "2026-06-14T10:00:00",
"completed_at": "2026-06-14T10:07:42",
"summary": {
"risk_score": 42, // 0-100, higher = more risk
"critical": 1,
"high": 3,
"medium": 5,
"low": 2,
"info": 1
},
"results": {
"vulnerabilities": [ // array of finding objects (vuln/both/api scans)
{
"title": "SQL Injection Indicator",
"category": "Injection",
"owasp_id": "A03:2021",
"severity": "critical", // critical|high|medium|low|info
"status": "vulnerable", // vulnerable|safe|info
"description": "Error-based SQL injection indicator detected.",
"evidence": "Response contained: You have an error in your SQL syntax",
"remediation": "Use parameterised queries or prepared statements.",
"cvss_score": 9.8,
"cwe_id": "CWE-89"
}
],
"performance": { // present for both/perf scans; null otherwise
"dns_ms": 12,
"ttfb_ms": 210,
"page_size_kb": 84,
"http2": true,
"gzip": true,
"performance_score": 87
},
"tech_stack": ["nginx/1.24", "Django"]
}
}
results.vulnerabilities, not at the root level. Code that reads a top-level findings key will get undefined.Server-Sent Events stream for real-time scan progress. Each event is a JSON object with type (progress/finding/done/error) and a data payload.
curl -H "X-Shieldome-Key: YOUR_KEY" \ -H "Accept: text/event-stream" \ /api/scan/{scan_id}/progress
Gracefully stop a running scan.
Delete a scan and its results from history.
List scans with optional filtering. Supports query parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | running, completed, or failed |
search | string | Filter by target URL substring |
tag | string | Filter by tag label |
page | int | Page number (default: 1) |
per_page | int | Results per page, max 100 (default: 20) |
{
"items": [...], // array of scan objects (same shape as GET /api/scan/{id})
"total": 47,
"page": 1,
"per_page": 20,
"pages": 3
}
Developer API (v1)
The /api/v1/ prefix is the stable, versioned surface for CI/CD tools, the GitHub Action, and third-party integrations. Authentication is the same X-Shieldome-Key header.
Trigger a new scan and get back a scan_id immediately. The scan runs asynchronously.
{
"target_url": "https://example.com", // required
"scan_type": "vuln", // vuln | perf | both | api
"custom_ip": "10.0.1.42" // optional IP override
}
{
"scan_id": "a1b2c3d4-...",
"status": "running",
"status_url": "/api/v1/scan/a1b2c3d4-...",
"sarif_url": "/api/v1/scan/a1b2c3d4-.../sarif"
}
Poll for scan status and results. Returns findings once status is completed.
{
"scan_id": "a1b2c3d4-...",
"target": "https://example.com",
"status": "completed", // running | completed | failed
"summary": {
"critical": 0, "high": 2, "medium": 5,
"low": 1, "info": 3, "risk_score": 38
},
"findings": [...] // absent while running; actionable findings only
}
List the last 50 scans for the authenticated user, newest first.
Export findings as SARIF 2.1.0 - same as /api/scan/{id}/sarif, exposed here for GitHub Action compatibility.
Reports & Exports
Download scan report as PDF. Returns application/pdf.
Download report as Word document (.docx).
Export findings as SARIF 2.1.0 - for GitHub Advanced Security, Azure DevOps, and other compatible platforms.
Export all scan findings as CSV - one row per finding across all scans.
Generate a combined PDF for multiple scans. Body: {"scan_ids": ["id1","id2"]}.
Batch Jobs
Start a batch scan job for multiple targets.
{
"targets": ["https://site1.com", "https://site2.com"],
"scan_type": "both"
}
Get batch job status and list of individual scan IDs.
Scheduled Scans
List all scheduled scan configurations for the authenticated account.
{
"target_url": "https://example.com", // required
"scan_type": "both", // vuln | perf | both | api
"interval_hours": 24 // hours between runs (min 1)
}
Toggle the enabled/disabled state of a schedule. No request body required.
Delete a scheduled scan. Past scan history is preserved.
Scan Comparison
Compare a scan with the most recent previous scan of the same target. Returns new, fixed, and unchanged finding counts.
{
"has_previous": true,
"previous_scan_id": "uuid",
"previous_scan_date": "2024-01-01T12:00:00",
"new_count": 2,
"fixed_count": 1,
"new_findings": [...]
}
Side-by-side diff of any two scan IDs. Returns lists of new, fixed, and common findings with optional AI summary.
{
"scan1": { "id": "...", "target_url": "...", "date": "..." },
"scan2": { "id": "...", "target_url": "...", "date": "..." },
"new_in_scan2": [...],
"fixed_in_scan2": [...],
"common": [...],
"new_count": 3,
"fixed_count": 1,
"ai_diff_summary": "..."
}
Security Badge
Returns an SVG badge showing the latest security grade for a domain. Embed in README files or websites.
