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

Pipelines and steps API

Reading, validating, saving, and deleting pipeline and reusable step definitions.

ReferenceDeveloperAutomation author

Key points

  • Both write routes take the YAML document as the request body. There is no JSON wrapper, and sending one produces a parse error that reads like a schema error.
  • Validation always answers 200: a rejected document is valid: false with issues carrying a path, a line, and a code.
  • Pipeline and step identifiers are catch-all path segments, so a team-prefixed name with slashes is passed through as-is.
  • A 403 rather than a 404 tells you the definition exists and access is the problem.
  • Editing a GitOps-managed definition creates a database override, which then shows as drift until it is pushed or discarded.
  • Deleting a pipeline keeps its runs; deleting a reusable step breaks the pipelines that include it, and nothing stops you.

Operations

GET/v1/pipelinesAuthorized

Lists pipelines the caller can see.

Notes

An empty list can mean "no pipelines" or "none you may see". Compare with /v1/auth/me before assuming the former.

Parameters

NameInTypeRequiredDescription
include_sourcequerybooleanOptionalInclude where each pipeline came from, which distinguishes a GitOps-managed definition from a database one.Default: false

Call it

List pipelinesapi-pipelines request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/pipelines?include_source=true" | jq
Result

One entry per pipeline the caller may see, already filtered by AAA.

Responses

200application/json

Pipelines ordered by path then name. The list is filtered per caller, so two callers can legitimately see different rows.

[
  {
    "id": "platform/release-service",
    "source": "config_repo",
    "version": "latest",
    "updated_at": "2026-08-19T09:58:12Z"
  }
]

When it fails

StatusCauseWhat to do
500The pipeline query failed.Platform fault; check database reachability.
503Authorization is unavailable, so the list cannot be filtered safely.Check AAA. The platform refuses to answer rather than return an unfiltered list.

Side effects

  • None.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
GET/v1/pipelines/{pipelineName...}Authorized

Reads one pipeline definition.

Parameters

NameInTypeRequiredDescription
pipelineNamepathstringRequiredPipeline identifier. It is a catch-all segment, so a team-prefixed name with slashes is passed as-is.

Call it

Read a pipeline definitionapi-pipelines request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/pipelines/platform/release-service" | jq -r .definition
Result

The stored YAML definition, plus the metadata describing where it came from.

Responses

200application/json

The definition and its provenance.

{
  "id": "platform/release-service",
  "source": "config_repo",
  "version": "latest",
  "definition": "name: release-service\ncontainer_image: alpine:3.20\n..."
}

When it fails

StatusCauseWhat to do
400The name is empty or malformed.Use the identifier from the list route.
403The caller may not read this pipeline.A 403 rather than a 404 means the pipeline exists and access is the problem.
404No pipeline with that identifier.Check the team prefix: release-service and platform/release-service are different pipelines.
502A GitOps-backed definition could not be fetched from its repository.Check the configuration repository connection and sync status.
503Authorization is unavailable.Check AAA.
500The definition could not be read.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
PUT/v1/pipelines/{pipelineName...}Authorized

Creates or updates a pipeline.

Notes

The request body is the YAML document, not {"definition": "..."}. Sending JSON gets a parse failure that looks like a schema error.

Parameters

NameInTypeRequiredDescription
pipelineNamepathstringRequiredIdentifier to store the definition under.

Call it

Save a pipelineapi-pipelines request
curl -sX PUT "$NOPSAI_URL/v1/pipelines/platform/release-service" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/yaml" \
  --data-binary @release-service.yaml
Result

201 with the stored record. The body is the YAML document itself — there is no JSON wrapper.

Replace before running
  • release-service.yaml is the pipeline manifest.

Responses

201application/json

Stored. The same status is returned for a create and an update.

{
  "id": "platform/release-service",
  "source": "database",
  "version": "latest",
  "updated_at": "2026-08-19T10:22:31Z"
}

When it fails

StatusCauseWhat to do
400The YAML is malformed, or the pipeline fails validation.Run POST /v1/pipelines/validate first: it returns every issue with a path and a line instead of the first failure.
500The definition could not be persisted.Retry; nothing was stored.

Side effects

  • Editing a GitOps-managed pipeline creates a database override, which then shows as drift against Git until it is pushed or discarded.
  • Writes an audit record.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
DELETE/v1/pipelines/{pipelineName...}Authorized

Deletes a pipeline.

Parameters

NameInTypeRequiredDescription
pipelineNamepathstringRequiredIdentifier of the pipeline to delete.

Call it

Delete a pipelineapi-pipelines request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/pipelines/platform/release-service" -w "%{http_code}\n"
Result

204. Existing run records are not deleted with it.

Responses

204

Deleted.

When it fails

StatusCauseWhat to do
400The name is empty or malformed.Use the identifier from the list route.
500The delete could not be persisted.Retry.

Side effects

  • Removes the definition. Runs it already produced stay, so history survives the pipeline.
  • A GitOps-managed pipeline reappears on the next sync unless it is removed from the repository too.
  • Writes an audit record.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
POST/v1/pipelines/validateAuthenticated

Validates pipeline YAML without saving it.

Notes

Always check valid rather than the status code. A rejected pipeline returns 200 with valid: false.

Call it

Validate a definitionapi-pipelines request
curl -sX POST "$NOPSAI_URL/v1/pipelines/validate" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/yaml" \
  --data-binary @release-service.yaml | jq
Result

valid: true with empty error and warning lists, or the issues with their paths and line numbers.

Replace before running
  • A JSON body works too, with the document under yaml or content.

Responses

200application/json

The document is valid.

