API Reference

Every HTTP and WebSocket endpoint the Sentinel dashboard API exposes. Paths are relative to your configured prefix (default /sentinel), so /api/threats is served at /sentinel/api/threats. Examples use http://localhost:8080/sentinel.

Authentication

Every endpoint except POST /api/auth/login, POST /api/auth/logout, and POST /csp-report requires Authorization: Bearer <token>. A missing, expired, or invalid token returns 401.

Response Format

Paginated lists wrap results in data with a meta object:

Paginated Responsejson
{
"data": [ ... ],
"meta": { "total": 142, "page": 1, "page_size": 20 }
}

Single resources come back as { "data": { ... } }; actions return a message:

Action and Error Responsesjson
{ "message": "Threat resolved" }
{ "error": "Invalid credentials", "code": "UNAUTHORIZED" }

Errors use standard status codes with an error message and a machine-readable code such as BAD_REQUEST, NOT_FOUND, RATE_LIMITED, WAF_DISABLED, or EPHEMERAL_STORAGE.

Authentication

MethodPathDescription
POST/api/auth/loginExchange {"username","password"} for a JWT ({"token","expires_in":86400}). Limited to 10 attempts per 15 minutes per client IP; every attempt is written to the audit log.
POST/api/auth/logoutReturns success. Tokens are stateless and are not revoked — discard the token client-side.
GET/api/auth/verifyReturns 200 if the token is valid, 401 if not.
Loginbash
TOKEN=$(curl -s -X POST http://localhost:8080/sentinel/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "your-password"}' | jq -r .token)

Threats

MethodPathDescription
GET/api/threatsPaginated threat events. Filters: severity (Low, Medium, High, Critical), type (e.g. SQLi, XSS), ip, search (path, IP, User-Agent), start_time / end_time (RFC 3339). Sort: sort_by = timestamp | severity | ip | path, sort_order = asc | desc.
GET/api/threats/:idOne threat event with its evidence, request metadata, CVSS score, and location.
POST/api/threats/:id/resolveMark a threat resolved. Audited.
POST/api/threats/:id/false-positiveMark a threat as a false positive (and resolved). Audited.
List Threatsbash
curl "http://localhost:8080/sentinel/api/threats?severity=High&type=SQLi&page=1&page_size=20" \
-H "Authorization: Bearer $TOKEN"
# {
# "data": [
# {
# "id": "3f9c...",
# "timestamp": "2026-09-11T10:30:00Z",
# "ip": "203.0.113.42",
# "method": "GET",
# "path": "/api/users",
# "threat_types": ["SQLi"],
# "severity": "High",
# "confidence": 80,
# "blocked": true,
# "cvss": 9.8,
# ...
# }
# ],
# "meta": { "total": 47, "page": 1, "page_size": 20 }
# }

Actors and IP Blocks

MethodPathDescription
GET/api/actorsPaginated threat actors (one per source IP). Filters: status, search, min_risk.
GET/api/actors/:ipOne actor profile: risk score, counts, attack types, targeted routes, location.
GET/api/actors/:ip/requestsPaginated threat events from one IP.
POST/api/actors/:ip/blockBlock the actor's IP. Optional body {"reason","permanent"}; blocks last 24 hours unless permanent is true. Audited.
GET/api/ip/blockedEvery blocked IP and CIDR with reason and expiry.
POST/api/ip/blockBlock an IP or CIDR. Body {"ip","reason","expiry","permanent"}; expiry is RFC 3339, default 24 hours. Audited.
DELETE/api/ip/block/:ipUnblock. Write a CIDR with _ for / (10.0.0.0_8). Audited.
GET/api/ip/:ip/reputationAbuseIPDB lookup (cached 24 hours). Blocks the IP when AutoBlock is on and the score clears MinAbuseScore.
GET/api/ip/feedsLive reputation checking (enabled, used_today, max_per_day) and each blocklist feed's entries, last_refresh, and error.

WAF

MethodPathDescription
GET/api/waf/rulesThe running WAF's mode and per-category rules (sensitivity levels), and whether the WAF is enabled.
PUT/api/waf/rulesChange the running WAF: body {"mode":"block","rules":{"SQLInjection":"strict"}}. Rule fields you omit keep their value. Invalid mode or level → 400; WAF disabled → 409 WAF_DISABLED. Applies immediately, and is stored so it survives a restart and reaches every replica (v2.6.0+); the response carries a warning when it could not be stored. Audited.
GET/api/waf/custom-rulesAll custom rules.
POST/api/waf/custom-rulesAdd a custom rule: id, name, pattern, applies_to, severity, action (block or log), enabled. Invalid regex or action → 400. Audited.
DELETE/api/waf/custom-rules/:idRemove a custom rule. Audited.
POST/api/waf/testTest a string against the built-in patterns and custom rules: body {"payload":"..."}. Returns each match with pattern, threat type, location, severity, and confidence.
Switch the running WAF to block modebash
curl -X PUT http://localhost:8080/sentinel/api/waf/rules \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode": "block", "rules": {"OpenRedirect": "off"}}'
curl -X POST http://localhost:8080/sentinel/api/waf/test \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"payload": "1 UNION SELECT password FROM users"}'

Rate Limits

MethodPathDescription
GET/api/rate-limitsConfiguration in effect: enabled, strategy, by_ip, by_user, global, and the live by_route table.
PUT/api/rate-limitsChange route limits on the running limiter: body {"by_route":{"/api/search":{"requests":10,"window":"1m"}}}. requests: 0 removes a route's limit. Every entry is validated first and the update is all-or-nothing (400 on a bad window, missing leading /, or unmatchable pattern); rate limiting disabled → 409 RATE_LIMIT_DISABLED. Stored like the WAF settings (v2.6.0+), with a warning in the response when it could not be. Audited.
GET/api/rate-limits/currentActive counters: key, count (usage now), limit, remaining, window_end.
POST/api/rate-limits/reset/:keyDelete one counter so the client can send again immediately. Audited.

Users, Audit Logs, and AuthShield

MethodPathDescription
GET/api/usersUsers seen by your UserExtractor: activity count, threat count, last seen. Empty without an extractor.
GET/api/users/:user_id/activityPaginated activity for one user; start_time / end_time filters.
GET/api/users/:user_id/threatsPaginated threats attributed to one user.
GET/api/audit-logsPaginated audit entries — GORM data changes, dashboard actions, logins. Filters: user_id, action, resource, start_time, end_time. Read-only: there is no endpoint to edit or delete an entry.
GET/api/audit-logs/verifyRecompute the audit hash chains and report ok, checked, unchained, chains, keyed, and problems (modified, missing, broken_link).
GET/api/auth-shield/statusPer-IP AuthShield state: failed attempts, lockout, CAPTCHA tier.
POST/api/auth/unblock-user/:usernameLift an AuthShield lockout for a username. Audited.

Alerts

MethodPathDescription
GET/api/alerts/configThe alert threshold in effect and which channels are configured (URLs masked).
PUT/api/alerts/configChange the running dispatcher's threshold: {"min_severity":"High"} (Low, Medium, High, Critical; any case). Unknown value → 400. Stored like the WAF settings (v2.6.0+). Audited.
POST/api/alerts/testReport how many providers are configured (does not deliver an alert).
GET/api/alerts/historyThe last 1,000 delivery attempts with channel, outcome, and error.

Dashboard Settings

The WAF mode and sensitivity, custom rules, route rate limits, and the alert threshold are stored when you change them, so they survive a restart and reach every replica within Storage.SyncInterval. Stored settings win over the values in your Config: these endpoints show which is in force and discard the stored ones. (v2.6.0+)

MethodPathDescription
GET/api/settings/liveWhat is stored (stored, null when your config is in force), what your config asked for (configured), whether the storage backend can keep settings (persistable), and the poll interval (sync_interval).
DELETE/api/settings/liveDiscard the stored settings and put the configured values back — here at once, on the other replicas at their next poll. 409 SETTINGS_NOT_PERSISTABLE when the storage backend cannot keep settings. Audited.
Go back to the settings in your configbash
curl -X DELETE http://localhost:8080/sentinel/api/settings/live -H "Authorization: Bearer $TOKEN"

AI

Without an AI provider configured, these return 200 with {"data": null, "message": "AI not configured"}. See what each call sends to the provider.

MethodPathDescription
POST/api/ai/analyze-threat/:idPlain-English analysis of one threat event.
GET/api/ai/analyze-actor/:ipAssessment of an actor's intent, sophistication, and risk.
GET/api/ai/daily-summarySummary of the last 24 hours from aggregate statistics.
POST/api/ai/queryAsk a question about your security data: {"query":"..."}.
GET/api/ai/waf-recommendationsSuggested custom rules from recent attack patterns.

Compliance Reports

MethodPathDescription
GET/api/reports/gdprGDPR evidence for window (Go duration, default 720h).
GET/api/reports/pci-dssPCI-DSS evidence for the last 90 days.
GET/api/reports/soc2SOC 2 evidence for window (default 720h).

Each report includes a provenance block and a truncated list. On in-memory storage in release mode they return 409 EPHEMERAL_STORAGE unless acknowledge_ephemeral=true is passed. Field-by-field contents are in Compliance Reports.

Score, Analytics, and Performance

MethodPathDescription
GET/api/scoreThe security score (0–100) with grade, sub-scores, and recommendations.
GET/api/analytics/summaryOverview figures for the dashboard home page.
GET/api/analytics/attack-trendsThreats per period with per-type counts. window (default 168h), interval = hour | day.
GET/api/analytics/geographicThreat counts by country. window (default 168h).
GET/api/analytics/top-targetsMost attacked route/method pairs. window, limit (default 10).
GET/api/csp-violations/statsAggregated CSP violation reports.
GET/api/performance/overviewAggregate latency percentiles, error rate, throughput, and pipeline emitted/dropped counters.
GET/api/performance/routesPer-route latency percentiles, error rates, and request counts.

CSP Report Receiver

MethodPathDescription
POST/csp-reportReceives browser CSP violation reports (legacy and Reporting API formats). No token — browsers do not send one. Limited to 100 reports per minute per client IP.

WebSocket

Browsers can't set headers on a WebSocket, so pass the JWT as a token query parameter: ws://localhost:8080/sentinel/ws/threats?token=<jwt>. Connections without a valid token are refused.

MethodPathDescription
WS/ws/threatsPipeline events as they happen, including every threat event.
WS/ws/metricsLive performance and system metrics.
WS/ws/alertsThe same event stream, for the alerts view.
WebSocket Connectionjavascript
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(
`${proto}//${window.location.host}/sentinel/ws/threats?token=${jwt}`
);
ws.onmessage = (event) => console.log(JSON.parse(event.data));

Next Steps


Built with by JB