Scans API
Base URL: https://app.pentestas.com. All endpoints require authentication β see Authentication.
Create a scan
POST /api/scans
Body:
{
"target_url": "https://app.example.com",
"scan_types": ["web", "api", "discovery"],
"config": {
"custom_headers": {"Authorization": "Bearer <token>"},
"cookies": {"sid": "abc..."},
"credentials": {"username": "scanner@example.com", "password": "..."},
"oauth_refresh": {
"token_url": "https://auth.example.com/oauth/token",
"refresh_token": "rt_..."
},
"max_depth": 3,
"active_verify": false
}
}
Response:
{
"scan_id": "c4e6f0b3-...",
"status": "pending",
"tenant_id": "...",
"target_url": "https://app.example.com",
"scan_types": ["web", "api", "discovery"],
"created_at": "2026-04-20T21:00:00Z"
}
The scan is enqueued immediately. Progress streams on /ws/scan/{scan_id}?token=<jwt>.
Field reference
| Field | Type | Required | Notes |
|---|---|---|---|
target_url |
string | yes | Full URL with scheme |
scan_types |
array[string] | yes | web / api / network / cloud / azure / google_workspace / discovery / auth / subdomain |
config |
object | no | See below |
config.custom_headers |
map | no | Injected on every request |
config.cookies |
map | no | Cookie name β value |
config.credentials |
object | no | {username, password, login_url?} for form login |
config.oauth_refresh |
object | no | See Authenticated scans |
config.max_depth |
int | no | Crawl depth (default 3, max plan-dependent) |
config.active_verify |
bool | no | Pro+ only; confirm with destructive probes |
config.agent_id |
uuid | no | Dispatch scan through a specific agent |
List scans
GET /api/scans?status=running&limit=50&offset=0
Filters:
statusβpending/running/completed/failed/cancelledtarget_urlβ substring matchscan_typeβ filter to scans including a specific typesinceβ ISO 8601 datetimelimitβ 1β100, default 20offsetβ pagination
Response: { total, scans: [{scan_id, status, target_url, findings_count, created_at, ...}] }.
Get a scan
GET /api/scans/{scan_id}
Returns the full scan object including live progress fields (current_phase, progress_message).
Get findings for a scan
GET /api/scans/{scan_id}/findings?severity=HIGH&verified=true
See Findings API.
Get the exploit graph
GET /api/scans/{scan_id}/graph
Returns attack chains + mermaid diagram text + graph edges. See Attack chain synthesis.
Export a report
GET /api/scans/{scan_id}/report?format=pdf
Formats: html, pdf, docx, json. Response is the binary; Content-Disposition: attachment set.
Cancel a scan
POST /api/scans/{scan_id}/cancel
Sets status to cancelled. A checkpoint is written so you can POST /api/scans/{scan_id}/resume later.
Resume
POST /api/scans/{scan_id}/resume
Restarts from the last checkpoint (typically a phase boundary).
Delete
DELETE /api/scans/{scan_id}
Cascade-deletes findings + reports. Audit-logged.
Rate limits
POST /api/scansβ 30/min per tenant.GETendpoints β 30/s per IP, burst 60.- Concurrent scan cap enforced: Free = 1, Pro = 5, Enterprise = negotiated.
Errors
| Status | Meaning |
|---|---|
| 400 | Validation failed (bad URL, unknown scan type) |
| 401 | Missing / invalid auth |
| 403 | Target domain not verified; demo user scanning outside allowed domains |
| 404 | Scan not found OR belongs to a different tenant |
| 409 | Too many concurrent scans |
| 429 | Rate limit |
| 503 | Celery queue full β retry with exponential backoff |
See also
- Authentication
- Findings API
- Webhooks β push scan lifecycle events to your endpoint