REST API
obserae exposes an HTTP API you can drive from scripts, supervision systems and
CI. It is the same server that renders the Web GUI: the browser
pages return HTML, while every path under /api/… returns JSON and every
path under /ws/… is a WebSocket. This page is the reference for the JSON
API — how to authenticate, what each endpoint does, and copy-paste curl
examples.
- Base URL — the web server binds
127.0.0.1:8080by default (web.bindin configuration). All examples below usehttp://127.0.0.1:8080; substitute your host and port. - Content type — send
Content-Type: application/jsonon requests with a body; responses are alwaysapplication/json. - Read vs. write — read endpoints (
GET) run on a dedicated reader pool and never block ingestion; mutations (POST/PATCH/DELETE) serialise on the writer. - Machine-readable spec — a full OpenAPI 3.1 description of every
endpoint ships alongside this page as
openapi.yaml. Import it into Postman/Insomnia, generate a client, or render it with Swagger UI / Redoc.
Authentication
Every /api/… endpoint requires an API token, sent as a Bearer credential:
Authorization: Bearer obs_…
The only exceptions are the two public probes documented below
(GET /healthz and GET /api/protocols).
Minting a token
A token is minted for a user and inherits that user’s permissions. The secret is shown once at creation — copy it then; obserae only stores a hash.
In the GUI — Identity & Access → Users → + New token. See Web GUI › API tokens.
From the CLI — over the admin socket (the secret is printed once):
obserae-cli user token-add --name ci --user alice obserae-cli user token-ls obserae-cli user token-rm <TOKEN_ID> # revoke
Give the token’s owner the least privilege it needs. A supervision system
that only polls /api/status, for example, should own a token whose user is in
the built-in monitoring group (just monitoring:read) and nothing else.
No CSRF, no MFA for tokens
Bearer-token clients are exempt from CSRF and from the MFA enrollment gate — a script never needs a CSRF header or a second factor. (CSRF only applies to the browser, which authenticates with a session cookie.) See authentication for how humans sign in.
Permissions
Authorization is enforced per route. If the token’s user lacks the required
permission, the call fails 403 (see Errors). The catalogue:
| Permission | Grants |
|---|---|
cartography:read / cartography:write | Read / edit the network map (hosts, networks, groups, services) |
rules:read / rules:write | Read / edit flow-matrix rules |
alerting:read / alerting:write | Read / edit detection & anomaly rules and rule sets |
sessions:read | Read sessions, run read-only NFQL helpers |
nfql:execute | Execute NFQL queries and manage saved queries |
alerts:read / alerts:ack | Read / acknowledge & delete detection alerts |
outputs:manage | Manage alert outputs (webhooks, syslog, …) |
sources:manage | Manage enrichment sources and flow exporters |
devices:manage | Manage OPNsense devices |
config:export / config:import | Export / import the YAML config bundle |
users:manage | Manage users, groups and tokens |
auth:manage | Manage LDAP/OIDC and login-security settings |
auditlog:read | Read and verify the audit log |
monitoring:read | Read the GET /api/status health snapshot |
system:manage | Storage / retention / backup / master-key operations |
Built-in groups bundle these: admin (everything), analyst
(read + query + rule authoring), auditor (read-only, incl. auditlog:read),
and monitoring (only monitoring:read). Create a custom group to grant an
exact subset.
Errors
Machine paths (/api/…, /ws/…) always answer failures with a JSON body:
| Status | Body | Meaning |
|---|---|---|
401 Unauthorized | {"error":"unauthenticated"} | Missing, unknown, revoked or expired token |
403 Forbidden | {"error":"forbidden"} | Token’s user lacks the required permission |
423 Locked | {"error":"…","holder":"<user>"} | A cartography mutation while another admin holds the edit lock |
400 Bad Request | {"error":"…"} | Malformed request (missing field, bad JSON) |
NFQL compile errors are not HTTP errors.
POST /api/queryreturns200 OKwith anerrorfield populated (see below) so a positioned compile error and a result share one response shape.
Health & status
| Method | Path | Permission | Purpose |
|---|---|---|---|
GET | /healthz | public | Plain-text liveness probe — returns ok |
GET | /api/status | monitoring:read | Full machine-facing daemon snapshot (JSON) |
GET | /api/protocols | public | IANA protocol number → name table (JSON) |
GET /api/status returns byte-for-byte the same payload as obserae-cli status
— one shared snapshot for both surfaces. It is the authenticated counterpart to
the public /healthz probe.
curl -H "Authorization: Bearer obs_…" http://127.0.0.1:8080/api/status
{
"version": "0.27.0",
"commit": "7a8e5e4",
"started_at": "2026-07-07T08:00:00Z",
"uptime_seconds": 3600,
"timestamp": "2026-07-07T09:00:00Z",
"data_version": 10,
"data_version_target": 10,
"flows": { "total": 1284003, "per_sec": 412.5 },
"cartography": { "networks": 12, "hosts": 340, "services": 28, "groups": 9 },
"sessions": { "active": 1204, "half_open": 33, "closed": 98120,
"open_live": 1237, "open_max": 200000, "fill_pct": 0.6,
"pressure": "ok", "evicted": 0,
"dropped_closes": 0, "dropped_emits": 0 },
"rules": { "total": 42, "expansions": 1180, "alert_rules_active": 7 },
"coverage": { "rate_1h": 0.98, "violations_closed_1h": 3 },
"alerts": { "total": 15, "by_status": { "open": 4, "acknowledged": 11 },
"by_severity": { "high": 2, "medium": 9, "low": 4 },
"last_1h": 1, "last_24h": 6 },
"ingestion": { "files_written": 8123, "records_ingested": 1284003,
"last_flush_at": "2026-07-07T08:59:57Z",
"avg_flush_ms": 41.2, "max_flush_ms": 190 },
"storage": { "db_path": "data/obserae.duckdb", "db_size_bytes": 12288,
"disk_free_bytes": 51230000000, "disk_total_bytes": 100000000000 },
"runtime": { "heap_inuse_bytes": 84934656, "goroutines": 61,
"udp_drops": 0, "matcher_cursor_lag_seconds": 1 },
"audit": { "integrity": "verified", "breaks": 0 },
"live_metrics_available": true
}
When the web GUI is disabled (
web.enabled: false) the producer-derived blocks (per_sec,coverage,runtime, ingestion timing, audit verdict) stay at zero andlive_metrics_availableisfalse; the always-available counts are still filled.
NFQL query execution
Run NFQL queries — the same engine the Investigation page uses. Compiled once, executed on the read-only pool with a row cap and timeout.
| Method | Path | Permission | Purpose |
|---|---|---|---|
POST | /api/query | nfql:execute | Execute a query, return columns + rows |
GET | /api/query/columns | sessions:read | Compile only — return output columns & types (?query=…) |
GET | /api/query/schema | sessions:read | Tables, columns, keywords and constants with descriptions |
GET | /api/query/cookbook | sessions:read | Built-in example queries |
GET | /api/query/carto-refs | sessions:read | Cartography names usable in predicates |
GET | /api/query/resolve-ip | sessions:read | Resolve an IP to its host/interface/network (?ip=…) |
Executing a query
POST /api/query with a JSON body:
| Field | Type | Notes |
|---|---|---|
source | string | The NFQL pipeline (required) |
args | array | Positional ? arguments; integers are bound as integers |
as_of | string | Optional RFC3339 instant; pins the “now” of every relative window (the investigate at fire time path). Empty = live now |
curl -X POST http://127.0.0.1:8080/api/query \
-H "Authorization: Bearer obs_…" \
-H "Content-Type: application/json" \
-d '{
"source": "FROM sessions | LAST 3600 | STATS COUNT(*) AS n BY server_port | SORT n DESC | HEAD 5",
"args": []
}'
Response (a result or a compile error, always 200 OK):
{
"columns": [
{ "name": "server_port", "type": "INTEGER" },
{ "name": "n", "type": "BIGINT" }
],
"rows": [
[443, 8123],
[53, 4102],
[22, 512]
],
"compile_ms": 3,
"exec_ms": 41,
"row_count": 3,
"truncated": false
}
On a bad query the same shape carries the message instead:
{ "columns": null, "rows": null, "error": "sem: 1:18: unknown column \"prtocol\"" }
truncated: true means the row cap was hit — narrow the query with HEAD or a
tighter window. Saved queries are managed under /api/queries (list/search with
sessions:read; create/update/delete with nfql:execute).
Endpoint reference by domain
All paths below are under the base URL and require the listed permission. GET
returns JSON; mutations take a JSON body and return the updated resource or a
status envelope. {…} marks a path parameter.
Cartography — the network map
cartography:read for GET, cartography:write for mutations. Mutations
also require the edit lock; a non-holder gets 423 Locked.
| Method | Path | Purpose |
|---|---|---|
GET | /api/carto/graph | Full graph (nodes, edges, groups) |
GET | /api/carto/layout | /color | /icon | /os-icon | Layout & styling data |
GET | /api/carto/search | Search hosts/networks/groups |
POST | /api/carto/networks | /groups | /hosts | /interfaces | /services | Create |
PATCH DELETE | /api/carto/{kind}/{name} | Update / delete |
POST | /api/carto/hosts/{name}/clone | Clone a host |
POST | /api/carto/auto-layout | Recompute layout |
GET POST | /api/carto/lock, /lock/heartbeat, /lock/release | Acquire/renew/release the single-editor lock |
Flow-matrix rules
rules:read / rules:write. See rules.
| Method | Path | Purpose |
|---|---|---|
GET | /api/rules, /api/rules/{name} | List / read |
POST | /api/rules | Create |
PATCH DELETE | /api/rules/{name} | Update / delete |
POST | /api/rules/{name}/enable | /disable | Toggle |
POST | /api/rules/bulk | Bulk apply |
POST | /api/rules/preview, /api/rules/find-covering | Dry-run helpers |
Detection (alerting) rules & rule sets
alerting:read / alerting:write. See alerting and
rulesets.
| Method | Path | Purpose |
|---|---|---|
GET POST | /api/alerting-rules | List / create alert rules |
GET PATCH DELETE | /api/alerting-rules/{id} | Read / update / delete |
POST | /api/alerting-rules/{id}/toggle | /duplicate | Enable-disable / clone |
GET | /api/alerting-rules/{id}/runs | Recent evaluations |
GET | /api/rulesets, /api/rulesets/shipped, /api/rulesets/{id} | Browse rule packs |
POST | /api/rulesets/validate | /import | Validate / import a pack |
POST DELETE | /api/rulesets/{id}/toggle, /api/rulesets/{id} | Toggle / delete |
Anomaly detection
alerting:read / alerting:write. See anomaly detection.
| Method | Path | Purpose |
|---|---|---|
GET | /api/anomaly/overview, /api/anomaly/rules | Overview & rule list |
GET | /api/anomaly/rules/{id}/baselines, .../baselines/{keyhash} | Learned baselines |
GET | /api/anomaly/rules/{id}/baselines/{keyhash}/series | /heatmap | Time series |
GET | /api/anomaly/rules/{id}/fires | Recent fires |
POST | /api/anomaly/rules/{id}/reset-baseline | Reset the baseline |
Detection alerts
alerts:read for GET, alerts:ack for mutations.
| Method | Path | Purpose |
|---|---|---|
GET | /api/alerts | List alerts (filters via query params) |
POST | /api/alerts/{id}/status | Acknowledge / change status |
POST | /api/alerts/delete | Bulk delete |
DELETE | /api/alerts/{id} | Delete one |
# List, then acknowledge one alert
curl -H "Authorization: Bearer obs_…" http://127.0.0.1:8080/api/alerts
curl -X POST http://127.0.0.1:8080/api/alerts/42/status \
-H "Authorization: Bearer obs_…" -H "Content-Type: application/json" \
-d '{"status":"acknowledged"}'
Sessions
| Method | Path | Permission | Purpose |
|---|---|---|---|
GET | /api/sessions/riverview | sessions:read | River-view session aggregation (JSON) |
Enrichment & exporters
sources:manage. See enrichment and exporters.
| Method | Path | Purpose |
|---|---|---|
GET PATCH | /api/enrichment | Read / update enrichment settings |
GET | /api/enrichment/sources | List enrichment sources |
PATCH | /api/enrichment/sources/{name} | Update a source |
POST | /api/enrichment/sources/{name}/refresh | Refresh now |
GET | /api/exporters | List flow exporters |
PATCH DELETE | /api/exporters/{ip} | Update / forget an exporter |
POST | /api/exporters/rescan | Rescan for exporters |
Devices (OPNsense)
devices:manage. See sources.
| Method | Path | Purpose |
|---|---|---|
GET POST | /api/devices | List / add a device |
GET PATCH DELETE | /api/devices/{id} | Read / update / remove |
POST | /api/devices/{id}/refresh | Poll the device now |
Outputs
outputs:manage. See outputs.
| Method | Path | Purpose |
|---|---|---|
GET POST | /api/outputs | List / create an output |
GET PATCH DELETE | /api/outputs/{id} | Read / update / delete |
POST | /api/outputs/{id}/toggle | /test | Enable-disable / send a test event |
GET | /api/outputs/{id}/deliveries | Recent delivery attempts |
Lifecycle — storage, retention, backup
system:manage. See lifecycle.
| Method | Path | Purpose |
|---|---|---|
GET | /api/lifecycle/storage | Storage usage |
GET PATCH | /api/lifecycle/retention | Read / set retention policy |
POST | /api/lifecycle/retention/run | Run retention now |
GET PATCH | /api/lifecycle/backup | Read / set backup schedule |
POST | /api/lifecycle/backup/run, /plan, /restore | Run / plan / restore |
GET | /api/lifecycle/backup/timeline, /next-action | Schedule state |
Configuration I/O
See CLI › config for the equivalent bundle operations.
| Method | Path | Permission | Purpose |
|---|---|---|---|
GET | /api/config-io/export | config:export | Export the full YAML config bundle |
POST | /api/config-io/validate | /import | config:import | Validate / import a bundle |
GET POST | /api/config-io/masterkey/export | /import | system:manage | Master-key material |
Audit log
auditlog:read. See verify for the tamper-evidence scheme.
| Method | Path | Purpose |
|---|---|---|
GET | /api/auditlog | Query the journal (filters via query params) |
GET | /api/auditlog/histogram, /facets | Aggregates for the UI |
GET | /api/auditlog/verify | Verify the hash chain & seals |
GET | /api/auditlog/export | Export matching entries |
Identity & access administration
users:manage unless noted; LDAP/OIDC and login security need auth:manage.
The account self-service endpoints require only an authenticated token (any
user managing their own MFA/password).
| Method | Path | Permission | Purpose |
|---|---|---|---|
POST | /api/users | users:manage | Create a user |
PATCH DELETE | /api/users/{username} | users:manage | Update / delete |
POST | /api/users/{username}/password | /mfa/reset | users:manage | Reset password / MFA |
POST | /api/groups, PATCH/DELETE /api/groups/{name} | users:manage | Manage groups |
POST | /api/tokens, /api/tokens/{id}/revoke, DELETE /api/tokens/{id} | users:manage | Manage API tokens |
GET PUT | /api/ldap/config, POST /api/ldap/test-connection | auth:manage | LDAP config |
GET PUT | /api/oidc/config, POST /api/oidc/test-connection | auth:manage | OIDC config |
GET PUT | /api/auth/login-security, PUT /api/auth/mfa-policy | users:manage | Login-security & MFA policy |
POST | /api/account/mfa/begin | /activate | /disable, /api/account/password | any authenticated | Self-service MFA & password |
WebSockets
Real-time streams for the GUI, also usable by a client that keeps a socket open.
Connect with the same Bearer token; an unauthenticated handshake gets a 401.
| Path | Permission | Pushes |
|---|---|---|
/ws/health | any authenticated | Health / cockpit snapshots |
/ws/alerts | alerts:read | alert.fired notifications |
/ws/carto | cartography:read | carto.changed broadcasts when the map is edited |
See also
- Web GUI — where to mint tokens and manage permissions.
- Authentication — how humans sign in (local, LDAP, OIDC).
- NFQL — the query language used by
POST /api/query. - CLI — the admin socket, which mirrors many of these operations.
Browse the interactive API reference. Every endpoint, schema and example rendered from the OpenAPI spec: /docs/api-reference/.