← Back
SwarmHawk 0.10 · REST API

SwarmHawk API

The SwarmHawk API gives you programmatic access to 27 automated security checks plus 3,000+ active nuclei CVE templates — SSL, DNS, CVEs with active exploit confirmation, breach exposure, typosquats, AI threat analysis and more — for any domain. Use it to build security workflows, automate due diligence, or wire SwarmHawk into your own platform.

There are two API surfaces:

  • /api/v2 — the legacy session/JWT-authenticated surface used by the dashboard. Most reads and writes are also available here for compatibility.
  • /api/v1 — the modern public Business / Enterprise API, secured with bearer API keys (swh_…). This is what new integrations should target.
Base URL https://api.swarmhawk.com
📘 All responses are JSON. All requests with a body must set Content-Type: application/json.

Quick Start — scan a domain in 30 seconds

# 1. Create an API key at www.swarmhawk.com → Account → API Keys (Business or Enterprise)
# 2. Add and start scanning a domain:

curl -X POST https://api.swarmhawk.com/api/v1/domains \
  -H "Authorization: Bearer swh_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

# Poll the latest report:
curl https://api.swarmhawk.com/api/v1/jobs/<job_id> \
  -H "Authorization: Bearer swh_your_key_here"
import requests

API_KEY = "swh_your_key_here"
BASE = "https://api.swarmhawk.com/api/v1"
H = {"Authorization": f"Bearer {API_KEY}"}

resp = requests.post(f"{BASE}/domains", headers=H, json={"domain": "example.com"})
job_id = resp.json()["job_id"]

job = requests.get(f"{BASE}/jobs/{job_id}", headers=H).json()
print(f"State: {job['state']}")
const BASE = "https://api.swarmhawk.com/api/v1";
const H = { "Authorization": "Bearer swh_your_key_here", "Content-Type": "application/json" };

const add = await fetch(`${BASE}/domains`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({ domain: "example.com" })
}).then(r => r.json());

const job = await fetch(`${BASE}/jobs/${add.job_id}`, { headers: H }).then(r => r.json());
console.log(`State: ${job.state}`);

Authentication

SwarmHawk uses two authentication methods depending on the endpoint:

MethodHeaderUsed for
API Key (Bearer)Authorization: Bearer swh_…/api/v1/* public Business / Enterprise API
Session JWTAuthorization: Bearer <jwt>/api/v2/* and dashboard endpoints (user-authenticated)
None—/public-scan, /health
🔑 Get your API key at www.swarmhawk.com → Sign In → Account → API Keys. API keys are available on the Business and Enterprise plans.

API Key format

swh_3f8a2b1c4d5e6f7a8b9c0d1e2f3a4b5c

Errors & Rate Limits

HTTP status codes

CodeMeaning
200Success
400Bad request — check your request body
401Missing or invalid API key / session token
403Forbidden — feature requires a paid plan
429Rate limit exceeded
500Server error — retry after 30 seconds

Error response shape

{
  "detail": "Invalid API key"
}

Rate limits

PlanLimit
FreeAPI keys not available; use the dashboard or /public-scan
BusinessDefault per-account quota with wallet top-up for ad-hoc credits
EnterpriseCustom limits negotiated per contract
/public-scanNo key required · throttled by IP

Scan a domain

There is no single "one-shot scan" endpoint. To scan a domain via the API:

  1. Add (and verify) the domain with POST /api/v1/domains — the first scan is queued automatically.
  2. To re-scan an already-added domain, call POST /api/v1/domains/{domain_id}/scan.
  3. Poll GET /api/v1/jobs/{job_id} for status and GET /api/v2/domains/{domain_id}/report for the latest results.
  4. For one-off anonymous scans without an account, use POST /public-scan (rate-limited, results not stored).
See the Add domain, Start scan, and Job status sections under the Business / Enterprise API for the canonical request shapes.

POST /public-scan

Anonymous scan — no API key required. Returns a subset of checks with some results locked. Use this for demos or embeds. Results are not saved.

POST /public-scan No auth
FieldTypeDescription
domainstringDomain to scan
{
  "domain": "example.com",
  "risk_score": 42,
  "critical": 1,
  "warnings": 3,
  "locked_count": 8,
  "checks": [/* 7 free checks visible, 8 locked */],
  "scanned_at": "2025-03-10T09:15:00Z"
}

GET /scan/passive

Fast fingerprint scan — detects software stack and checks for known CVEs without a full scan. Response in ~3 seconds. No result saved.

GET /scan/passive?domain=example.com Bearer
{
  "domain": "example.com",
  "software": [
    {"product": "nginx", "version": "1.18.0"},
    {"product": "WordPress", "version": "6.3.1"}
  ],
  "cve_hits": [
    {
      "product": "WordPress 6.3.1",
      "top_cve": "CVE-2023-39320",
      "top_cvss": 9.8,
      "cve_count": 3
    }
  ],
  "max_cvss": 9.8,
  "priority": "CRITICAL"
}

priority: CRITICAL (cvss ≥ 9) · HIGH (≥ 7) · MEDIUM (≥ 4) · LOW. If the upstream scanner fleet is unavailable, the endpoint returns an empty-result shape rather than 5xx-ing.

GET /api/v2/domains

List all domains in your account with their latest risk scores and checks.

GET /api/v2/domains Bearer
{
  "domains": [
    {
      "id": "uuid",
      "domain": "example.com",
      "country": "DE",
      "status": "active",
      "paid": true,
      "risk_score": 42,
      "scanned_at": "2025-03-10T09:15:00Z",
      "checks": [/* latest check array */],
      "scan_history": [{"date":"…","risk":42}]
    }
  ]
}

POST /api/v2/domains

Add a domain to your account and queue a background scan.

