Base URL (dev): http://localhost:4000
All /v1/* routes require Authorization: Bearer <jwt> except for /v1/auth/token.
Dev-only token minter. Refuses in NODE_ENV=production unless
SENTINEL_DEV_TOKEN_MINTER=1.
POST /v1/auth/token
{ "username": "alice", "password": "dev", "role": "analyst" }
200 OK
{ "token": "eyJhbGciOiJ...", "claims": { "sub": "alice", "role": "analyst" } }Roles: admin, analyst, viewer, service.
Returns up to 200 projects.
{ "slug": "payments-api", "name": "Payments API", "repoUrl": "https://github.com/org/payments" }Returns a single project.
Returns the last 50 scans for that project.
Trigger a scan. The API queues the scan (returns 202), then runs it against the scanner service asynchronously.
{
"projectSlug": "payments-api",
"workDir": "/workspace/payments-api",
"gitRef": "main",
"commitSha": "abc123...",
"kind": "full",
"triggeredBy": "ci"
}kind ∈ full | incremental | drift | ml_bom.
Returns the scan row (status, counters, risk score).
Returns up to 1000 components from a scan.
Returns up to 500 vulnerabilities, ordered by aiRiskScore DESC.
Semantic similarity search over the component graph.
{
"query": "logging library similar to log4j",
"topK": 20,
"ecosystem": "maven"
}Response:
{
"results": [
{ "name": "log4j-api", "version": "2.17.1", "purl": "...", "similarity": 0.92 }
]
}{ "approvedBy": "alice@corp" }Transitions a proposed remediation to queued, then immediately dispatches to the matching n8n webhook. Returns the updated row including n8n executionId.
Policy body shape:
{
"slug": "block-agpl",
"name": "Block AGPL in proprietary code",
"enabled": true,
"rules": {
"conditions": [
{ "field": "license", "op": "in", "value": ["AGPL-3.0", "SSPL-1.0"] }
],
"action": "block"
}
}op ∈ eq | neq | gt | gte | lt | lte | in | not_in | contains | matches.
action ∈ allow | warn | block | escalate | remediate | notify.
Returns the dashboard payload: overall counts, top-10 risks, per-ecosystem breakdown. One round-trip for the home page.
The server pushes JSON messages:
{ "topic": "project:123...", "message": { "kind": "sentinel.scan.completed", "payload": { ... } } }Client can also dynamically subscribe:
{ "type": "subscribe", "topics": ["scan:abc..."] }Every /v1/* route is rate-limited per-API-key per-route per-minute via Dragonfly.
Limits come back as X-RateLimit-* headers. Default: 120 req/min.
Uniform JSON shape:
{ "error": "short_slug", "detail": "optional human context" }Standard codes: 400 invalid_body, 401 unauthorized, 403 forbidden, 404 not_found, 429 rate_limited, 502 upstream_failed.