REST API Reference
REST API Reference
Crucible exposes an HTTP API for launching scenarios, querying execution history, restarting runs, and retrieving assessment reports. The implementation lives in apps/demo-dashboard/src/server/backend.ts. Real-time deltas stream over a WebSocket on the same host (see WebSocket events below).
The default base path is /api. The default WebSocket path is /. Both are configurable via attachCrucibleBackend() options.
Conventions
- All request bodies are JSON (
Content-Type: application/json). - All responses are JSON unless noted (HTML and PDF report exports are returned as files).
- Validation failures return
400with{ error, issues[] }. Theissuesarray uses Zod’s{ code, message, path }shape so clients can surface field-level errors. - Unknown top-level fields on launch endpoints are rejected — strict schemas catch typos early instead of silently dropping data.
GET /health
Liveness probe and informational metadata. The targetUrl field reports the engine’s default target — the value resolved from CRUCIBLE_TARGET_URL or the engine constructor at startup. Per-execution overrides are not reflected here. Web clients use this value to prefill launch dialogs.
Response 200
{
"status": "ok",
"timestamp": 1714291200000,
"scenarios": 87,
"targetUrl": "http://localhost:8880"
}
Per-run target URL
Three launch endpoints accept an optional targetUrl field that overrides the engine default for the duration of one execution:
POST /api/simulationsPOST /api/assessments
Restart inherits — see POST /api/executions/:id/restart for the semantics.
Validation rules
A provided targetUrl must:
- Be an absolute URL with
httporhttpsscheme. - Parse with the WHATWG URL parser.
- Have a non-empty hostname.
- Not include credentials (
user:pass@host). - Not include a fragment (
#section).
The check runs at the request boundary and again inside engine.startScenario(), which throws ScenarioTargetUrlError on the second pass. Either layer rejecting the override returns 400 with the offending message; no execution row is persisted.
Outbound allowlist scoping
Each execution gets its own outbound SSRF allowlist scoped to the effective target. Two concurrent runs against different hosts cannot pivot off each other — the allowlist for run A does not include run B’s host. See docs/architecture/scenario-engine.md for the full target-resolution and allowlist model.
POST /api/simulations
Launches a scenario in simulation mode. Returns immediately with the execution id; the run streams over the WebSocket.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
scenarioId |
string | yes | Catalog id of the scenario to run |
targetUrl |
string | null | no | Per-run override (see above). Omit to use engine default. |
triggerData |
object | no | Forwarded verbatim to the scenario. Recognized keys: expectWafBlocking (boolean) |
expectWafBlocking |
boolean | no | Legacy top-level form. Cannot be combined with the same key inside triggerData. |
Response 200
{
"executionId": "f3D7w3g0aY",
"mode": "simulation",
"wsUrl": "ws://localhost:3001/"
}
Response 400
Invalid scenarioId, malformed targetUrl, conflicting expectWafBlocking placements, or unknown top-level fields.
POST /api/assessments
Same shape as simulation launch but in assessment mode, which produces a final report.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
scenarioId |
string | yes | Catalog id of the scenario to run |
targetUrl |
string | null | no | Per-run override |
triggerData |
object | no | Forwarded verbatim. expectWafBlocking is not supported here — assessments evaluate against the scenario’s own assertions. |
Response 200
{
"executionId": "f3D7w3g0aY",
"mode": "assessment",
"reportUrl": "/api/reports/f3D7w3g0aY"
}
POST /api/executions/:id/restart
Replays an existing execution. The new run is a fresh execution row with a parentExecutionId reference back to :id, so restart chains are traceable in history.
Restart inherits the target. The new run uses the originating execution’s persisted targetUrl, not the current engine default and not a caller override. Restart deliberately does not expose a targetUrl parameter; if you want a different target, start a new run via POST /api/simulations or /api/assessments. This keeps restart idempotent — running the same execution id a week later still hits the same host even if the engine default has drifted.
If the source execution is currently running, pending, or paused, restart cancels it before starting the replay.
Response 200
{ "executionId": "9KpNc1xC2u" }
Response 400
The originating execution’s stored targetUrl no longer passes validation, or the scenario was edited after the original run in a way the engine can’t replay (e.g., a step type that’s no longer supported). The error message identifies the failing condition.
Response 404
Source execution id does not exist.
Response 500
Unexpected engine error during replay (e.g., persistence layer failure). The error message is surfaced for debugging.
GET /api/executions
Lists executions with optional filtering.
Query parameters
| Param | Type | Notes |
|---|---|---|
scenarioId |
string | Filter by scenario id |
status |
comma-separated list | Any of pending, running, completed, failed, cancelled, paused, skipped |
mode |
string | simulation or assessment |
since |
unix-ms | Only executions started at or after this timestamp |
until |
unix-ms | Only executions started at or before this timestamp |
limit |
int | Max 200, default 50 |
offset |
int | Pagination offset |
Response 200 — array of execution records.
GET /api/executions/:id
Returns the in-memory execution record (or its restored snapshot for terminal/evicted runs).
Execution control endpoints
All return { ok: true } on success or { count: N } for batch operations.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/executions/:id/pause |
Pause a running execution |
POST |
/api/executions/:id/resume |
Resume a paused execution |
POST |
/api/executions/:id/cancel |
Cancel a running or paused execution |
POST |
/api/executions/pause-all |
Pause every running execution |
POST |
/api/executions/resume-all |
Resume every paused execution |
POST |
/api/executions/cancel-all |
Cancel every active execution |
State-machine violations (e.g. pausing a completed execution) return 409 with a descriptive error.
Reports
| Method | Path | Purpose |
|---|---|---|
GET |
/api/reports/:id |
Returns the inline report JSON or the execution if not yet terminal |
GET |
/api/reports/:id/json |
Downloads the canonical JSON report file |
GET |
/api/reports/:id/html |
Downloads the HTML report |
GET |
/api/reports/:id/pdf |
Downloads the PDF report |
GET |
/api/reports/:id?format=json\|html |
Equivalent to the dedicated suffix endpoints |
If the report is still being generated, the endpoint responds with 202 and the current execution snapshot.
Scenario catalog
| Method | Path | Purpose |
|---|---|---|
GET |
/api/scenarios |
List all catalog scenarios |
PUT |
/api/scenarios/:id |
Update an existing catalog scenario; returns 400 on validation failure, 404 if the id is unknown |
WebSocket events
The WebSocket broadcasts execution lifecycle events to every connected client. New connections receive a STATUS_UPDATE snapshot for each currently-known execution as a seed.
Event payloads carry a targetUrl field on the execution object — clients can render “running against X” for each row without an extra round trip. Subsequent EXECUTION_DELTA messages omit targetUrl because it doesn’t change after creation; the snapshot already established it.
| Event type | Format | When |
|---|---|---|
STATUS_UPDATE |
snapshot | Connection seed; one per known execution |
EXECUTION_STARTED |
snapshot | New execution created (REST or WS-initiated) |
EXECUTION_UPDATED |
snapshot (first) / delta (subsequent) | State, step, or context change |
EXECUTION_DELTA |
delta | Incremental change after the first update |
EXECUTION_COMPLETED |
snapshot | Run reached completed |
EXECUTION_FAILED |
snapshot | Run reached failed |
EXECUTION_CANCELLED |
snapshot | Run reached cancelled |
EXECUTION_PAUSED |
snapshot | Run paused |
EXECUTION_RESUMED |
snapshot | Run resumed |
TERMINAL_OUTPUT |
n/a | Stdout/stderr from a terminal session |
Snapshot envelope:
{
"type": "EXECUTION_STARTED",
"format": "snapshot",
"timestamp": 1714291200000,
"payload": {
"id": "f3D7w3g0aY",
"scenarioId": "advanced-sqli-campaign",
"mode": "simulation",
"status": "pending",
"targetUrl": "http://staging.example:8080",
"steps": [],
"...": "remaining ScenarioExecution fields"
}
}
Delta envelope:
{
"type": "EXECUTION_DELTA",
"format": "delta",
"timestamp": 1714291200500,
"payload": {
"id": "f3D7w3g0aY",
"changes": {
"status": "running",
"steps": [{ "stepId": "step-0", "status": "running", "attempts": 1 }]
}
}
}
Related
- Scenario engine architecture — target resolution, outbound allowlist, DAG scheduling
- Database schema reference — persistence model for executions