This guide explains how to programmatically trigger a security scan against one of your registered assets using the S4E REST API, and how to poll for completion.

Prerequisites

  • A valid API token with scan:write permissions.
  • At least one asset registered in your S4E workspace.
  • The asset_id of the target asset (retrieve it from GET /api/assets).

Endpoint

POST https://api.s4e.io/api/scan/create

Required Headers

Header Value Description
Authorization Bearer <token> Your API authentication token.
Content-Type application/json Request body format.

Request Payload

Field Type Required Description
asset_id string Yes UUID of the target asset.
scan_type string Yes Scan category. One of full, quick, web_app, network, api, ssl.
options object No Additional scan options (see below).

Scan Options

The options object allows fine-grained control over scan behaviour:

Key Type Default Description
follow_redirects boolean true Whether the scanner should follow HTTP redirects.
max_depth integer 5 Maximum crawl depth for web application scans.
timeout integer 3600 Scan timeout in seconds.
enable_bruteforce boolean false Enable directory and parameter brute-forcing.

Response Format

A successful request returns 201 Created:

{
  "scan_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "queued",
  "created_at": "2026-04-28T14:30:00Z",
  "estimated_duration": 1200
}
Field Type Description
scan_id string Unique identifier of the created scan.
status string Initial status, typically queued.
created_at string ISO 8601 timestamp of scan creation.
estimated_duration integer Estimated runtime in seconds (may be null).

Polling for Scan Completion

After triggering a scan, poll its status until it reaches a terminal state.

GET https://api.s4e.io/api/scan/{scan_id}/status

Status Values

Status Description
queued Scan is waiting in the execution queue.
running Scan is actively in progress.
completed Scan finished successfully.
failed Scan encountered an unrecoverable error.
cancelled Scan was cancelled by a user or policy rule.

Tip

Use exponential back-off when polling. Start with a 5-second interval and increase it gradually to avoid rate-limit errors.

Warning

Do not poll more frequently than once per second. Requests that exceed the rate limit will receive a 429 Too Many Requests response.

Error Handling

HTTP Code Meaning Typical Cause
400 Bad Request Missing or invalid field in the payload.
401 Unauthorized Missing or expired Bearer token.
403 Forbidden Token lacks scan:write permission.
404 Not Found The specified asset_id does not exist.
409 Conflict A scan is already running for this asset.
429 Too Many Requests Rate limit exceeded.
500 Internal Server Error Unexpected server-side failure.

Note

When a 409 Conflict is returned, the response body includes the scan_id of the already-running scan so you can poll that one instead of creating a duplicate.

Examples

curl

# Trigger a full scan
curl -X POST https://api.s4e.io/api/scan/create \
  -H "Authorization: Bearer $S4E_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "asset_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "scan_type": "full",
    "options": {
      "max_depth": 10,
      "enable_bruteforce": false
    }
  }'

# Poll scan status
curl -s https://api.s4e.io/api/scan/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status \
  -H "Authorization: Bearer $S4E_API_TOKEN" | python3 -m json.tool

Python

import time
import requests

BASE_URL = "https://api.s4e.io"
HEADERS = {
    "Authorization": "Bearer <your-token>",
    "Content-Type": "application/json",
}

# 1. Trigger the scan
payload = {
    "asset_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "scan_type": "full",
    "options": {"max_depth": 10},
}
resp = requests.post(f"{BASE_URL}/api/scan/create", json=payload, headers=HEADERS)
resp.raise_for_status()
scan_id = resp.json()["scan_id"]
print(f"Scan created: {scan_id}")

# 2. Poll until completion
interval = 5
while True:
    status_resp = requests.get(
        f"{BASE_URL}/api/scan/{scan_id}/status", headers=HEADERS
    )
    status_resp.raise_for_status()
    state = status_resp.json()["status"]
    print(f"Scan status: {state}")

    if state in ("completed", "failed", "cancelled"):
        break

    time.sleep(interval)
    interval = min(interval * 2, 60)  # exponential back-off

if state == "completed":
    print("Scan finished successfully.")
else:
    print(f"Scan ended with status: {state}")

Tip

Store your API token in an environment variable (S4E_API_TOKEN) rather than hard-coding it in scripts. This prevents accidental exposure in version control.