curl --request GET \
--url {scheme}://{host}:{port}/{basePath}/analytics/cost \
--header 'X-API-Key: <api-key>'import requests
url = "{scheme}://{host}:{port}/{basePath}/analytics/cost"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('{scheme}://{host}:{port}/{basePath}/analytics/cost', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "62437",
CURLOPT_URL => "{scheme}://{host}:{port}/{basePath}/analytics/cost",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "{scheme}://{host}:{port}/{basePath}/analytics/cost"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("{scheme}://{host}:{port}/{basePath}/analytics/cost")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("{scheme}://{host}:{port}/{basePath}/analytics/cost")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"apiVersion": "v1",
"kind": "CostSummary",
"spec": {
"periodStart": "2026-08-26T14:00:00Z",
"periodEnd": "2026-09-25T14:00:00Z",
"totalLLMCost": 18.72,
"incidentCount": 312,
"costPerIncident": 0.06
}
}
Analytics Cost
Returns the LLM spend the controllers booked per incident — total cost, incident count and cost per incident for a namespace and window
curl --request GET \
--url {scheme}://{host}:{port}/{basePath}/analytics/cost \
--header 'X-API-Key: <api-key>'import requests
url = "{scheme}://{host}:{port}/{basePath}/analytics/cost"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('{scheme}://{host}:{port}/{basePath}/analytics/cost', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "62437",
CURLOPT_URL => "{scheme}://{host}:{port}/{basePath}/analytics/cost",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "{scheme}://{host}:{port}/{basePath}/analytics/cost"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("{scheme}://{host}:{port}/{basePath}/analytics/cost")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("{scheme}://{host}:{port}/{basePath}/analytics/cost")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"apiVersion": "v1",
"kind": "CostSummary",
"spec": {
"periodStart": "2026-08-26T14:00:00Z",
"periodEnd": "2026-09-25T14:00:00Z",
"totalLLMCost": 18.72,
"incidentCount": 312,
"costPerIncident": 0.06
}
}
from and to are an absolute period: with both, exactly [from, to]; with only from, it runs to now; with only to, the 30 days ending at to; with neither, the last 30 days.Summary Fields
| Field | Description |
|---|---|
periodStart / periodEnd | Period boundaries (periodEnd is to, or the request time without it) |
totalLLMCost | Sum of every incident’s estimatedCostUSD booked inside the window (entries without a recordedAt, written before the stamp existed, always count) |
incidentCount | Incidents with a ledger entry in the window |
costPerIncident | totalLLMCost / incidentCount |
viewer. A namespace with no chatcli-cost-ledger yet answers 200 with zero totals; any other failure to read the ledger returns 500 (failed to read the cost ledger: ...) instead of zeros. Each LLM call is priced at its own model’s rate when it is booked and added to the incident’s total, and concurrent bookings retry on conflict, so none is lost. There is no ROI, downtime-cost or per-incident breakdown in the response.
The ledger lives in one chatcli-cost-ledger ConfigMap per namespace (found by the label app.kubernetes.io/managed-by: chatcli-operator when aggregating); see Capacity, Noise and Cost for its format and the pricing override.
{
"apiVersion": "v1",
"kind": "CostSummary",
"spec": {
"periodStart": "2026-08-26T14:00:00Z",
"periodEnd": "2026-09-25T14:00:00Z",
"totalLLMCost": 18.72,
"incidentCount": 312,
"costPerIncident": 0.06
}
}
Authorizations
API key sent in the X-API-Key header. Keys are read from the Secret chatcli-operator-secrets, key api-keys (fallback: ConfigMap chatcli-operator-config, key api-keys) in the operator namespace, as a YAML list of {key, role, name, description} (name is the identity recorded on approval decisions); the operator chart creates it only with apiKeys.create: true. Roles: viewer < operator < admin — any other role string is denied everywhere. Changes are picked up within about 30 seconds; deleting both the Secret and the ConfigMap revokes every key. With no keys configured every /api/ call returns 401, unless CHATCLI_OPERATOR_DEV_MODE=true, which grants admin without a key (development only).
Query Parameters
Ledger to read. Empty aggregates every namespace.
Start of the time range (RFC3339). Unparseable values are ignored.
"2026-03-01T00:00:00Z"
End of the time range (RFC3339). Unparseable values are ignored.
"2026-03-19T23:59:59Z"