> ## Documentation Index
> Fetch the complete documentation index at: https://pentest-tools.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# API examples

> Python examples for common API workflows

These examples use Python and the `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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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}")
```

<Tip>
  Always store your API key in an environment variable:

  ```python theme={null}
  import os
  API_KEY = os.environ.get("PENTEST_TOOLS_API_KEY")
  ```
</Tip>

## Related topics

* [API overview](/api-reference)
* [Authentication](/api-reference/authentication)
* [Limits and errors](/api-reference/limits-and-errors)