POST /api/v2/domains Bearer
FieldTypeDescription
domainstringDomain to add, e.g. "mycompany.cz"
countrystringISO country code (optional — auto-detected from TLD)
{
  "id": "uuid",
  "domain": "mycompany.cz",
  "status": "scanning",
  "message": "Domain added — scan starting now"
}

GET /api/v2/domains/{domain_id}/report

Retrieve the latest full scan report for a domain. Free users see a subset; paid users see all 27 checks including nuclei active CVE validation and AI summary.

GET /api/v2/domains/{domain_id}/report Bearer
{
  "domain": "example.com",
  "risk_score": 42,
  "scanned_at": "2025-03-10T09:15:00Z",
  "paid": true,
  "critical": 1,
  "warnings": 3,
  "informational": 2,
  "locked_count": 0,
  "verified_at": "2026-04-08T10:00:00Z",
  "checks": [
    {
      "check": "ssrf",
      "status": "info",
      "confidence": "informational",
      "title": "A10: 3 SSRF surface indicator(s)",
      "detail": "Surface area detected (not confirmed vulnerabilities)…",
      "score_impact": 0
    }
  ]
}

Each check object now includes a confidence field: "confirmed" (scored) or "informational" (zero penalty, surface indicator only). Informational findings are never counted in warnings or critical.

GET /domains/{domain_id}/verify-token

Return the live ownership challenge issued by the verification orchestrator. SwarmHawk supports two verification methods — DNS TXT record or an HTTP file upload. Call POST /domains/{domain_id}/verify with method: "dns" or "http" once the record/file is published.

GET /domains/{domain_id}/verify-token Bearer
{
  "domain": "example.com",
  "verified_at": null,
  "orchestrator_state": "pending",
  "method": {
    "name": "DNS TXT record",
    "record_name": "_swarmhawk-verify.example.com",
    "record_value": "swarmhawk-verify=a3f7b2c1…",
    "hint": "Add a TXT record named _swarmhawk-verify.example.com with the value above. Click 'Check DNS' once propagated."
  },
  "methods": [
    {
      "type": "dns",
      "name": "DNS TXT record",
      "record_name": "_swarmhawk-verify.example.com",
      "record_value": "swarmhawk-verify=a3f7b2c1…",
      "hint": "Add a TXT record …"
    },
    {
      "type": "http",
      "name": "File upload",
      "path": "/.well-known/swarmhawk-verify.txt",
      "url": "https://example.com/.well-known/swarmhawk-verify.txt",
      "body": "swarmhawk-verify=a3f7b2c1…",
      "hint": "Upload a file at the URL containing the body above. Click 'Check File' once it returns 200."
    }
  ]
}

The HTTP method entry is only present when the orchestrator has issued an http_path challenge. If the orchestrator is unreachable the endpoint returns 503 no_verification_orchestrator.

POST /domains/{domain_id}/verify

Ask the orchestrator to confirm ownership using the selected method. On success, domains.verified_at is written and returned in subsequent report responses. The orchestrator caches positive verifications for 7 days.

POST /domains/{domain_id}/verify Bearer

Request body (optional, defaults to dns):

{ "method": "dns" }   // or "http"

Success response:

{ "verified": true, "method": "dns" }

Not-yet-found response:

{ "verified": false, "state": "pending", "method": "dns", "detail": "DNS-TXT record not found yet" }

GET /api/v2/domains/{domain_id}/history

Risk score trend over time — useful for building dashboards or alerting on score increases.

GET /api/v2/domains/{domain_id}/history Bearer
{
  "domain": "example.com",
  "scans": [
    {"risk_score":42,"critical":1,"warnings":3,"scanned_at":"2025-03-10T09:15:00Z"},
    {"risk_score":38,"critical":0,"warnings":4,"scanned_at":"2025-02-10T08:00:00Z"}
  ]
}

GET /api/v2/domains/{domain_id}/nis2

Map scan findings to NIS2 Directive articles. Returns a compliance score and per-article status — suitable as audit evidence for regulators.

GET /api/v2/domains/{domain_id}/nis2 Bearer
{
  "domain": "example.com",
  "risk_score": 42,
  "nis2_score": 71,
  "articles": [
    {
      "article": "Art.21(2)(i)",
      "requirement": "Secure communications",
      "status": "met"
    },
    {
      "article": "Art.21(2)(h)",
      "requirement": "Supply chain security",
      "status": "warning"
    }
  ]
}

POST /intel

Generate an AI-powered threat briefing for your domain portfolio using Claude. Returns a structured briefing with executive summary, critical findings, and remediation priorities.

POST /intel Bearer
FieldTypeDescription
domainsstringComma-separated: "example.com (risk:42), other.cz (risk:18)"
datestringToday's date, e.g. "10 March 2025"
{
  "text": "## Executive Summary\n\nYour domain portfolio shows...",
  "briefing": "…"
}

POST /api/v2/keys

Create a new API key for your account. Returns all keys including the new one.

POST /api/v2/keys Bearer
{
  "keys": [
    {
      "key": "swh_3f8a2b1c4d5e6f7a8b9c0d1e2f3a4b5c",
      "calls_this_month": 0,
      "limit_per_month": 10,
      "active": true,
      "created_at": "2025-03-10T09:00:00Z"
    }
  ]
}

GET /api/v2/keys

List all API keys and their usage for the current month.

GET /api/v2/keys Bearer
curl https://api.swarmhawk.com/api/v2/keys \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN"

DELETE /api/v2/keys/{key}

Revoke an API key immediately. All future requests with this key will be rejected.

DELETE /api/v2/keys/{key} Bearer
{ "revoked": "swh_3f8a2b1c…" }

Business / Enterprise API — /api/v1

