Docs

Authentication

All API requests must include your API key in the X-Shieldome-Key header. Generate keys in Account → API Keys.

bash
curl -H "X-Shieldome-Key: sk_live_xxxxxxxxxxxx" \
     https://app.shieldome.com/api/scans
⚠️
Keep API keys secret. Never expose them in client-side code or public repositories.

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.

bash
#!/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"
python
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.

bash
#!/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.

bash
#!/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:

json
{
  "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

EndpointLimit
POST /api/scan10 requests / minute
POST /api/scan/bulk3 requests / minute
POST /api/scan/from-openapi10 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.

HTTPcodeMeaning & what to do
400target_not_allowedTarget failed validation or is not an authorized domain. Verify/authorize the domain first.
403no_active_planNo active subscription. Activate a plan.
403authorization_requiredScan authorization for this target is not confirmed. Confirm you are authorized to scan it.
403asset_limit_reachedYour plan's asset/URL limit is reached. Remove assets or upgrade.
403ticket_requiredThis action needs a support ticket. Open one from the dashboard.
409replace_lockedURL replacement is temporarily locked. Wait for the cooldown.
429concurrent_limitToo many scans running at once. Wait for one to finish or run fewer in parallel.
429domain_cooldownThis 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:

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:

python
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:

json
{
  "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
}
ℹ️
Count real issues by 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.

yaml (GitHub Actions)
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"
yaml (GitLab CI)
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"
groovy (Jenkins)
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:

json
// 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.

EndpointUse with
GET /api/scan/{scan_id}/sarifGitHub Code Scanning (upload the SARIF)
GET /api/scan/{scan_id}/export/junit.xmlCI test-report viewers (Jenkins, GitLab)
GET /api/scan/{scan_id}/export/burpBurp Suite import
GET /api/scan/{scan_id}/export/stixThreat-intel platforms (STIX)
GET /api/scan/{scan_id}/report / /docxPDF / 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.

bash
curl -sf "$BASE/api/scans?status=completed&page=1&per_page=100" \
  -H "X-Shieldome-Key: $API_KEY" | jq '.[].id'

Scans

POST /api/scan

Start a new scan.

request body
{
  "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
}
response
{ "scan_id": "uuid", "status": "running" }

Authentication & access control fields

FieldTypeDescription
auth_configobjectAuthentication 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_headersobjectAdditional HTTP headers sent with every request during the scan. Keys are header names, values are header values.
excluded_pathsstringURL path prefixes to skip during crawling and scanning, one per line. E.g. "/logout\n/admin/delete".

Scan behaviour fields

FieldTypeDefaultDescription
scan_speedstring"normal"Scan speed mode: "normal", "cautious", or "stealth". See Scan Speed & Stealth Mode.
request_delay_msinteger0Fixed delay in milliseconds between requests, 0-5000. Overrides the speed mode's default delay.
tagsarray of strings[]Labels attached to this scan for filtering. E.g. ["production", "sprint-42"].
webhook_urlstringnullURL to POST scan results to when the scan completes. See Webhooks.
GET /api/scan/{scan_id}

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.

response
{
  "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"]
  }
}
⚠️
Findings are always nested at results.vulnerabilities, not at the root level. Code that reads a top-level findings key will get undefined.
GET /api/scan/{scan_id}/progress

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.

bash
curl -H "X-Shieldome-Key: YOUR_KEY" \
     -H "Accept: text/event-stream" \
     /api/scan/{scan_id}/progress
POST /api/scan/{scan_id}/abort

Gracefully stop a running scan.

DELETE /api/scan/{scan_id}

Delete a scan and its results from history.

GET /api/scans

List scans with optional filtering. Supports query parameters:

ParameterTypeDescription
statusstringrunning, completed, or failed
searchstringFilter by target URL substring
tagstringFilter by tag label
pageintPage number (default: 1)
per_pageintResults per page, max 100 (default: 20)
response
{
  "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.

ℹ️
These endpoints are an alias layer over the internal API. They return simplified payloads and will remain stable across minor releases.
POST /api/v1/scan

Trigger a new scan and get back a scan_id immediately. The scan runs asynchronously.

request body
{
  "target_url": "https://example.com",  // required
  "scan_type":  "vuln",               // vuln | perf | both | api
  "custom_ip":  "10.0.1.42"           // optional IP override
}
response - 202 Accepted
{
  "scan_id":   "a1b2c3d4-...",
  "status":    "running",
  "status_url": "/api/v1/scan/a1b2c3d4-...",
  "sarif_url":  "/api/v1/scan/a1b2c3d4-.../sarif"
}
GET /api/v1/scan/{scan_id}

Poll for scan status and results. Returns findings once status is completed.

response
{
  "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
}
GET /api/v1/scans

List the last 50 scans for the authenticated user, newest first.

GET /api/v1/scan/{scan_id}/sarif

Export findings as SARIF 2.1.0 - same as /api/scan/{id}/sarif, exposed here for GitHub Action compatibility.

Reports & Exports

GET /api/scan/{scan_id}/report

Download scan report as PDF. Returns application/pdf.

GET /api/scan/{scan_id}/docx

Download report as Word document (.docx).

GET /api/scan/{scan_id}/sarif

Export findings as SARIF 2.1.0 - for GitHub Advanced Security, Azure DevOps, and other compatible platforms.

GET /api/scans/export.csv

Export all scan findings as CSV - one row per finding across all scans.

POST /api/report/bulk

Generate a combined PDF for multiple scans. Body: {"scan_ids": ["id1","id2"]}.

Batch Jobs

POST /api/batch

Start a batch scan job for multiple targets.

request body
{
  "targets":   ["https://site1.com", "https://site2.com"],
  "scan_type": "both"
}
GET /api/batch/{batch_id}

Get batch job status and list of individual scan IDs.

Scheduled Scans

GET /api/schedules

List all scheduled scan configurations for the authenticated account.

POST /api/schedules
request body
{
  "target_url":     "https://example.com",  // required
  "scan_type":      "both",                 // vuln | perf | both | api
  "interval_hours": 24                     // hours between runs (min 1)
}
PUT /api/schedules/{id}/toggle

Toggle the enabled/disabled state of a schedule. No request body required.

DELETE /api/schedules/{id}

Delete a scheduled scan. Past scan history is preserved.

Scan Comparison

GET /api/scan/{scan_id}/compare

Compare a scan with the most recent previous scan of the same target. Returns new, fixed, and unchanged finding counts.

response
{
  "has_previous":       true,
  "previous_scan_id":   "uuid",
  "previous_scan_date": "2024-01-01T12:00:00",
  "new_count":          2,
  "fixed_count":        1,
  "new_findings":       [...]
}
GET /api/scan/compare-two?id1={id}&id2={id}

Side-by-side diff of any two scan IDs. Returns lists of new, fixed, and common findings with optional AI summary.

response
{
  "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

GET /badge/{domain}.svg

Returns an SVG badge showing the latest security grade for a domain. Embed in README files or websites.

markdown
![Security Grade](https://app.shieldome.com/badge/example.com.svg)
ℹ️
The badge uses the most recent completed scan for the domain. Configure custom colors and labels in Account → Badge & Certificate.