{
  "valid": true,
  "errors": [],
  "warnings": []
}
200application/json

The document is not valid. Validation failures are a body, not an HTTP error — the request itself succeeded.

{
  "valid": false,
  "errors": [
    {
      "message": "step \"package\" consumes an output of \"build\" without a dependency path",
      "path": "steps[3].variables.BUILD_TAG",
      "line": 34,
      "code": "missing_dependency_path"
    }
  ],
  "warnings": []
}

When it fails

StatusCauseWhat to do
400The request payload could not be read at all — invalid JSON when a JSON content type was declared.Send YAML with a YAML content type, or JSON with the document under yaml or content.

Side effects

  • None. Validation never stores anything.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/validation_handlers.go
  • services/nopsai/validation_contract.go
GET/v1/stepsAuthorized

Lists reusable step definitions.

Parameters

NameInTypeRequiredDescription
include_sourcequerybooleanOptionalInclude where each step definition came from.Default: false

Call it

List reusable stepsapi-pipelines request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/steps" | jq
Result

The step definitions a pipeline can pull in with include: step:<identifier>.

Responses

200application/json

Reusable steps the caller may see.

[
  {
    "id": "platform/shared/checkout",
    "source": "config_repo",
    "updated_at": "2026-08-18T16:40:02Z"
  }
]

When it fails

StatusCauseWhat to do
500The step query failed.Platform fault.
503Authorization is unavailable.Check AAA; the list is not returned unfiltered.

Side effects

  • None.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
GET/v1/steps/{stepPath...}Authorized

Reads one reusable step.

Parameters

NameInTypeRequiredDescription
stepPathpathstringRequiredStep identifier, including its team prefix.

Call it

Read a reusable stepapi-pipelines request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/steps/platform/shared/checkout" | jq -r .definition
Result

The stored step definition, which is what include: step:platform/shared/checkout expands to.

Responses

200application/json

The step definition and its provenance.

{
  "id": "platform/shared/checkout",
  "source": "config_repo",
  "definition": "name: checkout\nscript: |\n  git clone ...\n"
}

When it fails

StatusCauseWhat to do
404No reusable step with that identifier.List the steps: an include: that names a missing step fails pipeline validation with the same identifier.
403The caller may not read this step.Check ownership of the team that holds it.

Side effects

  • None.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
PUT/v1/steps/{stepName...}Authorized

Creates or updates a reusable step.

Notes

A shared step is shared blast radius: it takes effect for every pipeline that includes it, without those pipelines changing.

Parameters

NameInTypeRequiredDescription
stepNamepathstringRequiredIdentifier to store the step under.

Call it

Save a reusable stepapi-pipelines request
curl -sX PUT "$NOPSAI_URL/v1/steps/platform/shared/checkout" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/yaml" \
  --data-binary @checkout.yaml
Result

201 with the stored record.

Replace before running
  • checkout.yaml is a step definition, not a whole pipeline.

Responses

201application/json

Stored, for both create and update.

{
  "id": "platform/shared/checkout",
  "source": "database",
  "updated_at": "2026-08-19T10:31:44Z"
}

When it fails

StatusCauseWhat to do
400The YAML is malformed or the step fails validation.Validate first: a reusable step has its own rules and rejects pipeline-only directives.
500The definition could not be persisted.Retry.

Side effects

  • Every pipeline that includes this step picks up the change on its next run.
  • Writes an audit record.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
DELETE/v1/steps/{stepName...}Authorized

Deletes a reusable step.

Notes

Nothing blocks deleting a step other pipelines include. Check usage before removing a shared definition.

Parameters

NameInTypeRequiredDescription
stepNamepathstringRequiredIdentifier of the step to delete.

Call it

Delete a reusable stepapi-pipelines request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/steps/platform/shared/checkout" -w "%{http_code}\n"
Result

204.

Responses

204

Deleted.

When it fails

StatusCauseWhat to do
400The name is empty or malformed.Use the identifier from the list route.
500The delete could not be persisted.Retry.

Side effects

  • Pipelines that still include the step fail validation on their next save or run.
  • Writes an audit record.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/pipeline_handlers.go
POST/v1/steps/validateAuthenticated

Validates a reusable step definition.

Notes

A reusable step is validated against step rules, so a document that passes here can still be rejected as a pipeline, and the reverse.

Call it

Validate a step definitionapi-pipelines request
curl -sX POST "$NOPSAI_URL/v1/steps/validate" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/yaml" \
  --data-binary @checkout.yaml | jq
Result

The same valid, errors, warnings shape the pipeline validator returns.

Responses

200application/json

Validation ran. Read valid rather than the status code.

{
  "valid": true,
  "errors": [],
  "warnings": []
}

When it fails

StatusCauseWhat to do
400The payload could not be read.Send YAML with a YAML content type, or JSON with the document under yaml or content.

Side effects

  • None.

Proven by

  • services/nopsai/pipeline_handlers_test.go
  • services/nopsai/validation_handlers.go

How it works

Validate before saving, always. Save rejects on the first failure with a 400, while validation returns every issue at once with the line that produced it — the difference between one round trip and five.

A list route returning fewer rows than expected is usually authorization rather than absence: the result is filtered per caller. If authorization itself is unavailable, the platform answers 503 rather than returning an unfiltered list.

Reusable steps and pipelines are validated against different rule sets. A document that validates as a step can be rejected as a pipeline, and the reverse, so use the matching validation route.

Implementation evidence

  • services/nopsai/pipeline_handlers.go

    Pipeline and reusable step handlers.

  • services/nopsai/validation_contract.go

    The valid/errors/warnings response shape.