Trigger scan
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:writepermissions. - At least one asset registered in your S4E workspace.
- The
asset_idof the target asset (retrieve it from GET /api/assets).
Endpoint
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.
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.