Holmes Chat API¶
Note
This feature is available with the Robusta SaaS platform and self-hosted commercial plans. It is not available in the open-source version.
Use this endpoint to send questions to Holmes AI for root cause analysis. You can provide alert context and Holmes will investigate and return a detailed markdown analysis.
POST https://api.robusta.dev/api/holmes/<account_id>/chat¶
URL Parameters¶
Parameter |
Type |
Description |
Required |
|---|---|---|---|
|
string |
The unique account identifier (found in your |
Yes |
Request Body¶
Parameter |
Type |
Description |
Required |
|---|---|---|---|
|
string |
The cluster to query. If not provided, Holmes will try to extract it from the payload or use the single connected cluster. |
No |
|
string |
The question to ask Holmes. |
Yes |
|
object |
Alert payload or context data for Holmes to analyze (e.g., Prometheus alert labels). |
No |
|
string |
Additional instructions to include in the system prompt. |
No |
|
string |
The AI model to use. If not provided, uses the account default. |
No |
Example Request¶
The following curl command demonstrates how to ask Holmes about a failing pod:
curl -X POST 'https://api.robusta.dev/api/holmes/ACCOUNT_ID/chat' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer API-KEY' \
--data '{
"cluster_id": "my-cluster",
"ask": "Which pods are failing in the cluster?",
"payload": {
"alertname": "KubeContainerWaiting",
"container": "frontend",
"namespace": "production",
"pod": "frontend-59fbcd7965-drx6w",
"reason": "ContainerCreating",
"severity": "warning"
}
}'
In the command, make sure to replace the following placeholders:
ACCOUNT_ID: Your account ID, which can be found in yourgenerated_values.yamlfile.API-KEY: Your API Key for authentication. You can generate this token in the platform by navigating to Settings -> API Keys -> New API Key, and creating a key with the "Robusta AI" resource and "Write" permission.
Request Headers¶
Header |
Description |
|---|---|
|
Must be |
|
Bearer token for authentication (e.g., |
Response Format¶
The API returns a JSON object containing the cluster ID and the analysis in markdown format.
Example Response¶
{
"cluster_id": "my-cluster",
"analysis": "## Investigation Summary\n\n### Failing Pods\n\n| Pod | Namespace | Status | Reason |\n|-----|-----------|--------|--------|\n| `frontend-59fbcd7965-drx6w` | production | Pending | ContainerCreating |\n\n**Root Cause**: Missing Secret - The pod is stuck because the secret `frontend-api-keys` does not exist.\n\n### Fix\n\n```bash\nkubectl create secret generic frontend-api-keys --from-literal=API_KEY=<your-key> -n production\n```"
}
Response Fields¶
Field |
Type |
Description |
|---|---|---|
|
string |
The cluster that was queried. |
|
string |
Holmes AI analysis in markdown format. Contains investigation findings, root cause analysis, and remediation steps. |
Handling the Markdown Response¶
The analysis field contains markdown text with escaped newlines (\n). To render it properly:
Python:
import json
response_data = json.loads(response.text)
markdown_text = response_data["analysis"] # Already unescaped by json.loads
print(markdown_text) # Renders with proper line breaks
JavaScript:
const data = JSON.parse(responseText);
const markdownText = data.analysis; // Already unescaped by JSON.parse
// Use a markdown renderer like marked.js or react-markdown
Bash (using jq):
curl ... | jq -r '.analysis' # -r outputs raw string with actual newlines
Error Responses¶
Status |
Error |
Description |
|---|---|---|
400 |
Bad Request |
Invalid request body or could not determine cluster. |
401 |
Unauthorized |
Invalid or missing API key. |
403 |
Forbidden |
API key lacks required permissions. |
404 |
Not Found |
Specified cluster is not connected to this account. |
500 |
Internal Server Error |
Holmes request failed. |
Cluster Resolution¶
If cluster_id is not provided in the request, Holmes will attempt to determine the cluster using the following logic:
Extract from
payloadfields (cluster_name,cluster,cluster_id,source, or fromlabels/annotations).Use AI to extract the cluster name from the payload content.
If only one cluster is connected to the account, use that cluster.
If none of these methods succeed and multiple clusters are connected, the API will return an error listing the available clusters.
Streaming (SSE) Mode¶
The same endpoint supports streaming responses via Server-Sent Events (SSE). Instead of waiting for the full analysis, you receive a real-time stream of events as Holmes investigates — including tool calls, intermediate AI messages, and the final answer.
To enable streaming, set stream to true in the request body. All other parameters remain the same.
Example Streaming Request¶
curl -N -X POST 'https://api.robusta.dev/api/holmes/ACCOUNT_ID/chat' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer API-KEY' \
--data '{
"cluster_id": "my-cluster",
"ask": "Which pods are failing in the cluster?",
"stream": true,
"payload": {
"alertname": "KubeContainerWaiting",
"container": "frontend",
"namespace": "production",
"pod": "frontend-59fbcd7965-drx6w"
}
}'
Note
The -N flag disables output buffering in curl, which is important for seeing events as they arrive.
Streaming Response Format¶
The response has Content-Type: text/event-stream and uses the standard SSE wire format. Each event consists of an event line specifying the type, a data line containing a JSON payload, and a blank line:
event: <event_type>
data: <json_payload>
Events are delivered in order as the investigation progresses. A typical stream looks like this:
event: start_tool_calling
data: {"tool_call_id": "call_1", "tool_name": "kubectl_find_resource", "description": "Looking up failing pods"}
event: tool_calling_result
data: {"tool_call_id": "call_1", "name": "kubectl_find_resource", "result": {"status": "success", "data": "..."}}
event: ai_message
data: {"content": "I found a pod stuck in ContainerCreating state. Let me investigate further."}
event: start_tool_calling
data: {"tool_call_id": "call_2", "tool_name": "kubectl_describe_resource", "description": "Describing pod frontend-59fbcd7965-drx6w"}
event: tool_calling_result
data: {"tool_call_id": "call_2", "name": "kubectl_describe_resource", "result": {"status": "success", "data": "..."}}
event: token_count
data: {"input_tokens": 1520, "output_tokens": 305}
event: ai_answer_end
data: {"analysis": "## Investigation Summary\n\nThe pod `frontend-59fbcd7965-drx6w` is stuck ..."}
SSE Event Types¶
Event Type |
Description |
|---|---|
|
Holmes has started executing a tool (e.g., a |
|
A tool call has completed. Contains the result or error information. |
|
An intermediate text message from the AI while it is still investigating. These are partial thoughts, not the final answer. |
|
The final analysis. The |
|
An error occurred during the investigation. This is a terminal event. |
|
Token usage statistics for the request. |
|
Holmes needs user approval before executing a potentially dangerous tool. See approval_required below. |
Event Payloads¶
start_tool_calling¶
Emitted when Holmes begins executing a tool.
{
"tool_call_id": "call_abc123",
"tool_name": "kubectl_find_resource",
"description": "kubectl get pods -n production"
}
Field |
Type |
Description |
|---|---|---|
|
string |
Unique identifier for this tool call. Use it to correlate with the corresponding |
|
string |
Name of the tool being executed. |
|
string |
Human-readable description of what the tool is doing. |
tool_calling_result¶
Emitted when a tool call completes.
{
"tool_call_id": "call_abc123",
"name": "kubectl_find_resource",
"result": {
"status": "success",
"data": "NAME READY STATUS RESTARTS AGE\nfrontend-59fbcd7965-drx6w 0/1 ContainerCreating 0 12m"
}
}
Field |
Type |
Description |
|---|---|---|
|
string |
Matches the |
|
string |
Name of the tool that was called. |
|
object |
Result object with a |
|
string |
Optional. |
ai_message¶
Emitted when the AI produces intermediate text during its investigation.
{
"content": "I found a pod stuck in ContainerCreating state. Let me check the events."
}
Field |
Type |
Description |
|---|---|---|
|
string |
The text content of the AI's intermediate message. |
ai_answer_end¶
Emitted once when the investigation is complete. This is a terminal event — the stream ends after this.
{
"analysis": "## Investigation Summary\n\n**Root Cause**: Missing Secret ..."
}
Field |
Type |
Description |
|---|---|---|
|
string |
The complete analysis in markdown format. Identical to the |
error¶
Emitted when an error occurs. This is a terminal event — the stream ends after this.
{
"msg": "Failed to connect to cluster",
"error_code": "CLUSTER_ERROR"
}
Field |
Type |
Description |
|---|---|---|
|
string |
Human-readable error message. |
|
string |
Optional error code identifier. |
token_count¶
Emitted with token usage statistics.
{
"input_tokens": 1520,
"output_tokens": 305
}
Field |
Type |
Description |
|---|---|---|
|
integer |
Number of input tokens consumed. |
|
integer |
Number of output tokens generated. |
approval_required¶
Emitted when Holmes needs explicit user approval before executing a tool. This is a terminal event — the stream pauses until approval is granted.
{
"conversation_history": [
{
"role": "assistant",
"content": "I need to run a command that modifies resources.",
"tool_calls": [
{
"id": "call_xyz789",
"pending_approval": true,
"function": {
"name": "kubectl_exec",
"arguments": "{\"command\": \"kubectl delete pod test-pod -n default\"}"
}
}
]
}
]
}
Field |
Type |
Description |
|---|---|---|
|
array |
The full conversation history including the pending tool call. Assistant messages contain a |
Consuming the Stream¶
Python (using requests):
import requests
import json
response = requests.post(
"https://api.robusta.dev/api/holmes/ACCOUNT_ID/chat",
headers={
"Content-Type": "application/json",
"Authorization": "Bearer API-KEY",
},
json={
"ask": "Which pods are failing on cluster prod-us?",
"stream": True,
},
stream=True,
)
event_type = None
for line in response.iter_lines(decode_unicode=True):
if not line:
continue
if line.startswith("event: "):
event_type = line[7:]
elif line.startswith("data: ") and event_type:
data = json.loads(line[6:])
if event_type == "ai_answer_end":
print("Final analysis:", data["analysis"])
elif event_type == "ai_message":
print("AI:", data["content"])
elif event_type == "start_tool_calling":
print(f"Running tool: {data['tool_name']}")
elif event_type == "error":
print(f"Error: {data['msg']}")
event_type = None