Platform

Platform overviewArchitectureWorkflow orchestrationGitOps configurationGovernance and AAAAI and MCPKnowledge and contextRuntime and executionEvidence and monitoring

Use cases

All use casesProduction incidentRelease preparationHotfix to productionSecurity scan triage
Why NopsAIIntegrationsSecurity

Resources

All resourcesAI agent governanceMCP governanceMCP securitySelf-hosted platforms
PricingGitHub

Company

How a run worksAboutContactBook a demo

Assistant and AI surfaces API

Conversations, pipeline evaluation, and the hosted MCP endpoint — all running as the calling subject.

ReferenceDeveloperSecurityOperator

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/mcp is treated as tools/list, which makes the endpoint checkable by hand.
  • JSON-RPC errors come back as a 200 with an error member, 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

Read assistant configurationapi-assistant request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/config" | jq
Result

Which capability families are enabled. Runtime and admin execution tools are hidden unless action execution is on.

Responses

200application/json

Assistant feature flags.

{
  "enabled": true,
  "features": {
    "action_execution": false,
    "page_context": true
  }
}

Side effects

  • None.

Proven by

  • services/nopsai/assistant_orchestration_test.go
  • services/nopsai/assistant_routes.go
  • doc/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

List assistant modelsapi-assistant request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/models" | jq
Result

Profiles this caller is allowed to use — not every profile the platform has.

Responses

200application/json

Model profiles available to the caller.

[
  { "name": "reasoning-large", "provider": "anthropic", "is_default": true }
]

Side effects

  • None.

Proven by

  • services/nopsai/assistant_llm_test.go
  • services/nopsai/assistant_routes.go
GET/v1/assistant/conversationsAuthenticated

Lists the caller’s conversations.

Call it

List your conversationsapi-assistant request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations" | jq
Result

Only the caller’s own conversations. There is no route that lists another subject’s.

Responses

200application/json

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

StatusCauseWhat to do
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/assistant_orchestration_test.go
  • services/nopsai/assistant_routes.go
POST/v1/assistant/conversationsAuthenticated

Starts a conversation.

Call it

Start a conversationapi-assistant request
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 .id
Result

The conversation id to post messages into.

Responses

201application/json

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

StatusCauseWhat to do
400The body is malformed.Send valid JSON; the title is optional.
500The conversation could not be created.Retry.

Side effects

  • Creates a conversation owned by the caller.

Proven by

  • services/nopsai/assistant_orchestration_test.go
  • services/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

NameInTypeRequiredDescription
idpathuuidRequiredConversation id.

Call it

Read a conversationapi-assistant request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations/$CONVERSATION_ID" | jq
Result

The conversation and its message history.

Responses

200application/json

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

StatusCauseWhat to do
400The id is malformed.Use an id from the list.
404No conversation with that id for this caller.A conversation belonging to someone else is a 404, not a 403.
500The conversation could not be loaded.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/assistant_orchestration_test.go
  • services/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

NameInTypeRequiredDescription
idpathuuidRequiredConversation id.

Call it

Ask a questionapi-assistant request
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?"}' | jq
Result

The assistant’s reply, produced by reading only what this caller is allowed to see.

Responses

201application/json

The reply.

{
  "role": "assistant",
  "content": "The last run failed at the licences task...",
  "created_at": "2026-08-19T12:06:44Z"
}

When it fails

StatusCauseWhat to do
400An empty message or malformed body.Send message.
401The caller identity could not be resolved.The assistant runs as the caller and refuses to run as nobody.
404No conversation with that id for this caller.Confirm the id.
500The 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.go
  • services/nopsai/assistant_page_context_test.go
  • services/nopsai/assistant_routes.go
  • doc/assistant-capabilities.md
POST/v1/assistant/conversations/{id}/summarize-memoryAuthenticated

Compacts a long conversation into a summary the assistant keeps using.

Parameters

NameInTypeRequiredDescription
idpathuuidRequiredConversation id.

Call it

Summarise a long conversationapi-assistant request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations/$CONVERSATION_ID/summarize-memory" | jq
Result

A stored summary that replaces older turns in the prompt, keeping the conversation usable.

Responses

200application/json

The stored summary.

{
  "summary": "Investigating repeated licence-check failures on release-service."
}

When it fails

StatusCauseWhat to do
400The id is malformed or the conversation is empty.There has to be something to summarise.
404No conversation with that id for this caller.Confirm the id.
500Summarisation 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.go
  • services/nopsai/assistant_routes.go
