API reference
The Setup plugin exposes a REST API under /_plugins/_setup/. All endpoints require authentication.
Settings
Update settings
Persists configuration settings to the .wazuh-settings index. Currently, it supports the engine.index_raw_events boolean flag, which controls whether the Engine indexes raw events into the wazuh-events-raw-v5 data stream.
Request
- Method:
PUT - Path:
/_plugins/_setup/settings
Request body
engine(Object, required) — Engine settings object.engine.index_raw_events(Boolean, required) — whether the Engine indexes raw events into thewazuh-events-raw-v5data stream.
Example request
curl -sk -u admin:$WAZUH_INDEXER_ADMIN_PASSWORD -X PUT \
"https://127.0.0.1:9200/_plugins/_setup/settings" \
-H 'Content-Type: application/json' \
-d '{
"engine": {
"index_raw_events": true
}
}'
Example response (success)
{
"message": "Settings updated successfully.",
"status": 200
}
Example response (missing field)
{
"message": "Missing required field: 'engine.index_raw_events'.",
"status": 400
}
Example response (invalid type)
{
"message": "Field 'engine.index_raw_events' must be of type boolean.",
"status": 400
}
Status codes
- 200 — settings updated successfully.
- 400 — invalid request body, missing required fields, or wrong field type.
- 500 — internal server error (e.g., failed to persist settings to the index).
AI Assistant Sessions
Writes to the caller’s own AI assistant sessions, stored in the wazuh-ai-assistant-sessions data
stream. Every operation requires the plugin:wazuh/ai_assistant/session/write cluster permission,
which the wazuh_ai_assistant role — mapped to every authenticated user — grants.
user is derived from the authenticated caller, not from the request body. A value sent for it
is discarded, so a session can only ever be created under the caller’s own name. Update, rename and
delete load the stored session first and refuse anything that is not the caller’s, reported as
404 rather than 403 so the response does not confirm that somebody else’s session id exists.
There are no read endpoints, by design. Listing sessions and reading a transcript are direct
queries against the data stream, scoped by the role’s Document Level Security filter
({"term": {"user": "${user.name}"}}), which correctly scopes reads and gives you OpenSearch’s own
search, sorting and pagination. See
Access control.
Optimistic concurrency
Every write returns an opaque version token. A data stream permits no unconditional write to
one of its backing indices, so every write carries a concurrency check; version is what makes that
check visible to clients. Remember the token from the last response seen for a session and send it
back as expected_version on the next PUT — never parse or compare it.
On 409, re-read the session from the index with seq_no_primary_term: true, merge, and retry with
the version that read returned. Do not retry with a freshly read version without merging: that is
the silent overwrite expected_version exists to prevent.
Create a session
Only title and messages are persisted. user, created_at, updated_at and @timestamp are
stamped by the server; values sent for them, and any key outside the allowlist, are discarded, not
rejected, so a client may POST back a document it previously read. The id is generated by the
server: the data stream mints it, and client-chosen ids are not supported.
Request
- Method:
POST - Path:
/_plugins/_setup/ai_assistant/sessions
Request body
title(String, required) — must contain something other than whitespace, 200 characters or fewer. Trimmed before storing.messages(Array, required) — the transcript, up to 1000 entries. May be[]to open a blank session. Each entry is treated as an opaque object:messagesis mapped as{"type": "object", "enabled": false}, so entries are stored and returned verbatim but never indexed or schema-checked, and a provider returning unanticipated fields cannot break saving.
The whole request body must be 5 MiB or smaller, on every write route. The entry count bounds
how many turns a session holds but not how large each one is; without a byte ceiling the only limit
would be the cluster’s http.max_content_length (100MB by default), which would make the
500-session per-owner cap meaningless as a storage bound. Oversized bodies are rejected with 400
before being parsed.
Example request
curl -sk -u admin:$WAZUH_INDEXER_ADMIN_PASSWORD -X POST \
"https://127.0.0.1:9200/_plugins/_setup/ai_assistant/sessions" \
-H 'Content-Type: application/json' \
-d '{
"title": "Any brute force attempts today?",
"messages": [
{ "role": "user", "content": "Any brute force attempts today?", "createdAt": 1788961419723 }
]
}'
Example response (success)
{
"id": "8itvhqABtpJqXy3PWi31",
"title": "Any brute force attempts today?",
"created_at": "2026-09-09T13:50:39.861Z",
"updated_at": "2026-09-09T13:50:39.861Z",
"messages": [
{ "role": "user", "content": "Any brute force attempts today?", "createdAt": 1788961419723 }
],
"version": "12:1"
}
Status codes
- 200 — session created.
200, not201, matchingPOST /_plugins/_setup/ai_assistant/providers. - 400 — empty or malformed body; a missing, blank or over-long
title; a missingmessages; more than 1000 messages; a body over 5 MiB. - 403 — the caller lacks the
plugin:wazuh/ai_assistant/session/writecluster permission. - 409 — the caller already owns the maximum of 500 sessions.
409rather than400: the request is well formed, it is the stored state that blocks it. Deleting a session frees a slot; updating an existing session is never blocked by the cap.
Replace a session’s transcript
Replaces messages, and title when one is sent. user, created_at and @timestamp are carried
over from the stored session; updated_at is re-stamped.
Request
- Method:
PUT - Path:
/_plugins/_setup/ai_assistant/sessions/{id}
Request body
messages(Array, required) — replaces the stored transcript wholesale. Up to 1000 entries.title(String, optional) — optional on purpose. A chat client auto-saves on every turn; if each save resent a recomputed title it would silently revert a rename the user had just made. When absent, the stored title is kept.expected_version(String, optional) — theversionthe client last saw. When present, the write is rejected with409if the stored session has moved past it. When absent, the write still carries the version this request just read, so the check narrows to “since this request started” rather than being disabled.
Example request
curl -sk -u admin:$WAZUH_INDEXER_ADMIN_PASSWORD -X PUT \
"https://127.0.0.1:9200/_plugins/_setup/ai_assistant/sessions/8itvhqABtpJqXy3PWi31" \
-H 'Content-Type: application/json' \
-d '{
"messages": [
{ "role": "user", "content": "Any brute force attempts today?", "createdAt": 1788961419723 },
{ "role": "assistant", "content": "No brute force activity in the last 24 hours.", "createdAt": 1788961419744 }
],
"expected_version": "12:1"
}'
Example response (conflict)
{
"message": "Session was updated by another session since you last loaded it. Refresh and retry.",
"status": 409
}
Status codes
- 200 — session updated. Returns the full session with the write’s own fresh
version. - 400 — empty or malformed body; a
titlethat is present but blank or over-long; a missingmessages; more than 1000 messages; a body over 5 MiB. - 403 — the caller lacks the cluster permission.
- 404 — no session exists with the given id, or it belongs to another user.
- 409 — the session changed since the version being checked against. The write was not applied.
Rename a session
Changes title and nothing else. messages, user, created_at and @timestamp are carried over
untouched, and updated_at is deliberately not re-stamped: a rename is not session activity, and
bumping it would reorder a session list sorted by last activity.
Separate from PUT because a rename happens from a list row, where the client holds only a summary
and not the transcript.
Request
- Method:
PATCH - Path:
/_plugins/_setup/ai_assistant/sessions/{id}
Request body
title(String, required) — must contain something other than whitespace, 200 characters or fewer. Trimmed before storing.
Example request
curl -sk -u admin:$WAZUH_INDEXER_ADMIN_PASSWORD -X PATCH \
"https://127.0.0.1:9200/_plugins/_setup/ai_assistant/sessions/8itvhqABtpJqXy3PWi31" \
-H 'Content-Type: application/json' \
-d '{ "title": "Agent 003 connectivity" }'
Example response (success)
{
"id": "8itvhqABtpJqXy3PWi31",
"title": "Agent 003 connectivity",
"updated_at": "2026-09-09T13:44:09.066Z",
"version": "13:1"
}
The response carries the write’s own fresh version, so a client whose renamed session is also the
open one does not conflict on its next auto-save.
Status codes
- 200 — session renamed.
- 400 — empty or malformed body; a
titlethat is blank, whitespace-only or over-long; a body over 5 MiB. - 403 — the caller lacks the cluster permission.
- 404 — no session exists with the given id, or it belongs to another user.
- 409 — the session was written or deleted between this request’s read and its write.
Delete a session
Ownership is checked against the stored session first; one belonging to another user is reported as
404 and left untouched.
Request
- Method:
DELETE - Path:
/_plugins/_setup/ai_assistant/sessions/{id}
Example request
curl -sk -u admin:$WAZUH_INDEXER_ADMIN_PASSWORD -X DELETE \
"https://127.0.0.1:9200/_plugins/_setup/ai_assistant/sessions/8itvhqABtpJqXy3PWi31"
Example response (success)
{
"message": "Session deleted.",
"status": 200,
"id": "8itvhqABtpJqXy3PWi31"
}
Status codes
- 200 — session deleted.
- 403 — the caller lacks the cluster permission.
- 404 — no session exists with the given id, or it belongs to another user. A repeated delete also returns
404.
Documentation maintenance — modifications to the REST API must be reflected in both
openapi.ymland this file.