requests library to cover the most common workflows: managing targets, running scans, retrieving findings, and generating reports. Each section builds on the setup below, so start there before copying individual snippets.
Setup
Copy
Ask AI
import json
import time
import requests
from enum import IntEnum
API_KEY = "YOUR_API_KEY" # Get from My account > API
API_URL = "https://app.pentest-tools.com/api/v2"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
class Tool(IntEnum):
"""Available scanning tools and their IDs"""
SUBDOMAIN_FINDER = 20
WHOIS_LOOKUP = 30
PORT_SCANNER = 70
URL_FUZZER = 90
VHOSTS_FINDER = 160
WEBSITE_SCANNER = 170
ICMP_PING = 240
SHAREPOINT_SCANNER = 260
WORDPRESS_SCANNER = 270
DRUPAL_SCANNER = 280
JOOMLA_SCANNER = 290
WEBSITE_RECON = 310
SUBDOMAIN_TAKEOVER = 320
NETWORK_SCANNER = 350
SQLI_EXPLOITER = 380
DOMAIN_FINDER = 390
PASSWORD_AUDITOR = 400
SSL_SCANNER = 450
SNIPER = 490
WAF_DETECTOR = 500
API_SCANNER = 510
CLOUD_SCANNER = 520
PEOPLE_HUNTER = 530
KUBERNETES_SCANNER = 540
Targets
List all targets
Copy
Ask AI
def get_targets(workspace_id: int = None) -> list:
"""Get all targets, optionally filtered by workspace"""
params = {}
if workspace_id:
params["workspace_id"] = workspace_id
response = requests.get(
f"{API_URL}/targets",
headers=HEADERS,
params=params
)
response.raise_for_status()
return response.json()["data"]
# Usage
targets = get_targets()
for target in targets:
print(f"{target['id']}: {target['name']}")
Create a target
Copy
Ask AI
def create_target(name: str, description: str = "", workspace_id: int = None) -> int:
"""Create a new target and return its ID"""
data = {"name": name}
if description:
data["description"] = description
if workspace_id:
data["workspace_id"] = workspace_id
response = requests.post(
f"{API_URL}/targets",
headers=HEADERS,
json=data
)
response.raise_for_status()
return response.json()["data"]["created_id"]
# Usage
target_id = create_target(
name="https://example.com",
description="Main production site"
)
print(f"Created target: {target_id}")
Get target by ID
Copy
Ask AI
def get_target(target_id: int) -> dict:
"""Get a specific target by ID"""
response = requests.get(
f"{API_URL}/targets/{target_id}",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
target = get_target(12345)
print(f"Target: {target['name']}")
Delete a target
Copy
Ask AI
def delete_target(target_id: int) -> bool:
"""Delete a target"""
response = requests.delete(
f"{API_URL}/targets/{target_id}",
headers=HEADERS
)
return response.status_code == 204
# Usage
if delete_target(12345):
print("Target deleted")
Scans
Start a scan
Copy
Ask AI
def start_scan(
tool_id: int,
target_name: str = None,
target_id: int = None,
tool_params: dict = None,
workspace_id: int = None,
vpn_profile_uuid: str = None
) -> dict:
"""
Start a scan using either target_name or target_id.
Returns dict with target_id and created_id (scan_id).
"""
if not (target_name or target_id):
raise ValueError("Provide either target_name or target_id")
data = {"tool_id": tool_id}
if target_name:
data["target_name"] = target_name
else:
data["target_id"] = target_id
if tool_params:
data["tool_params"] = tool_params
if workspace_id:
data["workspace_id"] = workspace_id
if vpn_profile_uuid:
data["vpn_profile_uuid"] = vpn_profile_uuid
response = requests.post(
f"{API_URL}/scans",
headers=HEADERS,
json=data
)
response.raise_for_status()
return response.json()["data"]
# Usage: Start a Website Scanner with light scan
result = start_scan(
tool_id=Tool.WEBSITE_SCANNER,
target_name="https://example.com",
tool_params={"scan_type": "light"}
)
scan_id = result["created_id"]
print(f"Started scan {scan_id}")
Get scan status
Copy
Ask AI
def get_scan(scan_id: int) -> dict:
"""Get scan details and status"""
response = requests.get(
f"{API_URL}/scans/{scan_id}",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
scan = get_scan(12345)
print(f"Status: {scan['status_name']}, Progress: {scan['progress']}%")
List scans
Copy
Ask AI
def get_scans(
workspace_id: int = None,
target_id: int = None,
tool_id: int = None,
status: str = None,
started_after: str = None, # ISO date, e.g. "2025-01-01"
started_before: str = None, # ISO date, e.g. "2025-01-31"
limit: int = 100,
page: int = 1
) -> list:
"""
List scans with optional filters.
Valid status values: running, finished, stopped, timed out, waiting,
aborted, failed to start, VPN connection error, auth failed, connection error
"""
params = {"limit": limit, "page": page}
if workspace_id:
params["workspace_id"] = workspace_id
if target_id:
params["target_id"] = target_id
if tool_id:
params["tool_id"] = tool_id
if status:
params["status"] = status
if started_after:
params["start_time[start]"] = started_after
if started_before:
params["start_time[end]"] = started_before
response = requests.get(
f"{API_URL}/scans",
headers=HEADERS,
params=params
)
response.raise_for_status()
return response.json()["data"]
# Usage: Get all running scans
running_scans = get_scans(status="running")
for scan in running_scans:
print(f"Scan {scan['id']}: {scan['tool_name']} - {scan['progress']}%")
# Usage: Get all Website Scanner scans started in January 2025
website_scans = get_scans(
tool_id=Tool.WEBSITE_SCANNER,
started_after="2025-01-01",
started_before="2025-01-31"
)
Get scan output
Copy
Ask AI
def get_scan_output(scan_id: int) -> dict:
"""Get the JSON output of a completed scan"""
response = requests.get(
f"{API_URL}/scans/{scan_id}/output",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
output = get_scan_output(12345)
print(json.dumps(output, indent=2))
Stop a scan
Copy
Ask AI
def stop_scan(scan_id: int) -> bool:
"""Stop a running scan"""
response = requests.post(
f"{API_URL}/scans/{scan_id}/stop",
headers=HEADERS
)
return response.status_code == 204
# Usage
if stop_scan(12345):
print("Scan stopped")
Delete a scan
Copy
Ask AI
def delete_scan(scan_id: int) -> bool:
"""Delete a completed scan"""
response = requests.delete(
f"{API_URL}/scans/{scan_id}",
headers=HEADERS
)
return response.status_code == 204
Workspaces
List workspaces
Copy
Ask AI
def get_workspaces() -> list:
"""Get all workspaces"""
response = requests.get(
f"{API_URL}/workspaces",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
workspaces = get_workspaces()
for ws in workspaces:
print(f"{ws['id']}: {ws['name']} ({ws['scan_count']} scans, {ws['target_count']} targets)")
Create a workspace
Copy
Ask AI
def create_workspace(name: str, description: str = "") -> int:
"""Create a new workspace"""
data = {"name": name}
if description:
data["description"] = description
response = requests.post(
f"{API_URL}/workspaces",
headers=HEADERS,
json=data
)
response.raise_for_status()
return response.json()["data"]["created_id"]
# Usage
workspace_id = create_workspace("Q4 2024 Audit", "External penetration test")
Update a workspace
Copy
Ask AI
def update_workspace(workspace_id: int, name: str = None, description: str = None) -> bool:
"""Update workspace name and/or description"""
data = {}
if name:
data["name"] = name
if description is not None:
data["description"] = description
response = requests.put(
f"{API_URL}/workspaces/{workspace_id}",
headers=HEADERS,
json=data
)
return response.status_code == 204
Delete a workspace
Copy
Ask AI
def delete_workspace(workspace_id: int) -> bool:
"""Delete a workspace (cannot delete current active workspace)"""
response = requests.delete(
f"{API_URL}/workspaces/{workspace_id}",
headers=HEADERS
)
return response.status_code == 204
Findings
List findings
Copy
Ask AI
def get_findings(
workspace_id: int = None,
target_id: int = None,
task_id: int = None,
group_duplicates: bool = False,
limit: int = 100,
page: int = 1
) -> list:
"""Get findings with optional filters"""
params = {"limit": limit, "page": page}
if workspace_id:
params["workspace_id"] = workspace_id
if target_id:
params["target_id"] = target_id
if task_id:
params["task_id"] = task_id
if group_duplicates:
params["group_duplicates"] = "true"
response = requests.get(
f"{API_URL}/findings",
headers=HEADERS,
params=params
)
response.raise_for_status()
return response.json()["data"]
# Usage: Get findings from a specific scan
findings = get_findings(task_id=12345)
for finding in findings:
print(f"[{finding['cvss']}] {finding['name']}")
Get finding details
Copy
Ask AI
def get_finding(finding_id: int) -> dict:
"""Get detailed information about a finding"""
response = requests.get(
f"{API_URL}/findings/{finding_id}",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
finding = get_finding(12345)
print(f"Name: {finding['name']}")
print(f"Severity: {finding['risk_level']}")
print(f"Description: {finding['vuln_description']}")
Reports
Create a report
Copy
Ask AI
def create_report(
source: str,
resource_ids: list,
format: str = "pdf",
group_by: str = "target",
webhook_url: str = None
) -> int:
"""
Create a report from scans or findings.
Args:
source: "scans" or "findings"
resource_ids: List of scan IDs or finding IDs
format: "pdf", "docx", "html", "json", "csv", or "xlsx"
group_by: "target" or "vulnerability"
webhook_url: Optional URL to receive notification when ready
Returns:
Report ID
"""
data = {
"source": source,
"resources": resource_ids,
"format": format,
"group_by": group_by
}
if webhook_url:
data["webhook_url"] = webhook_url
response = requests.post(
f"{API_URL}/reports",
headers=HEADERS,
json=data
)
response.raise_for_status()
return response.json()["data"]["report_id"]
# Usage: Generate PDF report from scans
report_id = create_report(
source="scans",
resource_ids=[123, 124, 125],
format="pdf",
group_by="vulnerability"
)
print(f"Report {report_id} is being generated")
Get report status
Copy
Ask AI
def get_report(report_id: int) -> dict:
"""Get report details and status"""
response = requests.get(
f"{API_URL}/reports/{report_id}",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
report = get_report(12345)
print(f"Status: {report['status']}")
Download a report
Copy
Ask AI
def download_report(report_id: int, output_path: str) -> bool:
"""Download a completed report to a file"""
response = requests.get(
f"{API_URL}/reports/{report_id}/download",
headers=HEADERS
)
if response.status_code == 202:
print("Report not ready yet")
return False
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
return True
# Usage
if download_report(12345, "pentest_report.pdf"):
print("Report downloaded successfully")
HTTP Loggers
Create an HTTP logger
Copy
Ask AI
def create_http_logger(label: str) -> int:
"""Create a new HTTP logger for out-of-band testing"""
response = requests.post(
f"{API_URL}/http_loggers",
headers=HEADERS,
json={"label": label}
)
response.raise_for_status()
return response.json()["data"]["created_id"]
# Usage
logger_id = create_http_logger("SSRF Test - Production")
List HTTP loggers
Copy
Ask AI
def get_http_loggers() -> list:
"""Get all HTTP loggers"""
response = requests.get(
f"{API_URL}/http_loggers",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
loggers = get_http_loggers()
for logger in loggers:
print(f"{logger['label']}: {logger['num_requests']} requests captured")
Get logger data
Copy
Ask AI
def get_http_logger_data(logger_id: int) -> list:
"""Get all captured requests for a logger"""
response = requests.get(
f"{API_URL}/http_loggers/{logger_id}/data",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]["requests"]
# Usage: Check for SSRF callbacks
data = get_http_logger_data(12345)
for request in data:
print(f"{request['request_method']} from {request['ip_address']}")
Clear logger data
Copy
Ask AI
def clear_http_logger_data(logger_id: int) -> bool:
"""Clear all captured data for a logger"""
response = requests.delete(
f"{API_URL}/http_loggers/{logger_id}/data",
headers=HEADERS
)
return response.status_code == 204
VPN Profiles
List VPN profiles
Copy
Ask AI
def get_vpn_profiles() -> list:
"""Get all VPN profiles for internal network scanning"""
response = requests.get(
f"{API_URL}/vpn_profiles",
headers=HEADERS
)
response.raise_for_status()
return response.json()["data"]
# Usage
profiles = get_vpn_profiles()
for profile in profiles:
print(f"{profile['name']}: {profile['id']}")
Complete workflows
Full scan workflow with polling
Copy
Ask AI
def run_scan_and_wait(
tool_id: int,
target: str,
tool_params: dict = None,
poll_interval: int = 10,
timeout: int = 3600
) -> dict:
"""
Start a scan, wait for completion, and return results.
Args:
tool_id: Tool to use
target: Target URL or hostname
tool_params: Tool-specific parameters
poll_interval: Seconds between status checks
timeout: Maximum wait time in seconds
Returns:
Scan output data
"""
# Start the scan
result = start_scan(
tool_id=tool_id,
target_name=target,
tool_params=tool_params or {}
)
scan_id = result["created_id"]
print(f"Started scan {scan_id}")
# Poll for completion
start_time = time.time()
while True:
if time.time() - start_time > timeout:
raise TimeoutError(f"Scan {scan_id} timed out")
scan = get_scan(scan_id)
status = scan["status_name"]
progress = scan.get("progress", 0)
print(f"Status: {status}, Progress: {progress}%")
if status == "finished":
return get_scan_output(scan_id)
elif status in ("stopped", "failed to start", "timed out", "aborted",
"VPN connection error", "auth failed", "connection error"):
raise RuntimeError(f"Scan {status}")
time.sleep(poll_interval)
# Usage
output = run_scan_and_wait(
tool_id=Tool.WEBSITE_SCANNER,
target="https://example.com",
tool_params={"scan_type": "light"}
)
# Save results
with open("scan_results.json", "w") as f:
json.dump(output, f, indent=2)
Batch scanning multiple targets
Copy
Ask AI
def batch_scan(
targets: list,
tool_id: int,
tool_params: dict = None,
workspace_id: int = None
) -> list:
"""
Start scans on multiple targets and return scan IDs.
Respects rate limits with automatic retry.
"""
scan_ids = []
for target in targets:
while True:
try:
result = start_scan(
tool_id=tool_id,
target_name=target,
tool_params=tool_params,
workspace_id=workspace_id
)
scan_ids.append(result["created_id"])
print(f"Started scan for {target}: {result['created_id']}")
break
except requests.HTTPError as e:
if e.response.status_code == 429:
retry_after = int(e.response.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
else:
raise
return scan_ids
# Usage
targets = [
"https://app.example.com",
"https://api.example.com",
"https://admin.example.com"
]
scan_ids = batch_scan(
targets=targets,
tool_id=Tool.WEBSITE_SCANNER,
tool_params={"scan_type": "deep"}
)
Wait for multiple scans
Copy
Ask AI
def wait_for_scans(scan_ids: list, poll_interval: int = 30) -> dict:
"""
Wait for multiple scans to complete.
Returns dict mapping scan_id to final status.
"""
pending = set(scan_ids)
results = {}
while pending:
for scan_id in list(pending):
scan = get_scan(scan_id)
status = scan["status_name"]
if status in ("finished", "stopped", "failed to start", "timed out",
"aborted", "VPN connection error", "auth failed", "connection error"):
results[scan_id] = status
pending.remove(scan_id)
print(f"Scan {scan_id}: {status}")
if pending:
print(f"Waiting for {len(pending)} scans...")
time.sleep(poll_interval)
return results
# Usage
statuses = wait_for_scans(scan_ids)
finished = [sid for sid, status in statuses.items() if status == "finished"]
print(f"{len(finished)}/{len(scan_ids)} scans completed successfully")
Generate report after scans
Copy
Ask AI
def scan_and_report(
targets: list,
tool_id: int,
tool_params: dict = None,
report_format: str = "pdf"
) -> str:
"""
Scan multiple targets and generate a consolidated report.
Returns path to downloaded report.
"""
# Start all scans
scan_ids = batch_scan(targets, tool_id, tool_params)
# Wait for completion
statuses = wait_for_scans(scan_ids)
# Get completed scans
completed = [sid for sid, s in statuses.items() if s == "finished"]
if not completed:
raise RuntimeError("No scans completed successfully")
# Generate report
report_id = create_report(
source="scans",
resource_ids=completed,
format=report_format,
group_by="vulnerability"
)
# Wait for report
while True:
report = get_report(report_id)
if report["status"] == "finished":
break
elif report["status"] in ("failed", "unauthorized"):
raise RuntimeError(f"Report generation failed: {report['status']}")
time.sleep(5)
# Download
output_path = f"report_{report_id}.{report_format}"
download_report(report_id, output_path)
return output_path
# Usage
report_path = scan_and_report(
targets=["https://example.com", "https://api.example.com"],
tool_id=Tool.WEBSITE_SCANNER,
tool_params={"scan_type": "deep"},
report_format="pdf"
)
print(f"Report saved to: {report_path}")
Always store your API key in an environment variable:
Copy
Ask AI
import os
API_KEY = os.environ.get("PENTEST_TOOLS_API_KEY")