Key points
- Starting a run answers with JSON only when the request sends
Accept: application/json; otherwise it returns a plain-text confirmation. - Starting a run returns
run_id, while rerunning returnsrunId. The two routes do not share a response shape. - Poll
/statusfor a status, and/runs/{runID}withIf-None-Matchwhen you need detail — that route issues an ETag and answers 304 while nothing changed. - The run list filters on
limit,offset,teamId, andbranch. There is no pipeline filter, and passingteamIdchanges the response into a team-by-branch grouping. - Rerunning a run that is still in progress is refused with 409, and authorization is re-evaluated for whoever triggers the rerun.
- Rejecting an approval requires a comment; approving does not.
- Deliverables can be retried and cancelled per item without re-running the pipeline.
Operations
POST/v1/runAuthorized
Starts a run from an inline or referenced pipeline.
Notes
sensitive_variables names which of the supplied variables must be masked in logs. Passing a secret without naming it there puts it in the run record in clear text.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Nopsai-Scope | header | string | Optional | Run scope. Takes precedence over scope in the body. |
Accept | header | string | Optional | Send application/json to get a JSON body instead of a plain-text confirmation.Default: text/plain |
Call it
curl -sX POST "$NOPSAI_URL/v1/run" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data @run-request.json | jqResponses
The run was accepted and queued.
{
"run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
"trigger_event_id": ""
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | Invalid JSON, an unusable definition, or a variable override name outside ^[A-Za-z0-9_.-]+$. | The message names the offending variable names rather than just rejecting the request. |
| 403 | The caller may not use a resource the run touches — a model, an MCP profile, a scope, or the pipeline itself. | Authorization covers everything the run would reach, not just the pipeline. |
| 500 | The run could not be recorded or dispatched. | Retry; check the dispatcher has a registered runner. |
Side effects
- Creates a durable run record before any work starts.
- Submits the job to the dispatcher, which assigns it to a runner with matching scopes and free capacity.
- Writes an audit record naming the caller and every resource the run was authorized to use.
Proven by
services/nopsai/run_status_test.goservices/nopsai/aaa_integration_test.goservices/nopsai/run_handlers.goservices/nopsai/run_launcher.go
POST/v1/run/{pipelineName...}Authorized
Starts a run of a stored pipeline.
Notes
The scope decides which variables and secrets resolve. Running the right pipeline in the wrong scope is the most common cause of a run that fails before its first step.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
pipelineName | path | string | Required | Stored pipeline identifier, including its team prefix. |
X-Nopsai-Scope | header | string | Optional | Run scope. Takes precedence over scope in the body. |
Accept | header | string | Optional | Send application/json for a JSON response body.Default: text/plain |
Call it
curl -sX POST "$NOPSAI_URL/v1/run/platform/release-service" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"scope":"platform/production","variables":{"RELEASE_CHANNEL":"stable"}}' | jq -r .run_idResponses
Run queued.
{
"run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
"trigger_event_id": ""
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | Invalid payload, unknown pipeline, or invalid variable override names. | Confirm the identifier with GET /v1/pipelines. |
| 403 | The caller may not run this pipeline or use something it references. | Check /v1/access/effective-permissions for the pipeline and the scope. |
| 500 | The run could not be recorded or dispatched. | Retry. |
Side effects
- Creates a run record and submits it to the dispatcher.
- Variable overrides are recorded on the run, so a run is reproducible from its own record.
- Writes an audit record.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.go
GET/v1/runsAuthorized
Lists runs. Exposes only lightweight aggregate final_output_status metadata.
Notes
Passing teamId changes the response shape to a team-by-branch grouping. Code that parses both forms should branch on the parameter it sent.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | Optional | Page size. A value of zero or less becomes 50; anything above 1000 is capped at 1000.Default: 300 |
offset | query | integer | Optional | Rows to skip. Negative values are ignored.Default: 0 |
teamId | query | string | Optional | Restrict to one team. Changes the response into a team-by-branch grouping rather than a flat list. |
branch | query | string | Optional | Restrict to runs for one Git ref. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs?limit=20" | jq '.[] | {run_id, pipeline_name, status}'Responses
A flat list of run records, filtered per caller.
[
{
"run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
"pipeline_name": "release-service",
"pipeline_path": "platform",
"status": "success",
"git_ref": "refs/heads/main",
"started_at": "2026-08-19T10:44:02Z",
"finished_at": "2026-08-19T10:47:18Z",
"duration": "3m16s",
"is_complete": true
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | teamId is not a known team id or the root alias. | Read team ids from GET /v1/teams. |
| 503 | Authorization is unavailable, so runs cannot be filtered safely. | Check AAA. The platform refuses rather than returning runs the caller may not see. |
| 500 | The run query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.go
GET/v1/runs/{runID}Authorized
Full run detail including steps, tasks, and outputs.
Notes
Poll with the ETag. Polling this route without If-None-Match rebuilds the whole detail document every time.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run identifier. |
If-None-Match | header | string | Optional | ETag from a previous read. Matching returns 304 with no body, which is how the UI polls a live run cheaply. |
Call it
ETAG=$(curl -sD - -o run.json -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID" | awk '/^ETag:/ {print $2}' | tr -d "\r")
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $NOPSAI_TOKEN" -H "If-None-Match: $ETAG" "$NOPSAI_URL/v1/runs/$RUN_ID"Responses
The run, its steps and tasks, its child runs, AI usage, and its final outputs.
{
"run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
"pipeline_name": "release-service",
"status": "success",
"steps": [
{ "name": "build", "status": "success" },
{ "name": "production-gate", "status": "success" }
]
}Nothing changed since the ETag was issued.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 403 | The caller may not read this run. | Run visibility follows the owning team. |
| 404 | No run with that id. | Check the id: a deleted run is gone, not hidden. |
| 503 | Authorization is unavailable. | Check AAA. |
| 500 | Run detail could not be assembled. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.go
GET/v1/runs/{runID}/statusAuthorized
Compact run status for polling.
Notes
This is the route to poll in a script. It answers with one field and does not assemble the run graph.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run identifier. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/status" | jq -r .statusResponses
The current status and nothing else.
{"status":"running"}When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No run with that id. | Confirm the id from the run list. |
Side effects
- None.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_internal_handlers.go
GET/v1/runs/{runID}/logsAuthorized
Durable pipeline run logs.
Notes
Declared secrets, sensitive variable names, and outputs marked sensitive are masked before the response is written. A value a script printed itself cannot be masked, because the platform never knew it was one.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run identifier. |
after_id | query | integer | Optional | Return only records with a higher id, which is how a tail continues without re-reading.Default: 0 |
include_children | query | boolean | Optional | Include logs from child pipeline runs.Default: false |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/logs" \
| jq -r '.[] | "\(.timestamp) \(.step_name // "-") \(.line)"' | tail -40Responses
Log records, oldest first.
[
{
"id": 4211,
"timestamp": "2026-08-19T10:44:09Z",
"line": "building 1.0.1755600249",
"step_name": "build",
"stream": "stdout",
"level": "info"
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 403 | The caller may not read this run. | Log access follows run visibility. |
| 500 | The log query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_internal_handlers.gopkg/models/types.go
POST/v1/runs/{runID}/cancelAuthorized
Cancels an active run.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run to cancel. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/cancel" | jqResponses
Cancellation accepted.
{"status":"canceled"}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The id is malformed. | Use the run id from the list route. |
| 404 | No run with that id. | Confirm the id. |
| 405 | A method other than POST. | Use POST. |
| 500 | Cancellation could not be recorded. | Retry; the run keeps running until it succeeds. |
Side effects
- Signals the agent to stop and releases the runner capacity the run held.
- A pending approval on the run is closed out rather than left waiting.
- Writes an audit record.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.godoc/runtime-flows.md
POST/v1/runs/{runID}/rerunAuthorized
Starts a new run from the same definition and inputs.
Notes
A rerun repeats the definition, not the result. A pipeline whose definition changed since the original run reruns with the new definition.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | The run to repeat. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/rerun" | jq -r .runIdResponses
A new run was queued from the original definition, scope, and Git context.
{
"runId": "b71e0f22-9a3d-4c81-9d5c-1f0a5d7e2c44",
"triggerEventId": ""
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | The original run does not exist. | Confirm the id. |
| 409 | The original run is still in progress. | Wait for it to finish. Rerunning a live run is refused rather than queued. |
| 403 | The caller may not use a resource the rerun would touch. | Authorization is re-evaluated for the caller now, not inherited from whoever started the original run. |
| 405 | A method other than POST. | Use POST. |
| 500 | The rerun could not be created. | Retry. |
Side effects
- Creates a separate run record; the original is untouched.
- Writes an audit record naming the caller who triggered the rerun.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.go
DELETE/v1/runs/{runID}Authorized
Deletes a run record.
Notes
For bulk retention use the data cleanup routes, which preview before deleting. This route removes one record with no preview.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run to delete. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID" -w "%{http_code}\n"Responses
Deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The id is malformed. | Use a run id. |
| 404 | No run with that id. | It may already be deleted. |
| 500 | The delete could not be persisted. | Retry. |
Side effects
- Removes the run, its logs, and its generated outputs permanently.
- Writes an audit record.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.go
GET/v1/runs/{runID}/approvalsAuthorized
Lists approval checkpoints on a run.
Notes
expires_at is when the checkpoint times out. An expired approval produces a timed_out run, not a failed one.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run identifier. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/approvals" | jqResponses
Approval records for the run.
[
{
"run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
"step_name": "production-gate",
"task_name": "production-gate",
"approval_type": "production-release",
"assigned_teams": ["platform/sre"],
"allow_self_approval": false,
"status": "pending",
"requested_at": "2026-08-19T10:46:10Z",
"expires_at": "2026-08-20T10:46:10Z"
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 403 | The caller may not read this run. | Approval visibility follows run visibility. |
| 503 | Authorization is unavailable. | Check AAA. |
| 500 | The approval query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/approval_handlers_test.goservices/nopsai/approval_visibility_test.goservices/nopsai/approval_handlers.go
POST/v1/runs/{runID}/approvals/{approvalID}/approveAuthorized
Approves a checkpoint. Comment optional.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run holding the checkpoint. |
approvalID | path | string | Required | Checkpoint identifier from the approvals list. |
Call it
curl -sX POST "$NOPSAI_URL/v1/runs/$RUN_ID/approvals/$APPROVAL_ID/approve" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"comment":"Change 4821 signed off"}' | jqResponses
Decision recorded and the run released.
{
"step_name": "production-gate",
"status": "approved",
"decided_by_email": "[email protected]",
"decided_at": "2026-08-19T10:52:03Z",
"decision_comment": "Change 4821 signed off"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The body is malformed, or the checkpoint is no longer pending. | Re-read the approvals list: a decided or timed-out checkpoint cannot be decided again. |
| 403 | The caller is not in an assigned team, or is the requester and allow_self_approval is false. | Self-approval is a manifest decision, not an authorization override. |
| 404 | No such run or checkpoint. | Confirm both ids. |
Side effects
- Releases the paused run to continue.
- Records who decided, when, and with what comment, permanently on the run.
- Writes an audit record.
Proven by
services/nopsai/approval_handlers_test.goservices/nopsai/approval_handlers.goservices/nopsai/approval_schema.go
POST/v1/runs/{runID}/approvals/{approvalID}/rejectAuthorized
Rejects a checkpoint. Comment required.
Notes
Approval failures always fail closed. ignore_failure on the step does not apply to a rejected gate.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run holding the checkpoint. |
approvalID | path | string | Required | Checkpoint identifier from the approvals list. |
Call it
curl -sX POST "$NOPSAI_URL/v1/runs/$RUN_ID/approvals/$APPROVAL_ID/reject" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"comment":"Blocked: pending incident 2291"}' | jqResponses
Rejection recorded.
{
"step_name": "production-gate",
"status": "rejected",
"decided_by_email": "[email protected]",
"decided_at": "2026-08-19T10:53:41Z",
"decision_comment": "Blocked: pending incident 2291"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | No comment was supplied, or the checkpoint is no longer pending. | The message is comment is required when rejecting an approval. A rejection without a reason is not accepted. |
| 403 | The caller is not in an assigned team. | Only assigned teams decide. |
| 404 | No such run or checkpoint. | Confirm both ids. |
Side effects
- Fails the run closed at the checkpoint.
- Records the decision and its comment permanently.
- Writes an audit record.
Proven by
services/nopsai/approval_handlers_test.goservices/nopsai/approval_handlers.go
GET/v1/runs/{runID}/outputs/{outputID}/downloadAuthorized
Downloads generated final output content.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run that produced the deliverable. |
outputID | path | string | Required | Output identifier from run detail. |
Call it
curl -s -OJ -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/outputs/$OUTPUT_ID/download"Responses
The generated content: Markdown, JSON, HTML, PDF, or a spreadsheet, depending on the item type.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The ids are malformed. | Take both ids from run detail. |
| 403 | The caller may not read this run. | Generated content stays on authorized detail paths; run lists only expose an aggregate status. |
| 404 | No such run or output. | Confirm the output id. |
| 409 | The output has not been generated, or generation failed. | Check its status in run detail and retry generation before downloading. |
| 500 | The content could not be read. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/pipeline_final_output_contract_test.goservices/nopsai/pipeline_final_output_specs.godoc/final-output-rendering.md
POST/v1/runs/{runID}/outputs/{outputID}/retryAuthorized
Retries a failed final output.
Notes
Provider and network errors are not retried automatically, which is why this route exists: a renderer failing is not the work failing.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run that owns the deliverable. |
outputID | path | string | Required | Output identifier from run detail. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/outputs/$OUTPUT_ID/retry" | jqResponses
Retry accepted.
{"status":"pending"}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The ids are malformed. | Take both from run detail. |
| 404 | No such run or output. | Confirm the output id. |
| 409 | The output is already generating or already succeeded. | Cancel it first if you need to regenerate. |
| 500 | The retry could not be queued. | Retry. |
Side effects
- Re-runs generation for one item; the pipeline itself is not executed again.
- Writes an audit record.
Proven by
services/nopsai/pipeline_final_output_contract_test.goservices/nopsai/pipeline_final_output_specs.go
POST/v1/runs/{runID}/outputs/{outputID}/cancelAuthorized
Cancels in-progress final output generation.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runID | path | uuid | Required | Run that owns the deliverable. |
outputID | path | string | Required | Output identifier from run detail. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs/$RUN_ID/outputs/$OUTPUT_ID/cancel" | jqResponses
Cancellation accepted.
{"status":"canceled"}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The ids are malformed. | Take both from run detail. |
| 404 | No such run or output. | Confirm the output id. |
| 409 | The output is not generating. | Only in-progress generation can be cancelled. |
| 500 | The cancellation could not be recorded. | Retry. |
Side effects
- Stops generation for one item.
- Writes an audit record.
Proven by
services/nopsai/pipeline_final_output_contract_test.goservices/nopsai/pipeline_final_output_specs.go
GET/v1/runs-by-check/{checkRunID}Authorized
Resolves the run behind a GitHub check run.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
checkRunID | path | string | Required | GitHub check run id, as it appears in the check URL. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/runs-by-check/$CHECK_RUN_ID" | jq -r .run_idResponses
The run linked to that check run.
{
"run_id": "9c1f7a5e-2b44-4d2f-8f2a-2c9f0b6d4e11",
"pipeline_name": "release-service",
"status": "failure"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No run is linked to that check run id. | The check may predate the link, or belong to another install. |
Side effects
- None.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.go
DELETE/v1/repositories/{repoOwner}/{repoName}/branches/{branch...}Authorized
Deletes runs recorded for a deleted branch.
Notes
Housekeeping after a branch is merged and deleted. It is bulk and irreversible: there is no preview.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
repoOwner | path | string | Required | Repository owner. |
repoName | path | string | Required | Repository name. |
branch | path | string | Required | Branch name. A catch-all segment, so a branch with slashes is passed as-is. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" \
"$NOPSAI_URL/v1/repositories/acme/payments/branches/feature/new-checkout" -w "%{http_code}\n"Responses
Runs for the branch were deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The owner, repository, or branch is empty. | All three segments are required. |
| 500 | The delete could not be persisted. | Retry. |
Side effects
- Deletes every run recorded for that branch, with their logs and outputs.
- Writes an audit record.
Proven by
services/nopsai/run_status_test.goservices/nopsai/run_handlers.go
How it works
Every write here is authorized against more than the pipeline. Starting a run checks the caller against each resource the run would touch — the model, the MCP profiles, the scope — which is why a 403 can name something the caller did not think they were using.
Log masking covers what the platform knows to be secret: declared secrets, names listed in sensitive_variables, and outputs marked sensitive. A value a script prints itself cannot be masked, because nothing declared it.
The two bulk deletes on this page — deleting a run and clearing a branch — have no preview and no undo. Retention at scale belongs in the data cleanup routes, which preview first.
Implementation evidence
services/nopsai/run_handlers.goRun start, list, detail, cancel, rerun, and delete handlers.
services/nopsai/approval_handlers.goApproval listing and decision handling.
services/nopsai/run_internal_handlers.goStatus and log responses.