The /api/v1/* surface is the production write API used by Business and Enterprise customers. Every endpoint below accepts either X-API-Key: swh_… (recommended for server-to-server) or Authorization: Bearer <jwt> (for first-party tools).

Base URL https://api.swarmhawk.com

Tier gating

  • Free — API access denied (403 API_ACCESS_REQUIRED).
  • Pro — 10-call read-only trial. Write endpoints return 403 business_required.
  • Business — 500 calls/month, wallet/entitlement debit per scan/pentest/breach.
  • Enterprise — unlimited calls; admin-enrolled with a per-customer max_domains cap and per-call metering (details).

Typical reseller flow

# 1. Add a domain (returns the DNS challenge inline)
curl -X POST https://api.swarmhawk.com/api/v1/domains \
  -H "X-API-Key: swh_…" -H "Content-Type: application/json" \
  -d '{"domain":"acme.com"}'
# {"id":"…","domain":"acme.com","verification":{"state":"pending","dns_record_name":"_swarmhawk.acme.com","dns_record_value":"swh-verify=…"}}

# 2. Tenant adds the DNS TXT record, then verify
curl -X POST https://api.swarmhawk.com/api/v1/domains/{id}/verify \
  -H "X-API-Key: swh_…"
# {"verified":true,"state":"verified","method":"dns"}

# 3. Kick off a scan / pentest / breach lookup (returns job_id)
curl -X POST https://api.swarmhawk.com/api/v1/domains/{id}/scan -H "X-API-Key: swh_…"
# {"job_id":"…","status":"queued","domain":"acme.com","swarmhawk_job_id":"…"}

# 4. Poll the orchestrator-side job and pull results when status=done
curl https://api.swarmhawk.com/api/v1/jobs/{job_id} -H "X-API-Key: swh_…"
curl https://api.swarmhawk.com/api/v1/jobs/{job_id}/findings -H "X-API-Key: swh_…"
curl https://api.swarmhawk.com/api/v1/jobs/{job_id}/report -H "X-API-Key: swh_…"

POST /api/v1/keys

Issue an API key for the current user. Business: one key, 500 calls/month. Enterprise: one key, unlimited (admin-managed via /admin/api/v1/enterprise/*). Bearer JWT only — you can't bootstrap your first key with itself; the dashboard sign-in (or admin enrollment for Enterprise) is the entry point.

POST/api/v1/keysBearer only
curl -X POST https://api.swarmhawk.com/api/v1/keys -H "Authorization: Bearer …"
# { "keys": [{ "id":"…", "key_prefix":"swh_…", "calls_this_month":0, "limit_per_month":500 }],
#   "created": true, "is_trial": false, "full_key": "swh_…" }  ← plaintext shown ONCE

GET /api/v1/keys

List active keys + month-to-date usage. Plaintext is never re-displayed. Bearer JWT only — key-management endpoints aren't reachable with an API key.

GET/api/v1/keysBearer only
{ "keys": [{ "id":"…", "key_prefix":"swh_3f8a2b1c",
            "calls_this_month":142, "limit_per_month":500,
            "cost_this_month_usd":"23.50", "last_used_at":"2026-05-12T19:01:00Z" }] }

POST /api/v1/keys/{key_id}/regenerate

Revoke the existing key and issue a fresh one. The new plaintext is shown once in the response — the previous key stops working immediately. Bearer JWT only.

POST/api/v1/keys/{key_id}/regenerateBearer only

DELETE /api/v1/keys/{key_id}

Revoke a key. Idempotent. Bearer JWT only.

DELETE/api/v1/keys/{key_id}Bearer only

GET /api/v1/domains

List the caller's domains. Paginated; honours ?limit and ?offset.

GET/api/v1/domainsX-API-Key Bearer

POST /api/v1/domains

Add a domain and atomically bootstrap orchestrator verification. Returns the DNS-TXT challenge inline so you can deliver it to your tenant without a follow-up call. Free for the customer — domain adds do not debit wallet.

POST/api/v1/domainsX-API-Key Bearer

Body

FieldTypeRequiredDescription
domainstringyes4–253 chars, lowercased.
countrystring—ISO 3166-1 alpha-2. Auto-derived from TLD if absent.
industrystring—Free-form label for grouping/reporting.
primary_contactstring—Email for breach/alert routing.

Response (201)

{
  "id": "d1-uuid",
  "domain": "acme.example.com",
  "country": "US",
  "verification": {
    "state": "pending",
    "dns_record_name": "_swarmhawk.acme.example.com",
    "dns_record_value": "swh-verify=Xk2…",
    "http_path": "/.well-known/swarmhawk-verify.txt",
    "http_body": "swh-verify=Xk2…",
    "expires_at": "2026-05-19T20:00:00Z"
  }
}

Errors: 403 tier_locked on cap; 409 domain_already_added on duplicate; 422 invalid_domain.

GET /api/v1/domains/{domain_id}

Single-domain detail: risk_score, last_scanned_at, verified_at, country, etc. 404 on mismatch.

GET/api/v1/domains/{domain_id}X-API-Key Bearer

PATCH /api/v1/domains/{domain_id}

Toggle auto_rescan_enabled for a domain (the only mutable field on this endpoint today). Enabling requires Pro tier or higher (the daily_rescan feature gate); disabling is always allowed. To change industry, primary_contact, or country, delete and re-add the domain.

PATCH/api/v1/domains/{domain_id}Bearer only
curl -X PATCH https://api.swarmhawk.com/api/v1/domains/{id} \
  -H "Authorization: Bearer …" -H "Content-Type: application/json" \
  -d '{"auto_rescan_enabled": true}'
# { "ok": true }

DELETE /api/v1/domains/{domain_id}

Hard-delete a domain. The row, its verification state and any associated job records are removed. Frees up a slot against the tier cap. Quota +1.

DELETE/api/v1/domains/{domain_id}X-API-Key Bearer

POST /api/v1/domains/bulk-import

Multipart upload of a CSV — each row is domain,country (country is mandatory, 2-letter ISO code). The first row may be a domain,country header — it is skipped. Honours the per-customer cap; rows that overshoot land in the rejected list with reason quota exceeded. Quota +1 per call (not per row).

POST/api/v1/domains/bulk-importX-API-Key Bearer

Returns {accepted: [...], rejected: [...]}. Available on Business + Enterprise tiers (feature: bulk_import).

GET /api/v1/domains/{domain_id}/history

Time-ordered list of past scans + check deltas for a domain.

GET/api/v1/domains/{domain_id}/historyX-API-Key Bearer

GET /api/v1/domains/{domain_id}/verification

Read-only fetch of the current authz challenge state from the orchestrator (DNS + HTTP path + record values + expiry). Use this if you need to re-display the challenge after the initial POST /api/v1/domains response is lost.

GET/api/v1/domains/{domain_id}/verificationX-API-Key Bearer
{
  "domain": "acme.example.com",
  "state": "pending",            // pending | verified | failed | expired
  "verified_at": null,
  "dns_record_name": "_swarmhawk.acme.example.com",
  "dns_record_value": "swh-verify=Xk2…",
  "http_path": "/.well-known/swarmhawk-verify.txt",
  "http_body": "swh-verify=Xk2…",
  "expires_at": "2026-05-19T20:00:00Z"
}

POST /api/v1/domains/{domain_id}/verify

Ask the orchestrator to actually run the check. method=dns (default) does a TXT lookup; method=http fetches https://<domain><http_path> and compares to http_body.

POST/api/v1/domains/{domain_id}/verifyX-API-Key Bearer
curl -X POST https://api.swarmhawk.com/api/v1/domains/{id}/verify \
  -H "X-API-Key: swh_…" -H "Content-Type: application/json" \
  -d '{"method":"dns"}'
# {"verified":true,"state":"verified","method":"dns"}
# {"verified":false,"state":"pending","method":"dns","detail":"TXT record not found"}

On verified:true, both domains.verified_at and the orchestrator cache are flipped — subsequent scan/pentest/breach calls no longer return domain_unverified.

POST /api/v1/domains/{domain_id}/scan

Submit an attack-surface scan to the orchestrator. No domain-verification check — any added domain is eligible. Business: debits wallet/entitlement per service_prices.attack_surface. Enterprise: records a usage_events row with effective per-customer price, no wallet touch. Quota +1 on 201.

POST/api/v1/domains/{domain_id}/scanX-API-Key Bearer

Response (201)

{
  "job_id": "orch-uuid",        // poll at GET /api/v1/jobs/{job_id}
  "status": "queued",
  "domain": "acme.example.com",
  "swarmhawk_job_id": "sw-uuid"     // internal id, kept for audit
}

Errors: 402 INSUFFICIENT_BALANCE, 403 enterprise_inactive, 429 cooldown (orchestrator-enforced shared 24h window with pentest), 503 orchestrator_unavailable.

POST /api/v1/domains/{domain_id}/pentest

Submit an AI pentest. Optional rate_profile controls scan aggressiveness: stealth (3 rps) · regular (10 rps, default) · aggressive (25 rps). All other knobs stay at orchestrator defaults. Same wallet/metering split as /scan.

POST/api/v1/domains/{domain_id}/pentestX-API-Key Bearer
curl -X POST https://api.swarmhawk.com/api/v1/domains/{id}/pentest \
  -H "X-API-Key: swh_…" -H "Content-Type: application/json" \
  -d '{"rate_profile":"regular"}'
# {"job_id":"…","status":"queued","domain":"…","swarmhawk_job_id":"…"}

Same error surface as scan, plus 422 invalid_rate_profile.

POST /api/v1/domains/{domain_id}/breach-intel

Queue a darknet-research job that cross-checks darknet sources for credentials, hashes, and PII tied to this domain. Asynchronous — returns a job_id immediately; poll GET /api/v1/breach-intel/{job_id} for status and findings. Typical end-to-end completion is 30–90 s. Domain must be verified. Business: debits wallet/entitlement per service_prices.breach_intel. Enterprise: records a usage_events row with effective per-customer price, no wallet touch. Quota +1 on accepted submit. Per-domain cooldown: 7 days (the orchestrator's darknet window).

POST/api/v1/domains/{domain_id}/breach-intelX-API-Key Bearer

Response (202)

{
  "job_id": "sw-uuid",               // poll GET /api/v1/breach-intel/{job_id}
  "orchestrator_job_id": "orch-uuid",  // upstream id, kept for audit
  "status": "running",
  "domain": "acme.example.com",
  "billing_source": "wallet"           // or "entitlement" | "admin_override" | "enterprise_metered"
}

Errors: 402 INSUFFICIENT_BALANCE, 403 domain_unverified, 403 enterprise_inactive, 409 already_running (a job is already in flight for this domain), 429 cooldown (local 7-day window), 429 darknet_cooldown_active (orchestrator-enforced 7-day window with next_available_at), 502 orchestrator_unavailable, 503 orchestrator_unavailable.

GET /api/v1/breach-intel/{job_id}

Poll a darknet-research job. Ownership-checked; no quota. The backend lazily polls the orchestrator (5 s throttle) while the local row is still running, so this endpoint can be called frequently without flooding upstream. Once the job lands a terminal state (completed | failed | cancelled), subsequent calls return the cached row without any orchestrator round-trip.

GET/api/v1/breach-intel/{job_id}X-API-Key Bearer

Response (200) — running

{
  "job_id": "sw-uuid",
  "orchestrator_job_id": "orch-uuid",
  "domain": "acme.example.com",
  "status": "running",
  "leak_count": null,
  "findings": [],
  "pagination": { "page":1, "page_size":50, "total":0, "total_pages":0 },
  "error": null,
  "billing_source": "wallet",
  "requested_at": "2026-05-19T20:23:35Z",
  "completed_at": null
}

Response (200) — completed

{
  "job_id": "sw-uuid",
  "domain": "acme.example.com",
  "status": "completed",
  "leak_count": 4,
  "findings": [
    {
      "id": 2,
      "email": "[email protected]",
      "username": "alice",
      "breach_name": "leakdb",
      "breach_date": null,
      "has_password": true,                              // plaintext available via /reveal
      "password": { "sha1":"6DD85DDA386874EC…", "encrypted":true },
      "risk_level": "high"
    }
  ],
  "pagination": { "page":1, "page_size":50, "total":4, "total_pages":1 },
  "error": null,
  "billing_source": "wallet",
  "requested_at": "2026-05-19T20:23:35Z",
  "completed_at": "2026-05-19T20:23:43Z"
}

Response (200) — failed

{
  "job_id": "sw-uuid",
  "status": "failed",
  "error": "no_data_available",       // or "orchestrator_job_not_found" | "orchestrator_failed"
  "leak_count": null,
  "findings": [],
  "billing_source": "wallet",
  "requested_at": "2026-05-19T20:23:35Z",
  "completed_at": "2026-05-19T20:24:01Z"
}

On error = "no_data_available" or "orchestrator_job_not_found" the wallet/entitlement consumption is automatically refunded — no action needed from the caller.

POST /api/v1/breach-intel/{job_id}/reveal/{finding_id}

Decrypt one plaintext password from a darknet finding. Owner-only (cross-tenant attempts return 404 — existence-private). Domain must still be orchestrator-verified at the time of the call. Plaintext is returned inline and is NEVER persisted in SwarmHawk's DB; every call writes one admin_audit_log row. No quota cost.

POST/api/v1/breach-intel/{job_id}/reveal/{finding_id}X-API-Key Bearer

Response (200)

{
  "plaintext": "Hunter2!",
  "revealed_at": "2026-05-19T20:25:10Z"
}

Errors: 403 domain_unverified (verification lapsed since the scan ran), 404 finding_not_found (id not part of this job — also returned for cross-tenant), 502 orchestrator_reveal_failed. Only findings with has_password: true in the GET response can be revealed.

GET /api/v1/jobs/{job_id}

Job-status proxy to the orchestrator. The job_id here is the orchestrator id returned by POST /scan or /pentest. Ownership is enforced server-side — 404 on mismatch (existence-private). GET = no quota.

GET/api/v1/jobs/{job_id}X-API-Key Bearer
{
  "id": "orch-uuid",
  "status": "running",       // queued | running | done | failed | cancelled
  "created_at": "…",
  "updated_at": "…",
  "progress_pct": 42,        // integer 0–100 (NOT a 0–1 fraction)
  "phase": "recon",          // free-form server label, display-only
  "target": "acme.example.com"
}

Progress is reported as progress_pct (integer percentage, 0–100) plus a free-form phase label — both pass through verbatim from the scan engine, so treat unknown phase values as display-only.

Poll every 5–10 seconds while status is queued or running. Once done, fetch findings / report below.

GET /api/v1/jobs/{job_id}/findings

Structured findings list (vulnerabilities, exposed services, weak configs, etc.). Returned once status == 'done'.

GET/api/v1/jobs/{job_id}/findingsX-API-Key Bearer

Returns a bare JSON array. Each item is emitted verbatim by the scan engine, so treat fields beyond the common ones below as additive.

[
  {
    "id": "f-1",
    "severity": "high",        // info | low | medium | high | critical
    "title": "Exposed admin panel",
    "target": "admin.acme.example.com",
    "description": "…"
  }
]

Errors: 404 findings_not_available if no findings exist for the job yet (still running, or output not enabled for your account); 404 not_found for an unknown / cross-tenant job; 502 api_get_findings_unavailable if the scan service is unreachable.

GET /api/v1/jobs/{job_id}/report

Full JSON report (executive summary + findings + recommendations). Available once done.

GET/api/v1/jobs/{job_id}/reportX-API-Key Bearer

Returns a JSON object wrapping the engine-rendered structured report. The nested report object passes through from the scan engine.

{
  "status": "done",
  "generated_at": "2026-06-03T09:12:44Z",
  "model": "claude-…",
  "report": {
    "executive_summary": "…",
    "findings": [ /* same items as /findings */ ],
    "recommendations": [ "…" ]
  }
}

