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
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:
{"data": [ ... ],"meta": { "total": 142, "page": 1, "page_size": 20 }}
Single resources come back as { "data": { ... } }; actions return a message:
{ "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
| Method | Path | Description |
|---|---|---|
POST | /api/auth/login | Exchange {"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/logout | Returns success. Tokens are stateless and are not revoked — discard the token client-side. |
GET | /api/auth/verify | Returns 200 if the token is valid, 401 if not. |
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
| Method | Path | Description |
|---|---|---|
GET | /api/threats | Paginated 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/:id | One threat event with its evidence, request metadata, CVSS score, and location. |
POST | /api/threats/:id/resolve | Mark a threat resolved. Audited. |
POST | /api/threats/:id/false-positive | Mark a threat as a false positive (and resolved). Audited. |
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
| Method | Path | Description |
|---|---|---|
GET | /api/actors | Paginated threat actors (one per source IP). Filters: status, search, min_risk. |
GET | /api/actors/:ip | One actor profile: risk score, counts, attack types, targeted routes, location. |
GET | /api/actors/:ip/requests | Paginated threat events from one IP. |
POST | /api/actors/:ip/block | Block the actor's IP. Optional body {"reason","permanent"}; blocks last 24 hours unless permanent is true. Audited. |
GET | /api/ip/blocked | Every blocked IP and CIDR with reason and expiry. |
POST | /api/ip/block | Block an IP or CIDR. Body {"ip","reason","expiry","permanent"}; expiry is RFC 3339, default 24 hours. Audited. |
DELETE | /api/ip/block/:ip | Unblock. Write a CIDR with _ for / (10.0.0.0_8). Audited. |
GET | /api/ip/:ip/reputation | AbuseIPDB lookup (cached 24 hours). Blocks the IP when AutoBlock is on and the score clears MinAbuseScore. |
GET | /api/ip/feeds | Live reputation checking (enabled, used_today, max_per_day) and each blocklist feed's entries, last_refresh, and error. |
WAF
| Method | Path | Description |
|---|---|---|
GET | /api/waf/rules | The running WAF's mode and per-category rules (sensitivity levels), and whether the WAF is enabled. |
PUT | /api/waf/rules | Change 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-rules | All custom rules. |
POST | /api/waf/custom-rules | Add 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/:id | Remove a custom rule. Audited. |
POST | /api/waf/test | Test a string against the built-in patterns and custom rules: body {"payload":"..."}. Returns each match with pattern, threat type, location, severity, and confidence. |
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
| Method | Path | Description |
|---|---|---|
GET | /api/rate-limits | Configuration in effect: enabled, strategy, by_ip, by_user, global, and the live by_route table. |
PUT | /api/rate-limits | Change 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/current | Active counters: key, count (usage now), limit, remaining, window_end. |
POST | /api/rate-limits/reset/:key | Delete one counter so the client can send again immediately. Audited. |
Users, Audit Logs, and AuthShield
| Method | Path | Description |
|---|---|---|
GET | /api/users | Users seen by your UserExtractor: activity count, threat count, last seen. Empty without an extractor. |
GET | /api/users/:user_id/activity | Paginated activity for one user; start_time / end_time filters. |
GET | /api/users/:user_id/threats | Paginated threats attributed to one user. |
GET | /api/audit-logs | Paginated 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/verify | Recompute the audit hash chains and report ok, checked, unchained, chains, keyed, and problems (modified, missing, broken_link). |
GET | /api/auth-shield/status | Per-IP AuthShield state: failed attempts, lockout, CAPTCHA tier. |
POST | /api/auth/unblock-user/:username | Lift an AuthShield lockout for a username. Audited. |
Alerts
| Method | Path | Description |
|---|---|---|
GET | /api/alerts/config | The alert threshold in effect and which channels are configured (URLs masked). |
PUT | /api/alerts/config | Change 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/test | Report how many providers are configured (does not deliver an alert). |
GET | /api/alerts/history | The 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+)
| Method | Path | Description |
|---|---|---|
GET | /api/settings/live | What 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/live | Discard 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. |
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.
| Method | Path | Description |
|---|---|---|
POST | /api/ai/analyze-threat/:id | Plain-English analysis of one threat event. |
GET | /api/ai/analyze-actor/:ip | Assessment of an actor's intent, sophistication, and risk. |
GET | /api/ai/daily-summary | Summary of the last 24 hours from aggregate statistics. |
POST | /api/ai/query | Ask a question about your security data: {"query":"..."}. |
GET | /api/ai/waf-recommendations | Suggested custom rules from recent attack patterns. |
Compliance Reports
| Method | Path | Description |
|---|---|---|
GET | /api/reports/gdpr | GDPR evidence for window (Go duration, default 720h). |
GET | /api/reports/pci-dss | PCI-DSS evidence for the last 90 days. |
GET | /api/reports/soc2 | SOC 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
| Method | Path | Description |
|---|---|---|
GET | /api/score | The security score (0–100) with grade, sub-scores, and recommendations. |
GET | /api/analytics/summary | Overview figures for the dashboard home page. |
GET | /api/analytics/attack-trends | Threats per period with per-type counts. window (default 168h), interval = hour | day. |
GET | /api/analytics/geographic | Threat counts by country. window (default 168h). |
GET | /api/analytics/top-targets | Most attacked route/method pairs. window, limit (default 10). |
GET | /api/csp-violations/stats | Aggregated CSP violation reports. |
GET | /api/performance/overview | Aggregate latency percentiles, error rate, throughput, and pipeline emitted/dropped counters. |
GET | /api/performance/routes | Per-route latency percentiles, error rates, and request counts. |
CSP Report Receiver
| Method | Path | Description |
|---|---|---|
POST | /csp-report | Receives 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.
| Method | Path | Description |
|---|---|---|
WS | /ws/threats | Pipeline events as they happen, including every threat event. |
WS | /ws/metrics | Live performance and system metrics. |
WS | /ws/alerts | The same event stream, for the alerts view. |
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
- The Dashboard -- The UI built on these endpoints
- Configuration -- Every config field
- WAF -- Modes, sensitivity levels, and custom rules
- Compliance Reports -- Report contents and provenance