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.
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:
| Method | Header | Used for |
|---|---|---|
| API Key (Bearer) | Authorization: Bearer swh_… | /api/v1/* public Business / Enterprise API |
| Session JWT | Authorization: Bearer <jwt> | /api/v2/* and dashboard endpoints (user-authenticated) |
| None | — | /public-scan, /health |
API Key format
swh_3f8a2b1c4d5e6f7a8b9c0d1e2f3a4b5c
Errors & Rate Limits
HTTP status codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request — check your request body |
| 401 | Missing or invalid API key / session token |
| 403 | Forbidden — feature requires a paid plan |
| 429 | Rate limit exceeded |
| 500 | Server error — retry after 30 seconds |
Error response shape
{
"detail": "Invalid API key"
}Rate limits
| Plan | Limit |
|---|---|
| Free | API keys not available; use the dashboard or /public-scan |
| Business | Default per-account quota with wallet top-up for ad-hoc credits |
| Enterprise | Custom limits negotiated per contract |
| /public-scan | No key required · throttled by IP |
Scan a domain
There is no single "one-shot scan" endpoint. To scan a domain via the API:
- Add (and verify) the domain with
POST /api/v1/domains— the first scan is queued automatically. - To re-scan an already-added domain, call
POST /api/v1/domains/{domain_id}/scan. - Poll
GET /api/v1/jobs/{job_id}for status andGET /api/v2/domains/{domain_id}/reportfor the latest results. - For one-off anonymous scans without an account, use
POST /public-scan(rate-limited, results not stored).
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.
| Field | Type | Description |
|---|---|---|
| domain | string | Domain 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.
{
"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.
{
"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.
| Field | Type | Description |
|---|---|---|
| domain | string | Domain to add, e.g. "mycompany.cz" |
| country | string | ISO 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.
{
"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.
{
"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.
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.
{
"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.
{
"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.
| Field | Type | Description |
|---|---|---|
| domains | string | Comma-separated: "example.com (risk:42), other.cz (risk:18)" |
| date | string | Today'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.
{
"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.
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.
{ "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).
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_domainscap 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.
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.
{ "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.
DELETE /api/v1/keys/{key_id}
Revoke a key. Idempotent. Bearer JWT only.
GET /api/v1/domains
List the caller's domains. Paginated; honours ?limit and ?offset.
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.
Body
| Field | Type | Required | Description |
|---|---|---|---|
| domain | string | yes | 4–253 chars, lowercased. |
| country | string | — | ISO 3166-1 alpha-2. Auto-derived from TLD if absent. |
| industry | string | — | Free-form label for grouping/reporting. |
| primary_contact | string | — | 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.
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.
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.
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).
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}/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.
{
"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.
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.
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.
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).
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.
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.
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.
{
"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'.
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.
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.
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)thPOST /api/v1/domainsreturns403 tier_lockedwithfeature:"domains"and alimit/currentbody. - 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, andbreach-intelcall is recorded into an immutableusage_eventslog with the effective per-customer price, retrievable viaGET /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-customermax_domainscap.POST /api/v1/domains/{id}/verifyPOST /api/v1/domains/{id}/scanPOST /api/v1/domains/{id}/pentestPOST /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.
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
| period | string | current month | Format 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
domainsarray contains only domains with at least one event in the period. Domains you own but didn't use that month are omitted — queryGET /api/v1/domainsfor the full inventory. - If a domain is deleted between the event and the query, its row still appears with
domain_id: nullanddomain: "<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_onlyis 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.
SDK Methods
| Method | Description | Returns |
|---|---|---|
domains(min_risk, country, limit, offset) | Paginated scan results | list[dict] |
domain(name) | Single domain full detail | dict |
alerts(min_risk, since, limit) | High-risk findings, optional since datetime | list[dict] |
iter_alerts(min_risk, page_size) | Generator — auto-paginates through all results | Iterator[dict] |
stix_bundle(min_risk, limit) | STIX 2.1 bundle from /taxii endpoint | dict |
integrations() | List configured push integrations | list[dict] |
configure_integration(service, config) | Save integration credentials | dict |
test_integration(service, config) | Test connectivity without saving | {ok, message} |
Integration API
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.
POST /integrations/{service}
Save or update integration config. service is one of: splunk · sentinel · crowdstrike · gravityzone · cortex · jira · servicenow · webhook · stix
POST /integrations/{service}/test
Test connectivity without saving. Returns {"ok": true, "message": "..."}
DEL /integrations/{service}
Remove an integration config.
Supported services
| Service | Auth method | Trigger | Payload type |
|---|---|---|---|
| splunk | HEC token | risk ≥ 70 | CIM-aligned JSON event |
| sentinel | Workspace SharedKey HMAC | risk ≥ 70 | Flat KQL-ready fields (_s/_d/_b) |
| crowdstrike | OAuth2 client credentials | risk ≥ 70 | IOC (type=domain, action=detect/prevent) |
| gravityzone | Bearer access key | risk ≥ 70 | JSON-RPC createCustomRule |
| cortex | SHA-256 HMAC (api_key_id/nonce/ts) | risk ≥ 70 | External alert with MITRE tags |
| jira | Basic (email:api_token) | risk ≥ 70 AND cvss ≥ 7 | ADF-formatted issue with CVE/software tables |
| servicenow | Basic (user:password) | risk ≥ 70 | Incident with urgency/impact/SLA mapping |
| webhook | Optional HMAC-SHA256 signature | risk ≥ 70 | Enriched JSON payload |
| stix | Bearer (read via /taxii) | Pull-based | STIX 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.
GET /taxii/collections/swarmhawk/objects/
Returns a STIX 2.1 bundle of your high-risk domains as domain-name, vulnerability, and relationship objects.
| Param | Default | Description |
|---|---|---|
| min_risk | 70 | Minimum risk score (0–100) |
| limit | 100 | Max objects (1–500) |
STIX object types returned
| Type | Content |
|---|---|
| domain-name | Domain value, risk score, priority, country |
| vulnerability | CVE ID, CVSS score, description |
| relationship | domain-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.
pip install httpx and accept both CLI flags and environment variables.| Platform | Script | Min risk default | Download |
|---|---|---|---|
| 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 |
sdk/adapters/.Splunk TA Adapter
| Flag | Env var | Description |
|---|---|---|
| --api-key | SWARMHAWK_API_KEY | SwarmHawk API key |
| --min-risk | SWARMHAWK_MIN_RISK | Minimum risk score (default: 70) |
| --base-url | SWARMHAWK_BASE_URL | API base URL |
| --index | SWARMHAWK_SPLUNK_INDEX | Splunk index name (default: main) |
Emits sourcetype=swarmhawk:scan events with CIM-aligned fields: dest, vendor_product, category, severity.
Microsoft Sentinel Adapter
| Flag | Env var | Description |
|---|---|---|
| --workspace-id | AZURE_WORKSPACE_ID | Log Analytics workspace ID |
| --shared-key | AZURE_SHARED_KEY | Primary or secondary shared key |
| --log-type | AZURE_LOG_TYPE | Custom table name (default: SwarmHawk) |
| --min-risk | SWARMHAWK_MIN_RISK | Minimum 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
| Flag | Env var | Description |
|---|---|---|
| --cs-client-id | CS_CLIENT_ID | Falcon API client ID |
| --cs-client-secret | CS_CLIENT_SECRET | Falcon API client secret |
| --cs-base-url | CS_BASE_URL | Falcon base URL (default: api.crowdstrike.com) |
| --min-risk | SWARMHAWK_MIN_RISK | Minimum 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
| Flag | Env var | Description |
|---|---|---|
| --gz-api-url | GZ_API_URL | GravityZone JSON-RPC endpoint |
| --gz-access-key | GZ_ACCESS_KEY | GravityZone API access key |
| --gz-company-id | GZ_COMPANY_ID | Company/tenant ID |
| --min-risk | SWARMHAWK_MIN_RISK | Minimum 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
| Flag | Env var | Description |
|---|---|---|
| --xdr-api-key | XDR_API_KEY | Cortex XDR API key |
| --xdr-api-key-id | XDR_API_KEY_ID | API key numeric ID |
| --xdr-fqdn | XDR_FQDN | Your tenant FQDN (without https://api-) |
| --min-risk | SWARMHAWK_MIN_RISK | Minimum 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
| Flag | Env var | Description |
|---|---|---|
| --jira-base-url | JIRA_BASE_URL | Atlassian Cloud URL |
| --jira-email | JIRA_EMAIL | Atlassian account email |
| --jira-token | JIRA_TOKEN | Atlassian API token (id.atlassian.com → Security) |
| --jira-project | JIRA_PROJECT | Project key (default: SEC) |
| --jira-issue-type | JIRA_ISSUE_TYPE | Issue type (default: Bug) |
| --min-cvss | SWARMHAWK_MIN_CVSS | Minimum 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
| Flag | Env var | Description |
|---|---|---|
| --snow-instance | SNOW_INSTANCE | ServiceNow instance subdomain (no .service-now.com) |
| --snow-user | SNOW_USER | ServiceNow username |
| --snow-password | SNOW_PASSWORD | ServiceNow password |
| --snow-table | SNOW_TABLE | Target table (default: incident) |
| --min-risk | SWARMHAWK_MIN_RISK | Minimum risk score (default: 70) |
| Priority | Urgency | Impact | SLA target |
|---|---|---|---|
| CRITICAL | 1 | 1 | 4 hours |
| HIGH | 1 | 2 | 24 hours |
| MEDIUM | 2 | 2 | 7 days |
| LOW / INFO | 3 | 3 | 30 days |
Response Objects
Check object
| Field | Type | Description |
|---|---|---|
| check | string | Check identifier (see Check Names below) |
| status | string | ok · warning · critical · locked |
| title | string | One-line human-readable finding |
| detail | string | Full explanation with evidence |
| score_impact | number | Points added to risk score (0–40) |
Risk score interpretation
| Score | Level | Meaning |
|---|---|---|
| 0 – 29 | LOW RISK | Good security posture, minor issues |
| 30 – 59 | MEDIUM RISK | Notable issues requiring attention |
| 60 – 100 | HIGH RISK | Critical 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):
| check | Group | Description |
|---|---|---|
tls_protocol | TLS | Negotiated TLS version (flags TLS < 1.2) |
tls_cipher | TLS | Weak cipher suites |
tls_cert_expiry | TLS | Certificate expiry / already-expired |
tls_self_signed | TLS | Self-signed certificate |
tls_handshake | TLS | TLS handshake errors |
email_dmarc | DMARC missing or weak (p=none) | |
mx_blacklist | MX host on a public blocklist | |
blacklist | Reputation | Domain or A-record on a public blocklist (Spamhaus / URLhaus / Safe Browsing) |
ip_reputation | Reputation | Hosting IP reputation / geo / ASN signals |
cve_match | Vulns | Software fingerprint matched to known CVEs (CVSS + KEV + EPSS) |
exposed_git | Exposure | Public /.git/ directory |
exposed_env | Exposure | Public .env file |
mixed_content | Web | HTTPS page loads HTTP subresources |
sri_missing | Web | Subresource integrity missing on third-party scripts |
cookies_secure | Web | Cookies missing Secure / HttpOnly / SameSite |
takeover_risk | DNS | Dangling subdomain / takeover candidate |
security_txt | Hygiene | Missing /.well-known/security.txt (RFC 9116) |
whois_privacy | Hygiene | WHOIS privacy guard on / off |
ai_summary | Reporting | AI-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.
Get your free API key at www.swarmhawk.com → Sign In → Account → API Keys
Get Free API Key →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.