Errors: 404 report_not_available if the report has not been generated for this job (still running, or report generation not enabled for your account); 502 api_get_report_unavailable if the scan service is unreachable.

GET /api/v1/jobs/{job_id}/report.pdf

Same report rendered as PDF (binary stream with upstream Content-Disposition). Suitable for direct re-distribution to your tenant.

GET/api/v1/jobs/{job_id}/report.pdfX-API-Key Bearer

On success returns 200 with Content-Type: application/pdf and the upstream Content-Disposition filename.

PDF generation is not enabled on every account / job type. When no PDF is available you get 404 {"error":"report_not_available"} with a human-readable detail — not a generic 502. A 502 api_get_report_pdf_unavailable means the scan service itself is unreachable (retry with back-off). Fall back to GET …/report (JSON) when you only need the data.

Enterprise Tier

The Enterprise tier is built for hosting providers, MSSPs, and other resellers who run SwarmHawk as a white-labelled API on behalf of many tenants. It is admin-enrolled (no self-serve checkout) and contractually negotiated — contact sales to onboard.

What's different from Business

  • Hard per-customer domain cap — your contract sets max_domains (e.g. 100, 500, 5000); the (N+1)th POST /api/v1/domains returns 403 tier_locked with feature:"domains" and a limit/current body.
  • Unlimited API calls — your key carries limit_per_month: null; the per-month quota that applies to Pro/Business is bypassed.
  • Per-call metering — every scan, pentest, and breach-intel call is recorded into an immutable usage_events log with the effective per-customer price, retrievable via GET /api/v1/usage. Per-service prices can be contract-overridden by an admin (otherwise they fall back to the global rate-card).
  • No wallet / no Stripe — Enterprise billing happens off-platform on a monthly invoice cycle. Your reseller wallet balance is irrelevant.
  • API-only — Enterprise customers do not log in to the dashboard. Sales hands you your bearer key out-of-band; everything below works with X-API-Key: swh_….

