What you will do
- A scope is the namespace a run resolves variables and secrets in, and it is chosen when the run starts. Variables and scopes has the resolution rules; Credentials and secrets store covers the platform credential registry.
PUT /v1/variables/{name}?scope=<scope>andPUT /v1/secrets/{name}?scope=<scope>both take{"value": "..."}.- Listing secrets returns names and metadata only. Reading a value is a separate route guarded by its own action,
secret.read_value. - A pipeline declares what it needs:
variablesat the top level, andsecretson the step that consumes them. - A bare name resolves in the run scope;
scope/path:NAMEresolves in another scope and is still injected under the bare name. - A declared variable that cannot be resolved fails the run before execution instead of starting with a missing value.
Before you start
- Pipeline
- The pipeline from the previous page, saved and runnable
- Scope
- The scope you ran it in, for example
platform/production - Token
- A token allowed to write variables and secrets in that scope
Steps
- 01
Store a variable
Variables are plain configuration. They are readable back through the API, which makes them the wrong place for credentials.
Create a scoped variablebash curl -sX PUT "http://localhost:8080/v1/variables/RELEASE_CHANNEL?scope=platform/production" \ -H "Authorization: Bearer $NOPSAI_TOKEN" \ -H "Content-Type: application/json" \ -d '{"value":"stable"}'Verify- curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "http://localhost:8080/v1/variables?scope=platform/production" | jq
- 02
Store a secret
Secrets are encrypted with the master key before storage. The value can be read back later, but only by a caller granted
secret.read_valueon that secret.Important- Losing
NOPSAI_MASTER_KEYmakes every stored secret unreadable. It belongs in the same place as your other break-glass material.
Create a scoped secretbash RELEASE_TOKEN=$(openssl rand -hex 12) curl -sX PUT "http://localhost:8080/v1/secrets/RELEASE_TOKEN?scope=platform/production" \ -H "Authorization: Bearer $NOPSAI_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"value\":\"$RELEASE_TOKEN\"}" - Losing
- 03
Declare them in the pipeline
The pipeline states what it requires. The variable is declared once at the top level; the secret is declared on the step that needs it, so it is not injected into steps that do not.
Extend first-pipeline.yamlyaml name: first-pipeline container_image: alpine:3.20 variables: - RELEASE_CHANNEL steps: - name: prepare script: | echo "1.0.$(date +%s)" > /nopsai/outputs/BUILD_TAG echo "channel is $RELEASE_CHANNEL" outputs: - name: BUILD_TAG - name: publish depends_on: [prepare] secrets: - RELEASE_TOKEN variables: BUILD_TAG: $steps.prepare.outputs.BUILD_TAG script: | echo "publishing $BUILD_TAG to $RELEASE_CHANNEL" echo "token length is ${#RELEASE_TOKEN}" - 04
Run it and check redaction
Run in the same scope, then read the logs and confirm the secret value never appears in clear text.
Run and inspectbash curl -sX POST http://localhost:8080/v1/run/first-pipeline \ -H "Authorization: Bearer $NOPSAI_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"scope":"platform/production"}' | jq -r .run_idVerify- The logs show the channel and the token length, never the token itself.
How it works
Names must match ^[A-Za-z0-9_.-]+$, and the same runtime name may not resolve from two different scopes in one place — that is a conflict, not a precedence rule.
Cross-scope use is explicit: platform/shared:ARTIFACT_BUCKET resolves in platform/shared and is injected as ARTIFACT_BUCKET. It never happens implicitly.
To keep a secret in a configuration repository instead of the database, encrypt it first through POST /v1/secrets/encrypt; the envelope is safe to commit and resolves at run time.
Step variables apply to every task in a step; task variables are applied afterwards and win.
Scope is not team path. Scope decides which values resolve; team path decides who owns the resource and who is notified.
Limits
- A secret value is recoverable by anyone granted
secret.read_valueon it. Grant that action deliberately: it is the difference between storing a credential and publishing it.
Implementation evidence
services/nopsai/secrets_variables_handlers.goScope query parameter, request body, and encryption on write.
pkg/models/runtime_refs.goScoped reference parsing and name validation.

