|
| 1 | +# CrowdSec Web UI API |
| 2 | + |
| 3 | +All API routes return JSON unless a route explicitly redirects for OIDC login/callback. Routes are served below `/api`, or below `${BASE_PATH}/api` when `BASE_PATH` is configured. `GET /api/health` is always registered at the root path; when `BASE_PATH` is set, `${BASE_PATH}/api/health` is registered too. |
| 4 | + |
| 5 | +The browser UI authenticates with the HTTP-only `crowdsec_web_ui_session` cookie. There is no bearer-token API in this codebase. Authentication is enabled for new installs by default; migrated databases from older unauthenticated versions stay disabled until `AUTH_ENABLED=true` is set. When auth is enabled, protected API routes return `401` without a valid session. |
| 6 | + |
| 7 | +Protected application routes also ensure the backend can authenticate to CrowdSec LAPI. If LAPI login fails, they return `502`. |
| 8 | + |
| 9 | +`PERMISSION_READ_ONLY=true` or a read-only user role blocks enforcement and management writes. Blocked requests return: |
| 10 | + |
| 11 | +```json |
| 12 | +{ "error": "Read-only mode is enabled", "code": "READ_ONLY" } |
| 13 | +``` |
| 14 | + |
| 15 | +Read-only mode still allows user preferences and notification read-state writes. |
| 16 | + |
| 17 | +## Common Behavior |
| 18 | + |
| 19 | +### Pagination |
| 20 | + |
| 21 | +List endpoints that support pagination use `page` and `page_size`. Pagination is enabled only when the `page` query parameter is present. |
| 22 | + |
| 23 | +- `page` defaults to `1` and is clamped to at least `1`. |
| 24 | +- `page_size` defaults to `50` and is clamped from `10` to `200`. |
| 25 | +- Paginated responses use: |
| 26 | + |
| 27 | +```json |
| 28 | +{ |
| 29 | + "data": [], |
| 30 | + "pagination": { |
| 31 | + "page": 1, |
| 32 | + "page_size": 50, |
| 33 | + "total": 0, |
| 34 | + "total_pages": 0, |
| 35 | + "unfiltered_total": 0 |
| 36 | + }, |
| 37 | + "selectable_ids": [] |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +### Search and Filters |
| 42 | + |
| 43 | +`GET /api/alerts` and `GET /api/decisions` support a structured `q` search when paginated. Search supports free text, quoted phrases, `AND`, `OR`, `NOT`, `-`, grouping, field matching with `:`, exact matching with `=`, inequality with `<>`, and date comparisons with `<`, `<=`, `>`, `>=`. |
| 44 | + |
| 45 | +Alert search fields: `id`, `scenario`, `message`, `ip`/`source`, `country`, `as`, `target`, `date`/`created`/`created_at`/`time`, `sim`/`simulation`, `machine`, `origin`. |
| 46 | + |
| 47 | +Decision search fields: `id`, `alert`/`alert_id`, `scenario`/`reason`, `ip`/`value`, `country`, `as`, `target`, `date`/`created`/`created_at`/`time`, `action`, `type`, `status`, `duplicate`, `sim`/`simulation`, `machine`, `origin`. |
| 48 | + |
| 49 | +Date range filters use `dateStart` and `dateEnd`. Use `YYYY-MM-DD` for day buckets or values containing `T` for hour-level comparisons. `tz_offset` is an offset in minutes used for local bucket comparisons when the server has no fixed `TZ` configured. |
| 50 | + |
| 51 | +## Health |
| 52 | + |
| 53 | +| Method | Endpoint | Description | |
| 54 | +| --- | --- | --- | |
| 55 | +| GET | `/api/health` | Public health check returning `{ "status": "ok" }`. | |
| 56 | + |
| 57 | +## Auth |
| 58 | + |
| 59 | +These routes are mounted below `/api/auth`. Auth setup/login routes are available without a session so onboarding and login can work. |
| 60 | + |
| 61 | +| Method | Endpoint | Description | |
| 62 | +| --- | --- | --- | |
| 63 | +| GET | `/api/auth/status` | Auth state, setup status, current user/session state, OIDC availability, passkey availability, and password-login state. | |
| 64 | +| POST | `/api/auth/setup` | Create the first admin user and start a session. Requires `username` and `password`; only works before any auth user exists. | |
| 65 | +| POST | `/api/auth/login` | Password login with `username` and `password`. | |
| 66 | +| POST | `/api/auth/logout` | Clear the session cookie. | |
| 67 | +| GET | `/api/auth/me` | Current authenticated session user. | |
| 68 | +| GET | `/api/auth/settings` | Auth settings visible to the current user, including OIDC settings metadata and password/passkey state. | |
| 69 | +| PUT | `/api/auth/settings` | Admin-only auth settings update. Can disable password login and configure OIDC issuer, client ID, client secret, groups claim, admin groups, read-only groups, and unmatched-user policy. | |
| 70 | +| POST | `/api/auth/change-password` | Change the current user's password. The user must be logged in with password auth. | |
| 71 | +| GET | `/api/auth/passkeys` | List passkeys for the current user. | |
| 72 | +| PATCH | `/api/auth/passkeys/:id` | Rename a passkey with `{ "name": "..." }`. | |
| 73 | +| DELETE | `/api/auth/passkeys/:id` | Delete one of the current user's passkeys. | |
| 74 | +| POST | `/api/auth/webauthn/register/options` | Start passkey registration for the current user. | |
| 75 | +| POST | `/api/auth/webauthn/register/verify` | Complete passkey registration. Optional `name` is stored as the passkey label. | |
| 76 | +| POST | `/api/auth/webauthn/login/options` | Start passkey login. Optional `username` narrows allowed credentials. | |
| 77 | +| POST | `/api/auth/webauthn/login/verify` | Complete passkey login and start a session. | |
| 78 | +| GET | `/api/auth/oidc/login` | Redirect to the configured OIDC provider. | |
| 79 | +| GET | `/api/auth/oidc/callback` | OIDC callback handler; creates or updates the OIDC user session and redirects to the UI. | |
| 80 | + |
| 81 | +## Configuration |
| 82 | + |
| 83 | +| Method | Endpoint | Description | |
| 84 | +| --- | --- | --- | |
| 85 | +| GET | `/api/config` | Runtime UI config, LAPI status, sync status, simulation setting, table column preferences, time settings, metrics availability, metrics sidebar preference, and permissions. | |
| 86 | +| PUT | `/api/config/metrics-sidebar` | Save the metrics sidebar preference. Body: `{ "visible": true }`. | |
| 87 | +| PUT | `/api/config/table-columns` | Save visible columns for one table and viewport. Body: `{ "table": "alerts" \| "decisions", "viewport": "desktop" \| "mobile", "visible_columns": [...] }`. | |
| 88 | +| PUT | `/api/config/refresh-interval` | Update the refresh interval. Body: `{ "interval": "manual" \| "0" \| "5s" \| "30s" \| "1m" \| "5m" }`. Blocked in read-only mode. | |
| 89 | +| PUT | `/api/config/language` | Save language preference. Body: `{ "language": "browser" }` or a supported locale code. | |
| 90 | + |
| 91 | +Alert columns: `id`, `time`, `scenario`, `country`, `as`, `source`, `machine`, `origin`, `decisions`. |
| 92 | + |
| 93 | +Decision columns: `id`, `time`, `scenario`, `country`, `as`, `source`, `action`, `expiration`, `machine`, `origin`, `alert`. |
| 94 | + |
| 95 | +## Alerts |
| 96 | + |
| 97 | +| Method | Endpoint | Description | |
| 98 | +| --- | --- | --- | |
| 99 | +| GET | `/api/alerts` | List synced alerts. Without `page`, returns an array. With `page`, returns a paginated response. | |
| 100 | +| GET | `/api/alerts/:id` | Fetch alert details from CrowdSec LAPI, hydrate with decisions, and apply simulation visibility. `:id` must be numeric. | |
| 101 | +| POST | `/api/alerts/bulk-delete` | Delete multiple alerts by numeric ID. Body: `{ "ids": [1, "2"] }`. Also deletes cached linked decisions. Blocked in read-only mode. | |
| 102 | +| DELETE | `/api/alerts/:id` | Delete one alert from CrowdSec LAPI and local cache. `:id` must be numeric. Blocked in read-only mode. | |
| 103 | + |
| 104 | +Supported paginated alert filters: `q`, `ip`, `country`, `scenario`, `as`, `date`, `dateStart`, `dateEnd`, `target`, `simulation`, `tz_offset`. |
| 105 | + |
| 106 | +`simulation` accepts `all`, `live`, or `simulated`; unknown values behave like `all`. |
| 107 | + |
| 108 | +## Decisions |
| 109 | + |
| 110 | +| Method | Endpoint | Description | |
| 111 | +| --- | --- | --- | |
| 112 | +| GET | `/api/decisions` | List decisions. Without `page`, returns an array. With `page`, returns a paginated response. Active decisions are returned by default. | |
| 113 | +| POST | `/api/decisions` | Add a manual CrowdSec decision through LAPI. Body: `{ "ip": "1.2.3.4", "duration": "4h", "reason": "manual", "type": "ban" }`. `type` defaults to `ban` and accepts `ban` or `captcha`. Blocked in read-only mode. | |
| 114 | +| POST | `/api/decisions/bulk-delete` | Delete multiple decisions by numeric ID. Body: `{ "ids": [10, "11"] }`. Blocked in read-only mode. | |
| 115 | +| DELETE | `/api/decisions/:id` | Delete one decision from CrowdSec LAPI and local cache. `:id` must be numeric. Blocked in read-only mode. | |
| 116 | + |
| 117 | +Supported decision query parameters: `include_expired`, `page`, `page_size`, `q`, `alert_id`, `country`, `scenario`, `as`, `ip`, `target`, `dateStart`, `dateEnd`, `simulation`, `hide_duplicates`, `tz_offset`. |
| 118 | + |
| 119 | +- `include_expired=true` includes expired decisions within the configured lookback window. |
| 120 | +- Duplicate decisions are hidden by default in paginated results. Set `hide_duplicates=false` or filter by `alert_id` to show them. |
| 121 | +- `simulation` accepts `all`, `live`, or `simulated`; unknown values behave like `all`. |
| 122 | + |
| 123 | +## Cleanup and Cache |
| 124 | + |
| 125 | +| Method | Endpoint | Description | |
| 126 | +| --- | --- | --- | |
| 127 | +| POST | `/api/cleanup/by-ip` | Delete cached/LAPI alerts and decisions for one IP address or range. Body: `{ "ip": "1.2.3.4" }`. Blocked in read-only mode. | |
| 128 | +| POST | `/api/cache/clear` | Clear synced alert/decision data and run a bootstrap sync. Blocked in read-only mode. | |
| 129 | + |
| 130 | +Bulk delete and cleanup responses use: |
| 131 | + |
| 132 | +```json |
| 133 | +{ |
| 134 | + "requested_alerts": 0, |
| 135 | + "requested_decisions": 0, |
| 136 | + "deleted_alerts": 0, |
| 137 | + "deleted_decisions": 0, |
| 138 | + "failed": [], |
| 139 | + "ip": "1.2.3.4" |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +`ip` is only included for cleanup-by-IP responses. |
| 144 | + |
| 145 | +## Stats and Dashboard |
| 146 | + |
| 147 | +| Method | Endpoint | Description | |
| 148 | +| --- | --- | --- | |
| 149 | +| GET | `/api/stats/alerts` | Alert records shaped for chart/stat consumers within the configured lookback window. | |
| 150 | +| GET | `/api/stats/decisions` | Decision records shaped for chart/stat consumers within the configured lookback window. | |
| 151 | +| GET | `/api/dashboard/stats` | Aggregated dashboard totals, filtered totals, top targets/countries/scenarios/AS, world-map country data, and history series. | |
| 152 | + |
| 153 | +Supported dashboard filters: `country`, `scenario`, `as`, `ip`, `target`, `dateStart`, `dateEnd`, `simulation`, `granularity`, `tz_offset`. |
| 154 | + |
| 155 | +- `simulation` accepts `all`, `live`, or `simulated`. |
| 156 | +- `granularity=hour` returns hourly buckets; any other value uses daily buckets. |
| 157 | + |
| 158 | +## CrowdSec Metrics |
| 159 | + |
| 160 | +| Method | Endpoint | Description | |
| 161 | +| --- | --- | --- | |
| 162 | +| GET | `/api/metrics/crowdsec` | Fetch and normalize CrowdSec Prometheus metrics. Returns `404` when `CROWDSEC_PROMETHEUS_URL` is not configured and `502` when the metrics fetch fails. | |
| 163 | + |
| 164 | +The response includes `fetched_at`, `totals`, `bouncers`, `machines`, `parserSources`, `parserNodes`, `whitelists`, and `parserTimings`. It can also include runtime-only observability sections: `lapiRoutes` and `appsecEngines`. |
| 165 | + |
| 166 | +Parser, LAPI latency, AppSec, bouncer, and machine values are derived from the current CrowdSec Prometheus scrape. The endpoint does not query Prometheus history or calculate Grafana-style `rate()`/`increase()` windows, so metrics that require time-window tracking are intentionally omitted. |
| 167 | + |
| 168 | +## Notifications |
| 169 | + |
| 170 | +Notification inbox routes operate on generated notification items. |
| 171 | + |
| 172 | +| Method | Endpoint | Description | |
| 173 | +| --- | --- | --- | |
| 174 | +| GET | `/api/notifications` | List notification items. Supports `page` and `page_size`; defaults to page `1`, size `50` when omitted. | |
| 175 | +| POST | `/api/notifications/:id/read` | Mark one notification as read. Allowed in read-only mode. | |
| 176 | +| POST | `/api/notifications/bulk-read` | Mark multiple notifications as read. Body: `{ "ids": ["id-1", "id-2"] }`. Allowed in read-only mode. | |
| 177 | +| POST | `/api/notifications/bulk-delete` | Delete multiple notifications. Body: `{ "ids": ["id-1", "id-2"] }`. Blocked in read-only mode. | |
| 178 | +| POST | `/api/notifications/delete-read` | Delete all read notifications. Blocked in read-only mode. | |
| 179 | +| DELETE | `/api/notifications/:id` | Delete one notification. Blocked in read-only mode. | |
| 180 | +| GET | `/api/notifications/settings` | List notification channels and rules. | |
| 181 | + |
| 182 | +Notification configuration routes manage destinations and rule definitions. |
| 183 | + |
| 184 | +| Method | Endpoint | Description | |
| 185 | +| --- | --- | --- | |
| 186 | +| POST | `/api/notification-channels` | Create a notification channel. Blocked in read-only mode. | |
| 187 | +| PUT | `/api/notification-channels/:id` | Update a notification channel. Blocked in read-only mode. | |
| 188 | +| DELETE | `/api/notification-channels/:id` | Delete a notification channel. Blocked in read-only mode. | |
| 189 | +| POST | `/api/notification-channels/:id/test` | Send a test notification through a saved channel. Blocked in read-only mode. | |
| 190 | +| POST | `/api/notification-rules` | Create a notification rule. Blocked in read-only mode. | |
| 191 | +| PUT | `/api/notification-rules/:id` | Update a notification rule. Blocked in read-only mode. | |
| 192 | +| DELETE | `/api/notification-rules/:id` | Delete a notification rule. Blocked in read-only mode. | |
| 193 | + |
| 194 | +Channel create/update body: |
| 195 | + |
| 196 | +```json |
| 197 | +{ |
| 198 | + "name": "Security alerts", |
| 199 | + "type": "ntfy", |
| 200 | + "enabled": true, |
| 201 | + "config": {} |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +Supported channel types: `ntfy`, `gotify`, `email`, `mqtt`, `webhook`. |
| 206 | + |
| 207 | +Rule create/update body: |
| 208 | + |
| 209 | +```json |
| 210 | +{ |
| 211 | + "name": "IP bans", |
| 212 | + "type": "ip-ban", |
| 213 | + "enabled": true, |
| 214 | + "severity": "warning", |
| 215 | + "channel_ids": ["channel-id"], |
| 216 | + "config": {} |
| 217 | +} |
| 218 | +``` |
| 219 | + |
| 220 | +Supported rule types: `alert-spike`, `alert-threshold`, `new-alert-decision`, `new-cve`, `ip-ban`, `application-update`, `lapi-availability`. |
| 221 | + |
| 222 | +Supported severities: `info`, `warning`, `critical`. |
| 223 | + |
| 224 | +## Update Check |
| 225 | + |
| 226 | +| Method | Endpoint | Description | |
| 227 | +| --- | --- | --- | |
| 228 | +| GET | `/api/update-check` | Check whether a newer application release is available. Response is not cached. | |
| 229 | + |
| 230 | +Optional query parameters override the runtime version metadata used for the check: `version`, `branch`, `commit_hash`. |
| 231 | + |
| 232 | +## Example Requests |
| 233 | + |
| 234 | +List the first page of active decisions: |
| 235 | + |
| 236 | +```bash |
| 237 | +curl -b cookie.txt 'http://localhost:3000/api/decisions?page=1&page_size=50' |
| 238 | +``` |
| 239 | + |
| 240 | +Search alerts with structured syntax: |
| 241 | + |
| 242 | +```bash |
| 243 | +curl -b cookie.txt 'http://localhost:3000/api/alerts?page=1&page_size=50&q=origin:(manual%20OR%20CAPI)%20AND%20-country:us' |
| 244 | +``` |
| 245 | + |
| 246 | +Add a manual ban: |
| 247 | + |
| 248 | +```bash |
| 249 | +curl -b cookie.txt \ |
| 250 | + -H 'Content-Type: application/json' \ |
| 251 | + -d '{"ip":"1.2.3.4","duration":"4h","type":"ban","reason":"manual"}' \ |
| 252 | + http://localhost:3000/api/decisions |
| 253 | +``` |
0 commit comments