Available endpoints

Enterprise customers use the existing Business /api/v1/* surface plus the new GET /api/v1/usage:

  • POST /api/v1/keys, GET /api/v1/keys — view your own key metadata (the plaintext key itself is only shown once at enrollment / regeneration).
  • POST /api/v1/domains — subject to the per-customer max_domains cap.
  • POST /api/v1/domains/{id}/verify
  • POST /api/v1/domains/{id}/scan
  • POST /api/v1/domains/{id}/pentest
  • POST /api/v1/domains/{id}/breach-intel · GET /api/v1/breach-intel/{job_id}
  • GET /api/v1/usage — Enterprise-only (see below).

GET /api/v1/usage

Per-domain usage aggregates for an Enterprise customer in a given monthly billing period. Use this to reconcile your monthly invoice or to bill your own tenants per domain.

GET /api/v1/usage?period=YYYY-MM X-API-Key Bearer

Query parameters

NameTypeDefaultDescription
periodstringcurrent monthFormat YYYY-MM. Translates to [YYYY-MM-01, next-month-01) UTC. Malformed values return 400 invalid_period.

Example

curl https://api.swarmhawk.com/api/v1/usage?period=2026-05 \
  -H "X-API-Key: swh_…"

Response

{
  "period": "2026-05",
  "currency": "USD",
  "domains": [
    {
      "domain_id": "d1-uuid",
      "domain": "acme.example.com",
      "scans_count": 12,
      "pentests_count": 3,
      "breach_lookups_count": 8,
      "total_cost_usd": 234.50
    }
  ],
  "totals": {
    "scans_count": 47,
    "pentests_count": 11,
    "breach_lookups_count": 22,
    "total_cost_usd": 1284.75
  }
}

Notes

  • The domains array contains only domains with at least one event in the period. Domains you own but didn't use that month are omitted — query GET /api/v1/domains for the full inventory.
  • If a domain is deleted between the event and the query, its row still appears with domain_id: null and domain: "<deleted>" — the audit trail is preserved.
  • Cost reflects the effective per-call price at the time of the event: per-customer contract override if set, otherwise the global service-prices rate-card.
  • 403 enterprise_only is returned to any non-Enterprise tier hitting this endpoint.

Deactivation behaviour

If your Enterprise account is paused by an admin (non-payment, contract end, etc.), every /api/v1/* call — read and write — returns:

HTTP 403
{ "detail": { "error": "enterprise_inactive" } }

The API key is not revoked (so reactivation restores access without re-issuing keys), and historical usage_events + domains are preserved. Contact sales to reactivate.

Python SDK

The SwarmHawk Python SDK gives security platforms a clean programmatic interface to pull findings, configure push integrations, and consume STIX 2.1 bundles — no raw HTTP required.

# Install pip install httpx # only dependency # Download curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/swarmhawk_sdk.py
# Quick-start from swarmhawk_sdk import SwarmHawkClient client = SwarmHawkClient(api_key="swh_...") # Paginated high-risk domains for domain in client.iter_alerts(min_risk=80): print(domain["domain"], domain["risk_score"]) # Single domain detail result = client.domain("example.com") print(result["max_cvss"], result["priority"]) # STIX 2.1 bundle for your threat intel platform bundle = client.stix_bundle(min_risk=70) print(len(bundle["objects"]), "STIX objects")

SDK Methods

MethodDescriptionReturns
domains(min_risk, country, limit, offset)Paginated scan resultslist[dict]
domain(name)Single domain full detaildict
alerts(min_risk, since, limit)High-risk findings, optional since datetimelist[dict]
iter_alerts(min_risk, page_size)Generator — auto-paginates through all resultsIterator[dict]
stix_bundle(min_risk, limit)STIX 2.1 bundle from /taxii endpointdict
integrations()List configured push integrationslist[dict]
configure_integration(service, config)Save integration credentialsdict
test_integration(service, config)Test connectivity without saving{ok, message}

Integration API

⚠ Integrations are currently behind a feature flag. With INTEGRATIONS_ENABLED=false (the platform default), every /integrations* endpoint returns HTTP 410 { "error": "feature_disabled" }. Contact [email protected] if you need this re-enabled for your account.

When enabled, configure push integrations so SwarmHawk automatically sends critical findings to your security platforms the moment they're detected (risk ≥ 70).

GET /integrations

List all configured integrations for the authenticated user. Secrets are masked.

curl -H "Authorization: Bearer swh_..." \ https://api.swarmhawk.com/integrations

POST /integrations/{service}

Save or update integration config. service is one of: splunk · sentinel · crowdstrike · gravityzone · cortex · jira · servicenow · webhook · stix

curl -X POST \ -H "Authorization: Bearer swh_..." \ -H "Content-Type: application/json" \ -d '{"config": {"hec_url": "https://splunk:8088", "hec_token": "...", "index": "security"}, "enabled": true}' \ https://api.swarmhawk.com/integrations/splunk

POST /integrations/{service}/test

Test connectivity without saving. Returns {"ok": true, "message": "..."}

curl -X POST \ -H "Authorization: Bearer swh_..." \ -H "Content-Type: application/json" \ -d '{"config": {"url": "https://webhook.site/xxx", "secret": "optional"}}' \ https://api.swarmhawk.com/integrations/webhook/test

DEL /integrations/{service}

Remove an integration config.

Supported services

ServiceAuth methodTriggerPayload type
splunkHEC tokenrisk ≥ 70CIM-aligned JSON event
sentinelWorkspace SharedKey HMACrisk ≥ 70Flat KQL-ready fields (_s/_d/_b)
crowdstrikeOAuth2 client credentialsrisk ≥ 70IOC (type=domain, action=detect/prevent)
gravityzoneBearer access keyrisk ≥ 70JSON-RPC createCustomRule
cortexSHA-256 HMAC (api_key_id/nonce/ts)risk ≥ 70External alert with MITRE tags
jiraBasic (email:api_token)risk ≥ 70 AND cvss ≥ 7ADF-formatted issue with CVE/software tables
servicenowBasic (user:password)risk ≥ 70Incident with urgency/impact/SLA mapping
webhookOptional HMAC-SHA256 signaturerisk ≥ 70Enriched JSON payload
stixBearer (read via /taxii)Pull-basedSTIX 2.1 DomainName + Vulnerability objects

STIX / TAXII 2.1

SwarmHawk exposes a TAXII 2.1-compliant endpoint. Connect any threat intelligence platform (OpenCTI, MISP, ThreatConnect, Anomali) to pull SwarmHawk findings as STIX 2.1 objects.

GET /taxii/collections/

TAXII discovery endpoint — returns collection metadata.

curl -H "Authorization: Bearer swh_..." \ https://api.swarmhawk.com/taxii/collections/

GET /taxii/collections/swarmhawk/objects/

Returns a STIX 2.1 bundle of your high-risk domains as domain-name, vulnerability, and relationship objects.

curl "https://api.swarmhawk.com/taxii/collections/swarmhawk/objects/?min_risk=70&limit=200" \ -H "Authorization: Bearer swh_..."
ParamDefaultDescription
min_risk70Minimum risk score (0–100)
limit100Max objects (1–500)

STIX object types returned

TypeContent
domain-nameDomain value, risk score, priority, country
vulnerabilityCVE ID, CVSS score, description
relationshipdomain-name → related-to → vulnerability

All object IDs are deterministic (UUID5) — the same domain always produces the same STIX ID across bundle generations, enabling proper deduplication in downstream TIPs.

Adapter Scripts

Standalone Python scripts for platforms where you prefer a scheduled pull (cron / Lambda / Azure Function) over the always-on push connector. Each script fetches SwarmHawk alerts and pushes them into the target platform in one run.

All adapters require only pip install httpx and accept both CLI flags and environment variables.
PlatformScriptMin risk defaultDownload
Splunk SIEM splunk_ta.py 70 ↓ splunk_ta.py
Microsoft Sentinel sentinel_connector.py 70 ↓ sentinel_connector.py
CrowdStrike Falcon crowdstrike_adapter.py 80 ↓ crowdstrike_adapter.py
Bitdefender GravityZone gravityzone_adapter.py 80 ↓ gravityzone_adapter.py
Palo Alto Cortex XDR cortex_adapter.py 75 ↓ cortex_adapter.py
Jira jira_adapter.py 70 + cvss ≥ 7 ↓ jira_adapter.py
ServiceNow servicenow_adapter.py 70 ↓ servicenow_adapter.py
🐙 All scripts are also available in the swarmhawk GitHub repo under sdk/adapters/.

Splunk TA Adapter

# One-line download + run curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/adapters/splunk_ta.py pip install httpx python splunk_ta.py --api-key swh_... --min-risk 70
FlagEnv varDescription
--api-keySWARMHAWK_API_KEYSwarmHawk API key
--min-riskSWARMHAWK_MIN_RISKMinimum risk score (default: 70)
--base-urlSWARMHAWK_BASE_URLAPI base URL
--indexSWARMHAWK_SPLUNK_INDEXSplunk index name (default: main)

Emits sourcetype=swarmhawk:scan events with CIM-aligned fields: dest, vendor_product, category, severity.

Microsoft Sentinel Adapter

curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/adapters/sentinel_connector.py python sentinel_connector.py \ --api-key swh_... \ --workspace-id <WORKSPACE_ID> \ --shared-key <PRIMARY_OR_SECONDARY_KEY>
FlagEnv varDescription
--workspace-idAZURE_WORKSPACE_IDLog Analytics workspace ID
--shared-keyAZURE_SHARED_KEYPrimary or secondary shared key
--log-typeAZURE_LOG_TYPECustom table name (default: SwarmHawk)
--min-riskSWARMHAWK_MIN_RISKMinimum risk score (default: 70)

Data lands in SwarmHawk_CL custom table. KQL example: SwarmHawk_CL | where RiskScore_d >= 80 | sort by TimeGenerated desc

CrowdStrike Falcon Adapter

curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/adapters/crowdstrike_adapter.py python crowdstrike_adapter.py \ --api-key swh_... \ --cs-client-id <CLIENT_ID> \ --cs-client-secret <CLIENT_SECRET>
FlagEnv varDescription
--cs-client-idCS_CLIENT_IDFalcon API client ID
--cs-client-secretCS_CLIENT_SECRETFalcon API client secret
--cs-base-urlCS_BASE_URLFalcon base URL (default: api.crowdstrike.com)
--min-riskSWARMHAWK_MIN_RISKMinimum risk score (default: 80)

Pushes domains as IOCs (type=domain) in batches of 200. Action is prevent for confirmed malware/phishing, detect for CVE exposure.

Bitdefender GravityZone Adapter

curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/adapters/gravityzone_adapter.py python gravityzone_adapter.py \ --api-key swh_... \ --gz-api-url https://cloud.gravityzone.bitdefender.com/api/v1.0/jsonrpc/push \ --gz-access-key <KEY> \ --gz-company-id <ID>
FlagEnv varDescription
--gz-api-urlGZ_API_URLGravityZone JSON-RPC endpoint
--gz-access-keyGZ_ACCESS_KEYGravityZone API access key
--gz-company-idGZ_COMPANY_IDCompany/tenant ID
--min-riskSWARMHAWK_MIN_RISKMinimum risk score (default: 80)

Creates custom network rules via createCustomRule. Action is block for malware/phishing, report for CVE exposure.

Palo Alto Cortex XDR Adapter

curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/adapters/cortex_adapter.py python cortex_adapter.py \ --api-key swh_... \ --xdr-api-key <KEY> \ --xdr-api-key-id <ID> \ --xdr-fqdn <tenant>.xdr.us.paloaltonetworks.com
FlagEnv varDescription
--xdr-api-keyXDR_API_KEYCortex XDR API key
--xdr-api-key-idXDR_API_KEY_IDAPI key numeric ID
--xdr-fqdnXDR_FQDNYour tenant FQDN (without https://api-)
--min-riskSWARMHAWK_MIN_RISKMinimum risk score (default: 75)

Sends external alerts with MITRE ATT&CK technique tags (T1566, T1190, T1046, T1557…). Batches of 100 per API call.

Jira Adapter

curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/adapters/jira_adapter.py python jira_adapter.py \ --api-key swh_... \ --jira-base-url https://yourcompany.atlassian.net \ --jira-email [email protected] \ --jira-token <API_TOKEN> \ --jira-project SEC
FlagEnv varDescription
--jira-base-urlJIRA_BASE_URLAtlassian Cloud URL
--jira-emailJIRA_EMAILAtlassian account email
--jira-tokenJIRA_TOKENAtlassian API token (id.atlassian.com → Security)
--jira-projectJIRA_PROJECTProject key (default: SEC)
--jira-issue-typeJIRA_ISSUE_TYPEIssue type (default: Bug)
--min-cvssSWARMHAWK_MIN_CVSSMinimum CVSS to create ticket (default: 7.0)

Creates structured ADF issues with risk summary table, CVE findings, software stack, failed checks, remediation bullets, and NIS2 label. Only fires when risk ≥ 70 AND cvss ≥ min_cvss to avoid ticket spam.

ServiceNow Adapter

curl -O https://raw.githubusercontent.com/hastikdan/swarmhawk/main/sdk/adapters/servicenow_adapter.py python servicenow_adapter.py \ --api-key swh_... \ --snow-instance yourcompany \ --snow-user admin \ --snow-password <PASSWORD>
FlagEnv varDescription
--snow-instanceSNOW_INSTANCEServiceNow instance subdomain (no .service-now.com)
--snow-userSNOW_USERServiceNow username
--snow-passwordSNOW_PASSWORDServiceNow password
--snow-tableSNOW_TABLETarget table (default: incident)
--min-riskSWARMHAWK_MIN_RISKMinimum risk score (default: 70)
PriorityUrgencyImpactSLA target
CRITICAL114 hours
HIGH1224 hours
MEDIUM227 days
LOW / INFO3330 days

Response Objects

Check object

FieldTypeDescription
checkstringCheck identifier (see Check Names below)
statusstringok · warning · critical · locked
titlestringOne-line human-readable finding
detailstringFull explanation with evidence
score_impactnumberPoints added to risk score (0–40)

Risk score interpretation

ScoreLevelMeaning
0 – 29LOW RISKGood security posture, minor issues
30 – 59MEDIUM RISKNotable issues requiring attention
60 – 100HIGH RISKCritical findings, immediate action needed

Check Names

Each entry in the checks[] array of a scan report carries a check identifier. The exhaustive list of identifiers emitted by the scanner today (source: backend/scanhawk_translator.py + backend/post_scan_processor.py):

checkGroupDescription
tls_protocolTLSNegotiated TLS version (flags TLS < 1.2)
tls_cipherTLSWeak cipher suites
tls_cert_expiryTLSCertificate expiry / already-expired
tls_self_signedTLSSelf-signed certificate
tls_handshakeTLSTLS handshake errors
email_dmarcEmailDMARC missing or weak (p=none)
mx_blacklistEmailMX host on a public blocklist
blacklistReputationDomain or A-record on a public blocklist (Spamhaus / URLhaus / Safe Browsing)
ip_reputationReputationHosting IP reputation / geo / ASN signals
cve_matchVulnsSoftware fingerprint matched to known CVEs (CVSS + KEV + EPSS)
exposed_gitExposurePublic /.git/ directory
exposed_envExposurePublic .env file
mixed_contentWebHTTPS page loads HTTP subresources
sri_missingWebSubresource integrity missing on third-party scripts
cookies_secureWebCookies missing Secure / HttpOnly / SameSite
takeover_riskDNSDangling subdomain / takeover candidate
security_txtHygieneMissing /.well-known/security.txt (RFC 9116)
whois_privacyHygieneWHOIS privacy guard on / off
ai_summaryReportingAI-generated threat analysis (paid users only)

Granular checks (e.g. tls_protocol, tls_cipher) are grouped under broader categories (TLS, DNS, Headers, etc.) for display in the dashboard. The PDF report uses additional human-friendly labels (see backend/main.py:CHECK_LABELS) but the JSON identifiers above are the canonical surface.

Ready to start building?

Get your free API key at www.swarmhawk.com → Sign In → Account → API Keys

Get Free API Key →
Interactive Explorer

Try It Live

Full interactive API explorer — execute real requests against the SwarmHawk API directly from your browser. Paste your API key into the Authorize button to unlock authenticated endpoints.

Schema source: https://api.swarmhawk.com/openapi.json