Key points
- These surfaces run as the caller. The assistant sees exactly what that caller could fetch through the API, which is why two people get different answers to the same question.
- Feature flags decide which capability families exist; AAA still decides which resources within them are reachable.
- Mutating MCP tools stay hidden unless assistant action execution is enabled, and confirmed mutations still pass every existing check.
- A conversation belonging to another subject answers 404, not 403 — the API does not confirm it exists.
- An empty body on
/v1/mcpis treated astools/list, which makes the endpoint checkable by hand. - JSON-RPC errors come back as a 200 with an
errormember, not as an HTTP error. - An evaluation is advice; enforcement lives in governance levels, guardrails, and approvals.
Operations
GET/v1/assistant/configAuthenticated
Reads the assistant capability configuration for this install.
Notes
Feature flags come from setting/system/assistant.yaml, so they are GitOps-owned rather than set through this API.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/config" | jqResponses
Assistant feature flags.
{
"enabled": true,
"features": {
"action_execution": false,
"page_context": true
}
}Side effects
- None.
Proven by
services/nopsai/assistant_orchestration_test.goservices/nopsai/assistant_routes.godoc/assistant-capabilities.md
GET/v1/assistant/modelsAuthenticated
Lists the model profiles the caller may drive the assistant with.
Notes
The list is per caller, so two people can legitimately see different models.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/models" | jqResponses
Model profiles available to the caller.
[
{ "name": "reasoning-large", "provider": "anthropic", "is_default": true }
]Side effects
- None.
Proven by
services/nopsai/assistant_llm_test.goservices/nopsai/assistant_routes.go
GET/v1/assistant/conversationsAuthenticated
Lists the caller’s conversations.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations" | jqResponses
The caller’s conversations.
[{
"id": "d7a1b3c4-9e21-4f88-b0c2-5a7f1e3d9b60",
"title": "Why did release-service fail?",
"created_at": "2026-08-19T12:02:11Z",
"updated_at": "2026-08-19T12:06:44Z"
}]When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | The query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/assistant_orchestration_test.goservices/nopsai/assistant_routes.go
POST/v1/assistant/conversationsAuthenticated
Starts a conversation.
Call it
curl -sX POST "$NOPSAI_URL/v1/assistant/conversations" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Why did release-service fail?"}' | jq -r .idResponses
Conversation created.
{
"id": "d7a1b3c4-9e21-4f88-b0c2-5a7f1e3d9b60",
"title": "Why did release-service fail?",
"created_at": "2026-08-19T12:02:11Z",
"updated_at": "2026-08-19T12:06:44Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The body is malformed. | Send valid JSON; the title is optional. |
| 500 | The conversation could not be created. | Retry. |
Side effects
- Creates a conversation owned by the caller.
Proven by
services/nopsai/assistant_orchestration_test.goservices/nopsai/assistant_routes.go
GET/v1/assistant/conversations/{id}Authenticated
Reads one conversation with its messages.
Notes
Another subject’s conversation answers 404 rather than 403, so the API does not confirm it exists.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | uuid | Required | Conversation id. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations/$CONVERSATION_ID" | jqResponses
The conversation.
{
"id": "d7a1b3c4-9e21-4f88-b0c2-5a7f1e3d9b60",
"title": "Why did release-service fail?",
"created_at": "2026-08-19T12:02:11Z",
"updated_at": "2026-08-19T12:06:44Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The id is malformed. | Use an id from the list. |
| 404 | No conversation with that id for this caller. | A conversation belonging to someone else is a 404, not a 403. |
| 500 | The conversation could not be loaded. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/assistant_orchestration_test.goservices/nopsai/assistant_routes.go
POST/v1/assistant/conversations/{id}/messagesAuthenticated
Sends a message and gets the assistant’s reply.
Notes
The assistant is not a privileged reader. It sees exactly what the caller could fetch through the API, which is why two people get different answers to the same question.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | uuid | Required | Conversation id. |
Call it
curl -sX POST "$NOPSAI_URL/v1/assistant/conversations/$CONVERSATION_ID/messages" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"Why did the last release-service run fail?"}' | jqResponses
The reply.
{
"role": "assistant",
"content": "The last run failed at the licences task...",
"created_at": "2026-08-19T12:06:44Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An empty message or malformed body. | Send message. |
| 401 | The caller identity could not be resolved. | The assistant runs as the caller and refuses to run as nobody. |
| 404 | No conversation with that id for this caller. | Confirm the id. |
| 500 | The assistant call failed. | Retry; check the model profile is working. |
Side effects
- Stores the message and the reply on the conversation.
- Reads product data as the caller, so the answer is bounded by the caller’s own access.
- Records AI usage for the call.
Proven by
services/nopsai/assistant_orchestration_test.goservices/nopsai/assistant_page_context_test.goservices/nopsai/assistant_routes.godoc/assistant-capabilities.md
POST/v1/assistant/conversations/{id}/summarize-memoryAuthenticated
Compacts a long conversation into a summary the assistant keeps using.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | uuid | Required | Conversation id. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations/$CONVERSATION_ID/summarize-memory" | jqResponses
The stored summary.
{
"summary": "Investigating repeated licence-check failures on release-service."
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The id is malformed or the conversation is empty. | There has to be something to summarise. |
| 404 | No conversation with that id for this caller. | Confirm the id. |
| 500 | Summarisation failed. | Retry; it makes a model call. |
Side effects
- Makes a model call and records AI usage.
- Replaces older turns in the prompt with the summary.
Proven by
services/nopsai/assistant_orchestration_test.goservices/nopsai/assistant_routes.go
DELETE/v1/assistant/conversations/{id}Authenticated
Deletes a conversation.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | uuid | Required | Conversation to delete. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations/$CONVERSATION_ID" -w "%{http_code}\n"Responses
Conversation deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The id is malformed. | Use an id from the list. |
| 404 | No conversation with that id for this caller. | It may already be deleted. |
| 500 | The delete failed. | Retry. |
Side effects
- Removes the conversation and its messages. AI usage records remain for accounting.
Proven by
services/nopsai/assistant_orchestration_test.goservices/nopsai/assistant_routes.go
POST/v1/analysis/evaluateAuthenticated
Runs an AI evaluation over a pipeline analysis.
Notes
An evaluation is advice. Enforcement comes from governance level, guardrail knowledge, and approvals, which are separate mechanisms.
Call it
curl -sX POST "$NOPSAI_URL/v1/analysis/evaluate" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
--data @evaluation.json | jqResponses
The evaluation.
{
"summary": "Two steps have no failure handling.",
"findings": [{ "severity": "medium", "message": "announce has no timeout" }]
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The payload is malformed, or AI evaluation is disabled for this install. | The route refuses when the feature is off rather than silently returning nothing. |
| 502 | The model call failed. | Test the model profile. |
Side effects
- Makes a model call and records AI usage. It does not run the pipeline or consume runner capacity.
Proven by
services/nopsai/analysis_evaluation_handlers_test.goservices/nopsai/analysis_evaluation_handlers.go
POST/v1/analysis/teamAuthenticated
Runs the deterministic delivery analysis for a team and returns ranked findings, category scores, and the recommended next tool call.
Notes
Findings and scores are deterministic. Evidence the caller cannot read is reported in limitations and lowers ok to false rather than scoring as healthy, and with no readable evidence no health score is produced at all.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subject_id | query | string | Optional | Team id, team path, or slug, in the JSON body or as a query parameter. Defaults to the only visible team when the caller can see exactly one. |
Call it
curl -sX POST "$NOPSAI_URL/v1/analysis/team" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject_id":"platform","days":14}' | jqResponses
The analysis.
{
"analysis": "team",
"health_score": 60,
"findings": [{ "severity": "critical", "category": "reliability", "title": "45% of runs failed in the last 30 days" }],
"next_actions": [{ "tool": "nopsai.analyze_pipeline", "args": { "pipeline": "platform/deploy-api" } }]
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The subject type is not team or pipeline, or a pipeline was requested without subject_id. | Pass the subject id as path/name for a pipeline. |
| 401 | The request is unauthenticated. | Send a bearer token. |
Side effects
- Read-only. It makes no model call, applies nothing, and consumes no runner capacity.
Proven by
services/nopsai/hosted_mcp_analysis_test.goservices/nopsai/hosted_mcp_analysis_definition_test.goservices/nopsai/analysis_subject_handlers.goservices/nopsai/hosted_mcp_analysis.go
POST/v1/analysis/pipelineAuthenticated
Runs the deterministic analysis for one pipeline over its runs, step timings, spend, and stored definition.
Notes
Definition findings come from the stored YAML and match the rules the Analysis modal applies, so chat and modal agree. A definition that cannot be read is a limitation, not a clean result.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subject_id | query | string | Required | Pipeline id as path/name, in the JSON body or as a query parameter. |
Call it
curl -sX POST "$NOPSAI_URL/v1/analysis/pipeline" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject_id":"platform/deploy-api"}' | jqResponses
The analysis.
{
"analysis": "pipeline",
"health_score": 47,
"findings": [{ "severity": "critical", "category": "security", "title": "1 line embeds a secret-like literal" }]
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | subject_id is missing. | Pass the pipeline as path/name. |
| 401 | The request is unauthenticated. | Send a bearer token. |
Side effects
- Read-only. It makes no model call and applies nothing.
Proven by
services/nopsai/hosted_mcp_analysis_definition_test.goservices/nopsai/analysis_subject_handlers.goservices/nopsai/hosted_mcp_analysis_definition.go
POST/v1/analysis/runAuthenticated
Runs the deterministic analysis for one pipeline run: the likely failure domain, the first failure point, and what changed since the last successful run.
Notes
The domain classification is deterministic and matches the Analysis modal. Run logs are read through the same permission checks as the run itself, so a caller without log access loses that signal and keeps the rest.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
subject_id | query | string | Required | The run id, in the JSON body or as a query parameter. |
Call it
curl -sX POST "$NOPSAI_URL/v1/analysis/run" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject_id":"e3850cec-550f-456a-bec8-e67777d71d24"}' | jqResponses
The analysis.
{
"analysis": "run",
"primary_diagnosis": { "domain": "Application tests", "confidence": 0.82 },
"findings": [{ "severity": "high", "title": "First failure point: test / unit" }]
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | subject_id is missing. | Pass the run id. |
| 401 | The request is unauthenticated. | Send a bearer token. |
Side effects
- Read-only. It makes no model call and reruns nothing.
Proven by
services/nopsai/hosted_mcp_analysis_run_test.goservices/nopsai/hosted_mcp_analysis_run.go
POST/v1/mcpAuthenticated
The hosted MCP endpoint: JSON-RPC access to NopsAI as a set of tools.
Notes
An empty body is treated as tools/list, which makes it easy to check the endpoint by hand. Errors inside a JSON-RPC result are a 200 with an error member, not an HTTP error.
Call it
curl -sX POST "$NOPSAI_URL/v1/mcp" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | jqResponses
A JSON-RPC result.
{
"jsonrpc": "2.0",
"id": 2,
"result": { "tools": [{ "name": "list_runs" }] }
}Accepted for a notification-style request that expects no result.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 401 | No usable bearer token. | The hosted endpoint runs as the caller and refuses anonymous access. |
Side effects
- Tool calls perform real product operations as the caller, with the same AAA and audit as the REST routes they wrap.
- Mutating tools stay hidden unless assistant action execution is enabled, and confirmed mutations still pass every existing check.
Proven by
services/nopsai/hosted_mcp_tools_test.goservices/nopsai/hosted_mcp_api_bridge.godoc/mcp-feature-coverage.md
How it works
The security property that matters here is that none of these surfaces is a privileged reader. They are wrappers over the same authenticated API, so an assistant answer cannot reveal something the person asking could not have fetched themselves.
Conversations are per subject with no cross-subject read, which is why a missing conversation and someone else’s conversation are indistinguishable from the outside.
Every model-backed route here records AI usage, so assistant and evaluation spend show up in the same accounting as pipeline runs.
Implementation evidence
services/nopsai/assistant_routes.goAssistant configuration, conversations, and messages.
services/nopsai/hosted_mcp_api_bridge.goHosted MCP tool bridge and its authorization boundary.
services/nopsai/analysis_evaluation_handlers.goAnalysis evaluation handler and feature gate.

