HTTP API
The engine daemon binds a single Axum HTTP server on --api-addr (default 0.0.0.0:9090) that handles probes, metrics, REST control endpoints, an HTTP event ingest endpoint, and (with the daemon-otlp feature) OTLP log ingestion.
All bodies are JSON unless otherwise noted. All responses include a Content-Type header. Error responses are JSON objects with an error key.
Endpoint summary
| Path | Method | Permission | Description |
|---|---|---|---|
/healthz |
GET | always open | Liveness probe. Always 200 once the listener is up. |
/readyz |
GET | always open | Readiness probe. 200 when rules and pipelines are loaded; 503 during startup or after a failed reload. |
/metrics |
GET | metrics:read |
Prometheus text format. See Prometheus metrics. |
/api/v1/status |
GET | status:read |
Counters, state-entry counts, uptime, and (when configured) dynamic-source summary. |
/api/v1/correlations |
GET | correlations:read |
Compiled correlation list with per-group counts. Empty when the engine has no correlation rules. |
/api/v1/correlations/state |
GET | correlations:read |
Live per-group correlation window snapshot (current aggregate vs threshold, window entries, last alert, seconds to eviction). Filter with ?id= and ?group=. |
/api/v1/incidents |
GET | incidents:read |
Open incidents from the alert-pipeline grouping stage. |
/api/v1/risk |
GET | risk:read |
Open entities tracked by the risk accumulator, with their window score, tactic count, source count, and window bounds. |
/api/v1/silences |
GET, POST | silences:read, silences:write |
List silences, or create one (returns its id). |
/api/v1/silences/{id} |
DELETE | silences:write |
Remove a silence by id. |
/api/v1/dispositions |
GET, POST | dispositions:read, dispositions:write |
Ingest analyst dispositions, or read the per-rule false-positive ratio. Disabled by default; enable with daemon.dispositions.enabled: true or --enable-dispositions. |
/api/v1/rules |
GET | rules:read |
Rule counts and rules-directory path. |
/api/v1/reload |
POST | reload:execute |
Trigger an immediate rules + pipelines reload. |
/api/v1/events |
POST | events:ingest |
NDJSON event ingest. Only enabled with --input http. |
/api/v1/sources |
GET | sources:read |
Dynamic pipeline sources currently registered. |
/api/v1/sources/resolve |
POST | sources:write |
Force re-resolution of all dynamic sources (with no body) or one specific source (with {"source_id":"..."}). |
/api/v1/sources/resolve/{source_id} |
POST | sources:write |
Force re-resolution of a single source by path parameter (no body). Equivalent to the body variant above; useful when the caller has to fit inside an HTTP client that does not send a JSON body on POST. |
/api/v1/sources/cache/{source_id} |
DELETE | sources:write |
Invalidate one source’s cache so the next read fetches fresh. |
/api/v1/fields |
GET | fields:read |
Combined gap + broken-coverage report. Requires --observe-fields. |
/api/v1/fields/unknown |
GET | fields:read |
Fields seen in events that no rule references. Requires --observe-fields. |
/api/v1/fields/missing |
GET | fields:read |
Fields referenced by rules that have never appeared in an event. Requires --observe-fields. |
/api/v1/fields/observer |
DELETE | fields:write |
Reset the field observer’s counters. Requires --observe-fields. |
/api/v1/schemas |
GET | schemas:read |
Per-schema event breakdown and unknown rate. Requires --observe-schemas. |
/api/v1/schemas |
DELETE | schemas:write |
Reset the schema observer’s counters and samples (including the discovery sample). Requires --observe-schemas. |
/api/v1/schemas/suggestions |
GET | schemas:read |
Candidate schema signatures mined from the unrecognized-event sample. Requires --discover-schemas. |
/api/v1/tap |
GET | tap:read |
Stream a bounded, optionally-redacted window of the live event stream as chunked NDJSON. Disabled by default; enable with daemon.tap.enabled: true. |
/api/v1/detections/stream |
GET | detections:read |
Stream live detections as chunked NDJSON, with optional level / rule filters. Disabled by default; enable with daemon.tail.enabled: true. |
/api/v1/audit |
GET | audit:read |
Paginated control-plane mutation audit log (who, what, when, outcome). Requires --state-db; auto-enabled when a state database is configured. |
/v1/logs |
POST | events:ingest |
OTLP/HTTP log ingestion (application/x-protobuf or application/json, optionally gzip-encoded). Requires daemon-otlp. |
OTLP/gRPC LogsService/Export |
gRPC | events:ingest |
OTLP over gRPC on the same --api-addr. Requires daemon-otlp. |
The Permission column applies only when authentication is enabled; by default the daemon runs without authentication and every route is open. In-process TLS termination is available via the optional daemon-tls build feature: pass --tls-cert / --tls-key to terminate TLS for the HTTP REST, OTLP/HTTP, and OTLP/gRPC surfaces on the same --api-addr, and --tls-client-ca to require mTLS. See TLS termination for the API listener for the full flag set.
Authentication
Bearer-token authentication is opt-in and off by default, so loopback and trusted-network deployments are untouched. Enable it either with the --api-token-env <ENV_VAR> flag (a single token with full admin permissions, read from the named environment variable) or with the daemon.api.auth config block for per-token roles:
daemon:
api:
auth:
anonymous_permissions: ["metrics:read"]
roles:
triage-bot: ["*:read", "silences:write", "dispositions:write"]
tokens:
- name: grafana
role: reader
token_env: RSIGMA_API_TOKEN_GRAFANA
- name: shipper
role: ingest
token_env: RSIGMA_API_TOKEN_SHIPPER
- name: ci
role: triage-bot
token_env: RSIGMA_API_TOKEN_CI
Clients send Authorization: Bearer <token>. Each route requires the resource:action permission in the summary table above; a token’s permission set comes from its role. The built-in roles are reader (*:read), operator (*:read plus every control-plane write except reload), ingest (events:ingest only, so a log shipper’s token cannot create silences), and admin (*). Custom roles are permission lists with * wildcards ("*:read", "silences:*"); a token can also carry an inline permissions list instead of a role.
Token secrets never live in YAML: token_env names an environment variable, resolved once at startup, and a missing or empty variable fails startup. Token comparison is constant time. GET /healthz and GET /readyz are always unauthenticated so liveness probes need no secrets; anonymous_permissions grants a permission set to requests without an Authorization header (for example ["metrics:read"] keeps Prometheus scraping token-free, and ["*:read"] protects only the mutating endpoints).
Failure semantics: a missing or unrecognized token gets 401 Unauthorized (with a WWW-Authenticate: Bearer header); a recognized token without the required permission gets 403 Forbidden naming the missing permission. A presented-but-invalid token is always 401, never a fallback to the anonymous grants. OTLP/gRPC clients pass the same authorization metadata and receive UNAUTHENTICATED / PERMISSION_DENIED gRPC status codes. Rejections are counted in rsigma_api_auth_failures_total{reason} and logged at warn level with the token name (never the secret). See Security for the threat-model discussion.
Probes
GET /healthz
Liveness probe. Returns 200 once the listener has accepted at least one accept; never returns 5xx unless the process is being killed.
curl -sS http://127.0.0.1:9090/healthz
{"status":"ok"}
GET /readyz
Readiness probe. 200 when rules and pipelines are loaded; 503 during startup or after a reload failure. Drain traffic when 503.
curl -sS http://127.0.0.1:9090/readyz
{"status":"ready","rules_loaded":true}
503 body:
{"status":"starting","rules_loaded":false}
Status and counters
GET /api/v1/status
Snapshot of engine counters plus uptime. The dynamic_sources block is present only when a pipeline declares sources.
curl -sS http://127.0.0.1:9090/api/v1/status
{
"status": "running",
"detection_rules": 1,
"correlation_rules": 0,
"correlation_state_entries": 0,
"events_processed": 2,
"detection_matches": 1,
"correlation_matches": 0,
"uptime_seconds": 19.07,
"dynamic_sources": {
"total": 2,
"resolves_total": 4,
"errors_total": 0,
"cache_hits": 0
}
}
The same counters are exposed in Prometheus form on /metrics. Use /api/v1/status for a quick one-shot snapshot; use /metrics for monitoring. For a formatted view from the command line, rsigma engine status fetches this endpoint and renders it as a table (or json/ndjson/csv/tsv).
GET /api/v1/correlations
The compiled correlation list with per-group counts (no window contents). Empty when the engine has no correlation rules.
curl -sS http://127.0.0.1:9090/api/v1/correlations
{
"correlations": [
{
"index": 0,
"title": "Many Logins",
"type": "event_count",
"timespan_secs": 3600,
"group_by": ["User"],
"rule_refs": ["login-rule"],
"threshold": ">= 3",
"active_groups": 1
}
],
"count": 1
}
GET /api/v1/correlations/state
The live per-group window snapshot, the live counterpart of engine eval --dump-correlation-state. Each group reports the current aggregate (got) against the threshold, whether the condition is currently met, the window entries, earliest/latest timestamps, seconds_to_eviction, the last_alert and suppression_remaining (when applicable), and the raw window state. Filter with ?id= (correlation id, name, or title) and ?group= (substring of the rendered field=value key).
curl -sS 'http://127.0.0.1:9090/api/v1/correlations/state?group=admin'
{
"correlations": [ { "index": 0, "title": "Many Logins", "type": "event_count", "threshold": ">= 3", "active_groups": 1, "...": "..." } ],
"groups": [
{
"correlation_index": 0,
"correlation_title": "Many Logins",
"type": "event_count",
"group_key": [ { "field": "User", "value": "admin" } ],
"group_key_display": "User=admin",
"got": 2.0,
"threshold": ">= 3",
"met": false,
"entries": 2,
"timespan_secs": 3600,
"seconds_to_eviction": 3590,
"window": { "EventCount": { "timestamps": [1767225600, 1767225610] } }
}
],
"count": 1
}
GET /api/v1/incidents
Open incidents from the alert-pipeline grouping stage (present when --alert-pipeline configures a group block). Each entry has the same shape as an emitted IncidentResult, with state: open and trigger: snapshot.
curl -sS http://127.0.0.1:9090/api/v1/incidents
{
"count": 1,
"incidents": [
{
"incident_id": "f8bcd62a829b1126",
"state": "open",
"trigger": "snapshot",
"first_seen": 1719412800,
"last_seen": 1719412860,
"max_level": "high",
"result_count": 2,
"rule_counts": {"rule-1": 2},
"group_by": {"match.CommandLine": "malware x"},
"refs": [{"rule": "rule-1", "level": "high"}]
}
]
}
The include mode configured on the group block decides whether each incident carries lightweight refs or full results. See the Alert Pipeline guide.
GET /api/v1/risk
Open entities tracked by the risk accumulator (present when --risk configures an incident block). Each entry reports the entity, its accumulated window score, the distinct ATT&CK tactic count, the distinct contributing-source count, the retained contribution count, the window bounds, and the last-fired timestamp (when the entity has fired). Empty when no risk accumulator is configured.
curl -sS http://127.0.0.1:9090/api/v1/risk
{
"count": 1,
"entities": [
{
"entity_type": "user",
"entity_value": "alice",
"score": 120,
"tactic_count": 2,
"source_count": 2,
"result_count": 2,
"window_start": 1719412800,
"window_end": 1719412860,
"last_fired": 1719412860
}
]
}
See the Risk-Based Alerting guide.
GET /api/v1/silences
List operator silences (static config silences and API-created ones) with their derived state.
curl -sS http://127.0.0.1:9090/api/v1/silences
{
"count": 1,
"silences": [
{
"id": "0b6c...",
"matchers": [{"selector": "match.CommandLine", "op": "=~", "value": "malware.*"}],
"created_by": "ops",
"comment": "test maintenance",
"origin": "api",
"state": "active"
}
]
}
POST /api/v1/silences
Create a silence. The body is a JSON object with matchers (required; each {selector, op, value} where op is =, !=, =~, or !~), optional starts_at / ends_at (RFC 3339), created_by, and comment. Returns 201 with the assigned id. A missing matcher list or a bad regex returns 400. Once the dynamic-silence cap (max_silences, default 1000) is reached it returns 429; delete silences or raise the cap.
curl -sS -X POST http://127.0.0.1:9090/api/v1/silences \
-d '{"matchers":[{"selector":"rule","op":"=","value":"noisy-rule"}],"comment":"muted"}'
{ "status": "created", "id": "0b6c..." }
DELETE /api/v1/silences/{id}
Remove a silence by id. Returns 200 when removed, 404 when no such silence exists.
curl -sS -X DELETE http://127.0.0.1:9090/api/v1/silences/0b6c...
Audit trail
Append-only log of control-plane mutations (silences, dispositions, reload, source cache invalidation, field/schema observer resets). Enabled automatically when the daemon is started with --state-db; disable or tune retention with the daemon.api.audit config block. Data-plane ingest (POST /api/v1/events, OTLP) is never recorded. Each entry stores the HTTP method, matched route pattern, bearer token name (when authentication is enabled), response status, Unix timestamp, and an optional SHA-256 hex digest of the request body. The body itself is never stored. A request whose body exceeds max_body_bytes (default 64 KiB), whether declared via Content-Length or streamed, is rejected with 413 before the handler runs, and the rejected attempt is itself recorded (with a null digest).
Auth denials (401/403) are tracked separately via rsigma_api_auth_failures_total, not in this log.
GET /api/v1/audit
Paginated read of the audit log, newest first. Query parameters: limit (default 100, max 1000), offset, since and until (Unix seconds, inclusive bounds on ts). Returns 503 when audit is disabled (no state database). Requires audit:read when authentication is enabled.
curl -sS 'http://127.0.0.1:9090/api/v1/audit?limit=50'
{
"count": 1,
"entries": [
{
"id": 42,
"ts": 1714500000,
"method": "POST",
"endpoint": "/api/v1/silences",
"token": "op",
"payload_digest": "a1b2...",
"status": 201
}
]
}
Retention pruning runs on daemon startup and on every state-save interval (default 30s): rows older than max_age (default 720h) are deleted, then the table is trimmed to the newest max_entries (default 10000). An optional sink emits each record as a JSON line with on_full=drop backpressure.
Dispositions
The triage feedback loop. Disabled by default; both routes return 503 unless the daemon was started with daemon.dispositions.enabled: true, --enable-dispositions, or a configured daemon.dispositions.source. See the Triage Feedback Loop guide.
POST /api/v1/dispositions
Ingest one or more analyst dispositions. The body is a single JSON object, a JSON array of objects, or NDJSON. Each object carries rule_id (required for detection scope, with a title fallback), verdict (true_positive / false_positive / benign_true_positive), an optional scope (detection default, or incident with an incident_id), optional fingerprint / incident_id alert identities, an optional RFC 3339 timestamp, and optional analyst / note. Returns 200 with an ingest summary; a whole-body parse failure returns 400.
curl -sS -X POST http://127.0.0.1:9090/api/v1/dispositions \
-d '{"rule_id":"proc-injection","verdict":"false_positive","analyst":"alice"}'
{ "accepted": 1, "duplicate": 0, "rejected": 0 }
Redelivery is idempotent: records deduplicate on (fingerprint or incident_id, verdict), falling back to (rule_id, timestamp, analyst). An incident-scoped record with no rule_id resolves to the incident’s contributing rules through the live incident map; an unknown incident is reported in the summary’s errors and counted as rejected.
GET /api/v1/dispositions
The per-rule false-positive ratio view: the active window_seconds, numerator, and min_sample, plus a rules array (each with rule_id, the verdict counts, total, and fp_ratio, which is null until the rule reaches min_sample). This document deserializes directly as the rule scorecard --triage input.
curl -sS http://127.0.0.1:9090/api/v1/dispositions
{
"window_seconds": 2592000,
"numerator": "fp_only",
"min_sample": 5,
"rules": [
{ "rule_id": "proc-injection", "true_positives": 8, "false_positives": 1, "benign_true_positives": 0, "total": 9, "fp_ratio": 0.111 }
]
}
GET /api/v1/rules
Returns rule counts and the configured rules path. Useful for quickly confirming a reload picked up the expected number of rules.
curl -sS http://127.0.0.1:9090/api/v1/rules
{
"detection_rules": 22,
"correlation_rules": 2,
"rules_path": "/etc/rsigma/rules"
}
Reload
POST /api/v1/reload
Trigger a full reload: rules, pipelines, and dynamic source state. Equivalent to SIGHUP or to a file change inside the watched rules directory. The body is ignored.
curl -sS -X POST http://127.0.0.1:9090/api/v1/reload
{"status":"reload_triggered"}
The actual reload runs asynchronously; check /readyz and rsigma_reloads_total to confirm it completed. On a failure (parse error in a new rule), the daemon keeps serving the previously-loaded rules and increments rsigma_reloads_failed_total.
Event ingest (HTTP mode)
POST /api/v1/events
Active only when the daemon was started with --input http. Accepts NDJSON in the request body. Each line is parsed as a JSON object and queued for evaluation. Returns the number of accepted events.
curl -sS -X POST http://127.0.0.1:9090/api/v1/events \
-H 'Content-Type: application/x-ndjson' \
--data '{"CommandLine":"whoami /priv"}
{"CommandLine":"echo hello"}'
{"accepted":2}
Lines that fail to parse increment rsigma_events_parse_errors_total and are dropped silently. To inspect parse errors, scrape /metrics or watch the daemon’s stderr log.
Dynamic pipeline sources
GET /api/v1/sources
Lists every dynamic source registered by the loaded pipelines, with its type, refresh policy, and required flag.
curl -sS http://127.0.0.1:9090/api/v1/sources
{
"sources": [
{
"source_id": "ip_blocklist",
"pipeline": "dynamic_test",
"type": "Http",
"refresh": "Interval(300s)",
"required": true
},
{
"source_id": "field_config",
"pipeline": "dynamic_test",
"type": "File",
"refresh": "Once",
"required": true
}
]
}
When no pipelines declare sources:
{"sources":[]}
POST /api/v1/sources/resolve
Force re-resolution of every dynamic source (with no body) or one named source (with a JSON body):
curl -sS -X POST http://127.0.0.1:9090/api/v1/sources/resolve
{"status":"resolve_triggered"}
curl -sS -X POST http://127.0.0.1:9090/api/v1/sources/resolve \
-H 'Content-Type: application/json' \
--data '{"source_id":"ip_blocklist"}'
If no dynamic sources are configured:
{"error":"no dynamic sources configured"}
POST /api/v1/sources/resolve/{source_id}
Force re-resolution of one named source via a path parameter, with no request body. Equivalent to the body variant of POST /api/v1/sources/resolve and useful for clients that cannot send a JSON body on POST (some load balancers, the simplest curl --data '' recipes, etc.).
curl -sS -X POST http://127.0.0.1:9090/api/v1/sources/resolve/ip_blocklist
{"status":"resolve_triggered","source_id":"ip_blocklist"}
Returns 404 {"error":"no dynamic sources configured"} when no sources are registered, and 429 {"status":"resolve_already_pending"} if a refresh for the same source_id is still in flight.
DELETE /api/v1/sources/cache/{source_id}
Invalidate the cached value for one source so the next refresh fetches fresh. Useful when an upstream feed regenerates content out-of-band of its declared TTL.
curl -sS -X DELETE http://127.0.0.1:9090/api/v1/sources/cache/ip_blocklist
{"status":"invalidated","source_id":"ip_blocklist"}
The endpoint returns 200 OK for any source ID regardless of whether that ID is currently configured; nonexistent IDs are a no-op. If you need a strict check, list /api/v1/sources first and confirm the source is registered before invalidating.
Field observability
The daemon can record the field keys of every event it evaluates and join that against the field names referenced by loaded rules. This surfaces two halves of detection coverage from inside the process:
- Gap signal: fields in events that no rule references. Likely candidates for new detections, or a sign that an enricher should drop the field before ingestion.
- Broken-coverage signal: fields referenced by rules that have never appeared in an event. Either the rule is dead-lettered (wrong pipeline mapping, wrong logsource) or the event source has stopped emitting that field.
Field observation is off by default. Start the daemon with --observe-fields (and optionally --observe-fields-max-keys <N>, default 10000) to enable the surface. When disabled, all four endpoints below return 503 Service Unavailable with {"error":"field observation disabled","hint":"..."}.
Three Prometheus surfaces refresh on every /metrics scrape (and after every successful /api/v1/fields/* call): rsigma_fields_observed_total, rsigma_fields_observer_unique_keys, and rsigma_fields_observer_overflow_dropped_total. See Prometheus metrics for the catalog entries.
GET /api/v1/fields
One-shot snapshot bundling summary, unknown, and missing sections. Useful for dashboards that want all three views in a single round-trip. Each list section is paginated via ?limit=N&offset=M.
curl -sS 'http://127.0.0.1:9090/api/v1/fields?limit=10'
{
"summary": {
"events_observed": 1248,
"unique_keys_observed": 18,
"rule_fields_loaded": 22,
"overflow_dropped": 0,
"max_keys": 10000,
"uptime_seconds": 312.4,
"intersection_count": 12,
"unknown_count": 6,
"missing_count": 10
},
"unknown": {
"items": [{"field": "src_ip", "count": 1187}],
"total": 6,
"offset": 0,
"limit": 10,
"next_offset": null
},
"missing": {
"items": [{
"field": "ProcessGuid",
"rule_count": 3,
"sources": ["detection"],
"rule_titles": ["Sysmon Process Tampering", "..."],
"truncated": false
}],
"total": 10,
"offset": 0,
"limit": 10,
"next_offset": null
}
}
GET /api/v1/fields/unknown
Event field paths that the observer has seen but no loaded rule references. Sorted by descending count, then ascending name. Paginated with ?limit=N&offset=M.
curl -sS 'http://127.0.0.1:9090/api/v1/fields/unknown?limit=5'
{
"items": [
{"field": "src_ip", "count": 1187},
{"field": "User", "count": 1183}
],
"total": 6,
"offset": 0,
"limit": 5,
"next_offset": null
}
GET /api/v1/fields/missing
Field names referenced by loaded rules that have never appeared in an event since the observer was started (or last reset). Each entry includes rule_count (total rules touching the field), sources (the kinds the field originated in: detection, correlation, filter, metadata), and rule_titles (up to 10 sample titles, with truncated: true when more exist).
curl -sS 'http://127.0.0.1:9090/api/v1/fields/missing?limit=5'
{
"items": [
{
"field": "ProcessGuid",
"rule_count": 3,
"sources": ["detection"],
"rule_titles": ["Sysmon Process Tampering"],
"truncated": false
}
],
"total": 10,
"offset": 0,
"limit": 5,
"next_offset": null
}
DELETE /api/v1/fields/observer
Clear the observer’s counters and overflow tally, and reset the per-observer uptime clock. Returns what was cleared so dashboards can subtract baselines.
curl -sS -X DELETE http://127.0.0.1:9090/api/v1/fields/observer
{"status":"reset","previous_keys":18,"previous_events":1248}
A DELETE does not affect rule loading or any other daemon state. Use it after a rule reload to start a clean coverage window against the updated rule set.
Schema observability
Available when the daemon is started with --observe-schemas. Every event is classified by schema (content-based recognition: ECS, Sysmon, rendered Windows Event Log, CEF, OCSF, a generic_json fallback, plus any --schema-config signatures), so a mixed stream’s composition and its unknown rate are visible at a glance. See engine classify for the one-shot equivalent and the signature format.
GET /api/v1/schemas
Returns the per-schema counts and the classified/unknown totals since daemon start. Returns 503 when --observe-schemas is off.
curl -sS http://127.0.0.1:9090/api/v1/schemas
{
"summary": {
"events_observed": 1248,
"classified": 1203,
"unknown": 45,
"ambiguous": 0,
"uptime_seconds": 612.4
},
"by_schema": [
{"schema": "ecs", "count": 900},
{"schema": "sysmon", "count": 250},
{"schema": "generic_json", "count": 53}
],
"unknown_shapes": [
{"keys": ["deviceModel", "vendorField", "widgetId"], "count": 45}
],
"routing_pruning": [
{"schema": "sysmon", "eligible": 120, "pruned": 380}
]
}
unknown_shapes is a bounded, redacted sample of the field-key sets (key names only, never values) of unknown events, so you can author a signature for what is unrecognized. routing_pruning is the per-schema eligible-versus-pruned rule count, present when schema routing and logsource routing are both active. The same signals are exposed as the rsigma_events_by_schema_total{schema}, rsigma_events_unknown_schema_total, rsigma_events_ambiguous_schema_total, and rsigma_schema_rules_eligible{schema} / rsigma_schema_rules_pruned{schema} Prometheus metrics. A rising unknown rate flags a source whose schema RSigma does not recognize; add a signature with --schema-config.
GET /api/v1/schemas/suggestions
Mines the daemon’s unrecognized-event sample into candidate schema signatures, the live equivalent of engine discover-schemas. Requires --discover-schemas (which implies --observe-schemas); returns 503 when that sampler is off. Because the sample is keys-only (values are never retained), proposals use presence predicates and are tagged source: keys-only; run the offline command over a corpus for value markers.
curl -sS http://127.0.0.1:9090/api/v1/schemas/suggestions
{
"summary": {"events_mined": 320, "shapes": 4, "clusters": 2, "candidates": 2},
"candidates": [
{
"name": "discovered_devicevendor",
"specificity": 60,
"source": "keys-only",
"support": 210,
"coverage_of_unknown": 0.66,
"predicates": ["field_present: deviceVendor", "field_present: signatureId"],
"sample_field_sets": [["deviceVendor", "signatureId", "src"]],
"overlap_warnings": []
}
],
"signatures_yaml": "schemas:\n - name: discovered_devicevendor\n specificity: 60\n match:\n - field_present: deviceVendor\n - field_present: signatureId\n"
}
The rsigma_unknown_schema_clusters gauge tracks how many distinct schemas discovery would propose. Review, rename, and refine the signatures_yaml before committing it to a --schema-config file.
DELETE /api/v1/schemas
Reset the schema observer, clearing the per-schema counts, the unknown-shape sample, and the discovery sample. The discovery sample is capped and not a reservoir, so on a long-running daemon it eventually stops admitting genuinely new shapes; a DELETE refreshes it without a restart. Returns 503 when --observe-schemas is off.
curl -sS -X DELETE http://127.0.0.1:9090/api/v1/schemas
Live event tap
GET /api/v1/tap
Stream a bounded window of the live event stream as chunked NDJSON, one event per line, followed by a summary record. The capture ends at duration or limit, whichever comes first, and a dropped client connection tears the session down automatically. The capture is lossy by design: a full per-session buffer drops events (counted in the summary) rather than ever applying backpressure to the engine. This is the endpoint behind rsigma engine tap.
Disabled by default (the tap exfiltrates raw events). Enable it with daemon.tap.enabled: true or the --enable-tap flag; otherwise the endpoint returns 503 Service Unavailable with {"error":"event tap disabled","hint":"..."}.
| Query param | Default | Description |
|---|---|---|
duration |
30s |
Capture window (humantime). Rejected with 400 above daemon.tap.max_duration (default 5m). |
limit |
unset | Stop after N events, before the duration if reached first. |
stage |
decoded |
decoded (post-parse, post-filter) or raw (the input line as received). |
redact |
unset | Comma-separated dotted field paths, redacted server-side before the data leaves the daemon. |
curl -sS -N 'http://127.0.0.1:9090/api/v1/tap?duration=10s&redact=user.email,src_ip'
{"CommandLine":"whoami","src_ip":"rsigma:redacted:cfea2addbf5c8284","user":{"email":"rsigma:redacted:509efebfb0e7ac1e"}}
{"CommandLine":"id","src_ip":"rsigma:redacted:8e1b...","user":{"email":"rsigma:redacted:1f9c..."}}
{"rsigma_tap_summary":{"captured":2,"dropped":0,"duration_ms":10000,"stage":"decoded"}}
Redaction is server-side. Raw values for redacted fields never cross the wire. Each value is replaced with a deterministic per-session token (rsigma:redacted:<16 hex>), so equal values map to equal tokens within one capture (preserving correlation cardinality on replay) while a random per-session salt blocks dictionary reversal and cross-fixture linkage. Paths use the same object-key / numeric-index navigation as the enrichment template engine, except a non-numeric segment meeting an array fans out to every element (fail-closed).
Error semantics:
| Status | When |
|---|---|
400 Bad Request |
Malformed params, an invalid stage, or a duration over daemon.tap.max_duration. |
409 Conflict |
The concurrent-session cap (daemon.tap.max_sessions, default 2) is reached. |
503 Service Unavailable |
The tap is disabled (the default; not enabled via daemon.tap.enabled: true or --enable-tap). |
Anyone who can reach this endpoint can read live event traffic. It is off by default; enable it only behind mTLS and redact sensitive fields. See Security.
Four Prometheus metrics track the tap: rsigma_tap_sessions_total, rsigma_tap_active_sessions, rsigma_tap_events_streamed_total, and rsigma_tap_events_dropped_total. See Prometheus metrics.
Live detection tail
GET /api/v1/detections/stream
Stream live detections as chunked NDJSON, one result per line, followed by a summary record. The capture ends at duration or limit, whichever comes first; with neither it streams until the client disconnects. Each line is the same EvaluationResult shape the sinks emit (so engine tail and a saved sink file are the same format), captured after post-evaluation enrichment and before dispatch, regardless of which sinks are configured. The stream is lossy by design: a full per-session buffer drops detections (counted in the summary) rather than ever backpressuring the sink task. This is the endpoint behind rsigma engine tail.
Disabled by default. Enable it with daemon.tail.enabled: true or the --enable-tail flag; otherwise the endpoint returns 503 Service Unavailable with {"error":"detection tail disabled","hint":"..."}.
| Query param | Default | Description |
|---|---|---|
duration |
unset | Capture window (humantime). Unset streams until the client disconnects. |
limit |
unset | Stop after N detections, before the duration if reached first. |
level |
unset | Minimum severity (informational, low, medium, high, critical); lower or unleveled results are excluded. |
rule |
unset | Case-insensitive substring matched against the rule title or id. |
curl -sS -N 'http://127.0.0.1:9090/api/v1/detections/stream?level=high&rule=whoami'
{"rule_title":"Whoami Detector","rule_id":"...","level":"high","tags":[],"matched_selections":["selection"],"matched_fields":[{"field":"CommandLine","value":"whoami"}]}
{"rsigma_tail_summary":{"streamed":1,"dropped":0}}
Error semantics:
| Status | When |
|---|---|
400 Bad Request |
Malformed params or an invalid level. |
409 Conflict |
The concurrent-session cap (daemon.tail.max_sessions, default 2) is reached. |
503 Service Unavailable |
The tail is disabled (the default; not enabled via daemon.tail.enabled: true or --enable-tail). |
Two Prometheus metrics track the tail: rsigma_tail_active_sessions and rsigma_tail_detections_dropped_total. See Prometheus metrics.
OTLP ingest
POST /v1/logs (HTTP)
OTLP log ingestion over HTTP. Accepts application/x-protobuf or application/json, optionally gzip-encoded. Returns application/x-protobuf (or application/json matching the request) with the standard OTLP ExportLogsServiceResponse. Requires the daemon to be built with daemon-otlp.
gRPC LogsService/Export
The same OTLP gRPC service binds on the same --api-addr. Use grpcurl or any OTLP client to publish:
grpcurl -plaintext -d @ rsigma.internal:9090 \
opentelemetry.proto.collector.logs.v1.LogsService/Export \
< logs.json
See OTLP Integration for full agent recipes (Grafana Alloy, Vector, Fluent Bit, OpenTelemetry Collector) and the LogRecord-to-rsigma field mapping.
See also
- Streaming Detection for the daemon overview and hot-reload semantics.
- OTLP Integration for
/v1/logsagent recipes. - Prometheus Metrics for
/metricsdefinitions and alert recipes. - Observability for the broader
tracingand metrics story. - Processing Pipelines: dynamic pipelines for the source declarations exposed by
/api/v1/sources. - Security: TLS termination for the API listener for the optional
daemon-tlsbuild feature and the--tls-*flag set.