# Look up the Control Plane and Service IDs so the dashboard's gateway_service
# preset filter resolves to the scoped UUID Konnect expects.
CP_ID=$(kongctl get gateway control-plane "${KONNECT_CONTROL_PLANE_NAME}" \
--pat "${KONNECT_TOKEN}" -o json --jq '.id' -r)
SERVICE_ID=$(kongctl get gateway control-plane service claude-code-sso \
--control-plane-name "${KONNECT_CONTROL_PLANE_NAME}" \
--pat "${KONNECT_TOKEN}" -o json --jq '.id' -r)
EXISTING_DASHBOARDS=$(kongctl api get "/v2/dashboards?filter%5Blabels.recipe%5D=claude-code-sso-recipe" \
--pat "${KONNECT_TOKEN}" -o json --jq '.data | length')
if [ "${EXISTING_DASHBOARDS}" -gt 0 ]; then
echo "Claude Code Usage dashboard already exists. Reusing."
else
cat <<'EOF' | jq --arg ref "${CP_ID}:${SERVICE_ID}" '.definition.preset_filters[0].value = [$ref]' > claude-code-usage-dashboard.json
{
"name": "Claude Code Usage",
"definition": {
"tiles": [
{
"id": "e3f9588a-71fd-4996-818f-33c93afe6eae",
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 1
},
"position": {
"col": 0,
"row": 0
}
},
"definition": {
"chart": {
"type": "single_value",
"chart_title": "Total cost ($)"
},
"query": {
"filters": [
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"cost"
],
"datasource": "llm_usage",
"dimensions": []
}
}
},
{
"id": "9edab295-d705-403f-a612-59847420a195",
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 1
},
"position": {
"col": 2,
"row": 0
}
},
"definition": {
"chart": {
"type": "single_value",
"chart_title": "Total Tokens"
},
"query": {
"filters": [
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"total_tokens"
],
"datasource": "llm_usage",
"dimensions": []
}
}
},
{
"id": "e5cc504e-c505-4f0b-8633-638a0e7b5833",
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 1
},
"position": {
"col": 4,
"row": 0
}
},
"definition": {
"chart": {
"type": "single_value",
"chart_title": "Total Claude Code requests"
},
"query": {
"filters": [
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"ai_request_count"
],
"datasource": "llm_usage",
"dimensions": []
}
}
},
{
"id": "4ab1fb73-d60b-4f77-b915-a0c88d427263",
"type": "chart",
"layout": {
"size": {
"cols": 3,
"rows": 2
},
"position": {
"col": 0,
"row": 1
}
},
"definition": {
"chart": {
"type": "top_n",
"chart_title": "Top Claude Models by Usage"
},
"query": {
"limit": 10,
"filters": [],
"metrics": [
"total_tokens",
"ai_request_count"
],
"datasource": "llm_usage",
"dimensions": [
"ai_request_model"
]
}
}
},
{
"id": "e6c5fbe0-9dd2-46ac-af1c-3ba3af9ffd43",
"type": "chart",
"layout": {
"size": {
"cols": 3,
"rows": 2
},
"position": {
"col": 3,
"row": 1
}
},
"definition": {
"chart": {
"type": "timeseries_line",
"stacked": false,
"chart_title": "Model usage trend (top 5)"
},
"query": {
"limit": 5,
"filters": [
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"total_tokens"
],
"datasource": "llm_usage",
"dimensions": [
"ai_request_model",
"time"
]
}
}
},
{
"id": "fd14bf28-0cd2-409e-9a48-2cbbd7907409",
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 0,
"row": 3
}
},
"definition": {
"chart": {
"type": "donut",
"chart_title": "Claude health check"
},
"query": {
"filters": [
{
"field": "gateway_service",
"operator": "not_empty"
},
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"ai_request_count"
],
"datasource": "llm_usage",
"dimensions": [
"status_code_grouped"
]
}
}
},
{
"id": "f26edaa8-56d5-49f8-8f04-ade9451c76ec",
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 2,
"row": 3
}
},
"definition": {
"chart": {
"type": "donut",
"chart_title": "Claude provider usage"
},
"query": {
"filters": [
{
"field": "gateway_service",
"operator": "not_empty"
},
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"ai_request_count"
],
"datasource": "llm_usage",
"dimensions": [
"ai_provider"
]
}
}
},
{
"id": "d27037a3-7ace-49cc-affb-3b34a71ab9b1",
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 4,
"row": 3
}
},
"definition": {
"chart": {
"type": "timeseries_bar",
"stacked": true,
"chart_title": "LLM latency (avg)"
},
"query": {
"filters": [
{
"field": "ai_request_model",
"operator": "not_empty"
},
{
"field": "ai_request_model",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"llm_latency_average"
],
"datasource": "llm_usage",
"dimensions": [
"ai_request_model",
"time"
]
}
}
},
{
"id": "5f88c24b-80de-4fe1-8948-ef0bde29f948",
"type": "chart",
"layout": {
"size": {
"cols": 3,
"rows": 2
},
"position": {
"col": 0,
"row": 9
}
},
"definition": {
"chart": {
"type": "horizontal_bar",
"stacked": true,
"chart_title": "Claude model usage (requests)"
},
"query": {
"filters": [
{
"field": "ai_request_model",
"operator": "not_empty"
},
{
"field": "ai_request_model",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"ai_request_count"
],
"datasource": "llm_usage",
"dimensions": [
"ai_request_model"
]
}
}
},
{
"id": "702f6291-e02d-47eb-b38b-85438c643712",
"type": "chart",
"layout": {
"size": {
"cols": 3,
"rows": 2
},
"position": {
"col": 3,
"row": 9
}
},
"definition": {
"chart": {
"type": "vertical_bar",
"stacked": true,
"chart_title": "Claude model usage count (tokens)"
},
"query": {
"filters": [
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"total_tokens"
],
"datasource": "llm_usage",
"dimensions": [
"ai_request_model"
]
}
}
},
{
"id": "2242b427-2999-4cfe-b17e-9e4ca61be362",
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 0,
"row": 11
}
},
"definition": {
"chart": {
"type": "vertical_bar",
"stacked": true,
"chart_title": "AI security report"
},
"query": {
"filters": [
{
"field": "status_code_grouped",
"value": [
"4XX"
],
"operator": "in"
},
{
"field": "ai_provider",
"operator": "not_empty"
},
{
"field": "ai_provider",
"value": [
"UNSPECIFIED"
],
"operator": "not_in"
}
],
"metrics": [
"ai_request_count"
],
"datasource": "llm_usage",
"dimensions": [
"route",
"status_code"
]
}
}
},
{
"id": "df1b0486-2c04-430e-a8ff-7e26c7821bf7",
"type": "chart",
"layout": {
"size": {
"cols": 3,
"rows": 2
},
"position": {
"col": 2,
"row": 11
}
},
"definition": {
"chart": {
"type": "timeseries_bar",
"stacked": true,
"chart_title": "Monthly spend trends"
},
"query": {
"limit": 10,
"filters": [],
"metrics": [
"cost"
],
"datasource": "llm_usage",
"dimensions": [
"ai_request_model",
"time"
],
"time_range": {
"type": "relative",
"time_range": "30d"
},
"granularity": "daily"
}
}
}
],
"template_id": "AI_GATEWAY",
"preset_filters": [
{
"field": "gateway_service",
"value": [],
"operator": "in"
}
]
},
"labels": {
"recipe": "claude-code-sso-recipe"
}
}
EOF
DASHBOARD_ID=$(kongctl api post /v2/dashboards \
-f claude-code-usage-dashboard.json \
--pat "${KONNECT_TOKEN}" -o json --jq '.id' -r)
rm -f claude-code-usage-dashboard.json
echo "Created Claude Code Usage dashboard (id: ${DASHBOARD_ID}). Open it in Konnect at Observability → Custom dashboards → 'Claude Code Usage'."
fi