DELETE/v1/assistant/conversations/{id}Authenticated

Deletes a conversation.

Parameters

NameInTypeRequiredDescription
idpathuuidRequiredConversation to delete.

Call it

Delete a conversationapi-assistant request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/assistant/conversations/$CONVERSATION_ID" -w "%{http_code}\n"
Result

204. The conversation and its messages go; recorded AI usage stays.

Responses

204

Conversation deleted.

When it fails

StatusCauseWhat to do
400The id is malformed.Use an id from the list.
404No conversation with that id for this caller.It may already be deleted.
500The delete failed.Retry.

Side effects

  • Removes the conversation and its messages. AI usage records remain for accounting.

Proven by

  • services/nopsai/assistant_orchestration_test.go
  • services/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

Evaluate a pipeline analysisapi-assistant request
curl -sX POST "$NOPSAI_URL/v1/analysis/evaluate" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  --data @evaluation.json | jq
Result

A structured evaluation the pipeline Health tab renders alongside the analysis.

Replace before running
  • evaluation.json is the analysis payload the Health tab submits.

Responses

200application/json

The evaluation.

{
  "summary": "Two steps have no failure handling.",
  "findings": [{ "severity": "medium", "message": "announce has no timeout" }]
}

When it fails

StatusCauseWhat to do
400The payload is malformed, or AI evaluation is disabled for this install.The route refuses when the feature is off rather than silently returning nothing.
502The 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.go
  • services/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

NameInTypeRequiredDescription
subject_idquerystringOptionalTeam 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

Analyse a team over the last two weeksapi-assistant request
curl -sX POST "$NOPSAI_URL/v1/analysis/team" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subject_id":"platform","days":14}' | jq
Result

Findings ordered by severity, category scores, a health score, and next_actions.

Replace before running
  • platform is a team path, team slug, or numeric team id.

Responses

200application/json

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

StatusCauseWhat to do
400The 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.
401The 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.go
  • services/nopsai/hosted_mcp_analysis_definition_test.go
  • services/nopsai/analysis_subject_handlers.go
  • services/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

NameInTypeRequiredDescription
subject_idquerystringRequiredPipeline id as path/name, in the JSON body or as a query parameter.

Call it

Analyse a pipelineapi-assistant request
curl -sX POST "$NOPSAI_URL/v1/analysis/pipeline" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subject_id":"platform/deploy-api"}' | jq
Result

Run findings and definition findings in one ranked list, with category scores and next_actions.

Replace before running
  • platform/deploy-api is the pipeline path and name.

Responses

200application/json

The analysis.

{
  "analysis": "pipeline",
  "health_score": 47,
  "findings": [{ "severity": "critical", "category": "security", "title": "1 line embeds a secret-like literal" }]
}

When it fails

StatusCauseWhat to do
400subject_id is missing.Pass the pipeline as path/name.
401The 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.go
  • services/nopsai/analysis_subject_handlers.go
  • services/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

NameInTypeRequiredDescription
subject_idquerystringRequiredThe run id, in the JSON body or as a query parameter.

Call it

Analyse a failed runapi-assistant request
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"}' | jq
Result

A primary diagnosis, ranked findings, and next_actions naming the logs, the pipeline, and the last successful run.

Replace before running
  • e3850cec-… is the run id.

Responses

200application/json

The analysis.

{
  "analysis": "run",
  "primary_diagnosis": { "domain": "Application tests", "confidence": 0.82 },
  "findings": [{ "severity": "high", "title": "First failure point: test / unit" }]
}

When it fails

StatusCauseWhat to do
400subject_id is missing.Pass the run id.
401The 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.go
  • services/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

List the tools this subject may callapi-assistant request
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"}' | jq
Result

The tool list, scoped to the caller. An empty body defaults to tools/list rather than a parse error.

Responses

200application/json

A JSON-RPC result.

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": { "tools": [{ "name": "list_runs" }] }
}
202

Accepted for a notification-style request that expects no result.

When it fails

StatusCauseWhat to do
401No 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.go
  • services/nopsai/hosted_mcp_api_bridge.go
  • doc/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.go

    Assistant configuration, conversations, and messages.

  • services/nopsai/hosted_mcp_api_bridge.go

    Hosted MCP tool bridge and its authorization boundary.

  • services/nopsai/analysis_evaluation_handlers.go

    Analysis evaluation handler and feature gate.