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

Variables and secrets API

The three stores behind runtime configuration, and which action decides who can recover a stored secret.

ReferenceDeveloperSecurityAdministrator

Key points

  • A scope secret is not write-only. GET /v1/secrets/{secretName} decrypts and returns the value to a caller holding secret.read_value on that secret.
  • Listing secrets and reading a value are separate actions: secret.list_metadata returns names, secret.read_value returns the value.
  • Reading a secret value is audited, naming the caller and the secret.
  • The credential store is different: /v1/system/credentials returns metadata and version history, never a value.
  • Names and scopes come from the path and query; the request body carries only value.
  • Writing over a GitOps-managed value makes the database the owner, and the next sync reports drift rather than restoring Git.
  • A GitOps envelope from /v1/secrets/encrypt is bound to this install’s master key, which is what makes committing it safe and the key a restore dependency.

Operations

GET/v1/variablesAuthorized

Lists runtime variables in a scope.

Requires variable.list

Notes

Variable values are readable by design. Anything that should not be is a secret, not a variable.

Parameters

NameInTypeRequiredDescription
scopequerystringOptionalScope to list. Values are stored per scope, so the same name can exist in several.Default: the default scope
include_sourcequerybooleanOptionalInclude whether each value came from the database or a configuration repository.Default: false

Call it

List variables in a scopeapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/variables?scope=platform/production&include_source=true" | jq
Result

Variable names and values for that scope. Variables are configuration, not credentials, so values are listed.

Responses

200application/json

Variables in the requested scope.

[
  {
    "name": "RELEASE_CHANNEL",
    "value": "stable",
    "scope": "platform/production",
    "source": "database",
    "updated_at": "2026-08-19T09:12:44Z"
  }
]

When it fails

StatusCauseWhat to do
503Authorization is unavailable, so the list cannot be filtered.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
GET/v1/variables/scopesAuthorized

Lists the scopes that hold variables.

Notes

Useful before writing a value: a typo in a scope name creates a new scope silently rather than failing.

Call it

List variable scopesapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/variables/scopes" | jq
Result

Every scope the caller may see that holds at least one variable.

Responses

200application/json

Scopes holding variables.

[
  { "scope": "platform/production" },
  { "scope": "platform/shared" }
]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
GET/v1/variables/{variableName}Authorized

Reads one variable value.

Parameters

NameInTypeRequiredDescription
variableNamepathstringRequiredVariable name. Must match ^[A-Za-z0-9_.-]+$.
scopequerystringOptionalScope to read from.

Call it

Read a variableapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/variables/RELEASE_CHANNEL?scope=platform/production" | jq -r .value
Result

The stored value for that scope.

Responses

200application/json

The variable value.

{"value":"stable"}

When it fails

StatusCauseWhat to do
404No variable with that name in that scope.Check the scope: the same name in another scope is a different variable.
500The read failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
PUT/v1/variables/{variableName}Authorized

Creates or replaces a variable in a scope.

Notes

Writing over a GitOps-managed variable makes the database the owner. The next config sync reports drift rather than silently restoring Git.

Parameters

NameInTypeRequiredDescription
variableNamepathstringRequiredVariable name.
scopequerystringOptionalScope to write into. An unknown scope is created rather than rejected.

Call it

Set a variableapi-variables-and-secrets request
curl -sX PUT "$NOPSAI_URL/v1/variables/RELEASE_CHANNEL?scope=platform/production" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value":"stable"}'
Result

201. The body carries only value; the name and scope come from the path and query.

Responses

201application/json

Stored, for both create and replace.

{"name":"RELEASE_CHANNEL","scope":"platform/production","source":"database"}

When it fails

StatusCauseWhat to do
400The body is not valid JSON, or the name is unusable.Send {"value": "..."} and a name matching ^[A-Za-z0-9_.-]+$.
500The write failed.Retry.

Side effects

  • Marks the value as database-owned, which takes it out of GitOps management until the configuration repository is reconciled.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
DELETE/v1/variables/{variableName}Authorized

Deletes a variable from a scope.

Notes

Deleting is silent about whether anything was there. Read it first if you need to know.

Parameters

NameInTypeRequiredDescription
variableNamepathstringRequiredVariable to delete.
scopequerystringOptionalScope to delete from.

Call it

Delete a variableapi-variables-and-secrets request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/variables/RELEASE_CHANNEL?scope=platform/production" -w "%{http_code}\n"
Result

204, whether or not the variable existed.

Responses

204

Deleted.

When it fails

StatusCauseWhat to do
500The delete failed.Retry.

Side effects

  • Pipelines that declare the variable fail before execution until it resolves again.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
GET/v1/secretsAuthorized

Lists secret names and metadata in a scope.

Requires secret.list_metadata

Notes

Listing needs secret.list_metadata, which is a weaker action than reading a value. The two are granted separately on purpose.

Parameters

NameInTypeRequiredDescription
scopequerystringOptionalScope to list.

Call it

List secrets in a scopeapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/secrets?scope=platform/production" | jq
Result

Names and metadata only. This route never returns values, whatever the caller holds.

Responses

200application/json

Secret metadata for the scope.

[
  {
    "name": "REGISTRY_TOKEN",
    "scope": "platform/production",
    "source": "database",
    "updated_at": "2026-08-19T09:15:02Z"
  }
]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
  • services/nopsai/pkg/routeauthz/routeauthz.go
GET/v1/secrets/scopesAuthorized

Lists the scopes that hold secrets.

Requires secret.list_metadata

Call it

List secret scopesapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/secrets/scopes" | jq
Result

Scope names only, no secret names and no values.

Responses

200application/json

Scopes holding secrets.

[
  { "scope": "platform/production" },
  { "scope": "platform/shared" }
]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
GET/v1/secrets/{secretName}Authorized

Reads and decrypts one secret value.

Requires secret.read_value

Notes

This route is the reason secret.read_value exists as a separate action from secret.list_metadata and secret.write_value. Anyone holding it can recover any secret in scope.

Parameters

NameInTypeRequiredDescription
secretNamepathstringRequiredSecret name.
scopequerystringOptionalScope to read from.

Call it

Read a secret valueapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/secrets/REGISTRY_TOKEN?scope=platform/production" | jq -r .value
Result

The decrypted value — for a caller holding secret.read_value on that secret, and only for them.

Responses

200application/json

The decrypted secret value.

{"value":"<the stored secret>"}

When it fails

StatusCauseWhat to do
400No secret name in the path.Name the secret.
403The caller does not hold secret.read_value on this secret.This is the action that governs recovering a stored credential. Grant it deliberately.
404No secret with that name in that scope.Check the scope.
409The secret exists but has no value set.Write a value before reading one.
500Decryption failed, which usually means the master key changed.Restore the original NOPSAI_MASTER_KEY or rotate the secret.

Side effects

  • Writes an audit record naming the caller and the secret. Reading a secret is an audited event, not a silent one.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
  • services/nopsai/pkg/routeauthz/routeauthz.go
PUT/v1/secrets/{secretName}Authorized

Creates or replaces a secret value.

Requires secret.write_value

Parameters

NameInTypeRequiredDescription
secretNamepathstringRequiredSecret name. Must match ^[A-Za-z0-9_.-]+$.
scopequerystringOptionalScope to write into.

Call it

Store a secretapi-variables-and-secrets request
curl -sX PUT "$NOPSAI_URL/v1/secrets/REGISTRY_TOKEN?scope=platform/production" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"value\":\"$TOKEN\"}"
Result

201. The value is encrypted with the master key before it is stored.

Replace before running
  • $TOKEN is the secret material; keep it out of shell history.

Responses

201application/json

Stored, for both create and replace.

{"name":"REGISTRY_TOKEN","scope":"platform/production","source":"database"}

When it fails

StatusCauseWhat to do
400The body is not valid JSON or the name is unusable.Send {"value": "..."}.
500Encryption or storage failed.Retry. A failed write leaves the previous value in place.

Side effects

  • Encrypts with NOPSAI_MASTER_KEY before storage.
  • Takes the value out of GitOps ownership until reconciled.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
DELETE/v1/secrets/{secretName}Authorized

Deletes a secret from a scope.

Requires secret.delete

Parameters

NameInTypeRequiredDescription
secretNamepathstringRequiredSecret to delete.
scopequerystringOptionalScope to delete from.

Call it

Delete a secretapi-variables-and-secrets request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/secrets/REGISTRY_TOKEN?scope=platform/production" -w "%{http_code}\n"
Result

204. Steps declaring the secret fail to resolve on their next run.

Responses

204

Deleted.

When it fails

StatusCauseWhat to do
500The delete failed.Retry.

Side effects

  • Removes the encrypted value permanently.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
POST/v1/secrets/encryptAuthorized

Encrypts a value into a GitOps-safe envelope.

Requires secret.write_value

Notes

The envelope is bound to this install’s master key. Another install cannot decrypt it, which is what makes committing it safe and what makes the key a restore dependency.

Call it

Encrypt a value for a configuration repositoryapi-variables-and-secrets request
curl -sX POST "$NOPSAI_URL/v1/secrets/encrypt" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"value\":\"$TOKEN\"}" | jq -r .encrypted
Result

An envelope that is safe to commit and resolves at run time on this install.

Replace before running
  • $TOKEN is the plaintext to protect.

Responses

200application/json

The encrypted envelope.

{"encrypted":"enc:v1:9f2c..."}

When it fails

StatusCauseWhat to do
400The body carries no value.Send {"value": "..."}.
500Encryption failed.Check that NOPSAI_MASTER_KEY is configured.

Side effects

  • Stores nothing. The envelope is returned and forgotten.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
GET/v1/system/credentialsAuthorized

Lists platform credentials. Metadata only.

Notes

has_value answers "is this configured?" without exposing anything. That is the whole read surface for a credential value.

Parameters

NameInTypeRequiredDescription
kindquerystringOptionalRestrict to one credential kind.

Call it

List credentialsapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/credentials" | jq '.[] | {reference, kind, status, active_version}'
Result

References, kinds, and rotation state. Unlike a scope secret, no route returns a credential value.

Responses

200application/json

Credential metadata.

[{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}]

When it fails

StatusCauseWhat to do
401No usable caller identity.Send a token.
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
  • services/nopsai/credential_models.go
POST/v1/system/credentialsAuthorized

Creates a platform credential.

Notes

A credential is referenced by name from everywhere else, which is what keeps provider keys out of pipeline YAML and out of profile documents.

Call it

Store a provider API keyapi-variables-and-secrets request
curl -sX POST "$NOPSAI_URL/v1/system/credentials" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"reference\":\"platform/anthropic\",\"kind\":\"api_key\",\"value\":\"$API_KEY\"}" | jq '{id, reference, has_value}'
Result

The credential record. The value is stored encrypted and never returned again.

Replace before running
  • reference is what other resources name — a model profile, an MCP server, a Git connection.

Responses

201application/json

Credential created.

{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}

When it fails

StatusCauseWhat to do
400A missing reference, an unknown kind, or an empty value.The message names the field.
401No usable caller identity.Send a token.
403The caller may not create credentials.Credential creation is separately authorized from reading metadata.
503Authorization is unavailable.Check AAA.

Side effects

  • Encrypts the value with the master key and stores it as version 1.
  • Writes an audit record.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
GET/v1/system/credentials/{credentialID}Authorized

Reads credential metadata and version history. The value is never returned.

Parameters

NameInTypeRequiredDescription
credentialIDpathuuidRequiredCredential identifier.

Call it

Read a credential and its versionsapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/credentials/$CREDENTIAL_ID" | jq
Result

Metadata plus the version list — which version is active, and what is available to roll back to.

Responses

200application/json

Credential metadata and versions.

{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}

When it fails

StatusCauseWhat to do
403The caller may not read this credential.Metadata access is authorized per credential.
404No credential with that id.List the credentials.
503Authorization is unavailable.Check AAA.
500The versions could not be loaded.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
PUT/v1/system/credentials/{credentialID}/valueAuthorized

Rotates a credential by adding a new version.

Notes

Rotation is additive rather than destructive, which is the difference between this and a scope secret: a bad rotation is undone by activating the previous version.

Parameters

NameInTypeRequiredDescription
credentialIDpathuuidRequiredCredential identifier.

Call it

Rotate a credentialapi-variables-and-secrets request
curl -sX PUT "$NOPSAI_URL/v1/system/credentials/$CREDENTIAL_ID/value" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"value\":\"$NEW_KEY\"}" | jq '{active_version, last_rotated_at}'
Result

A new version becomes active. The previous versions remain, which is what makes a bad rotation recoverable.

Responses

200application/json

Rotated; the new version is active.

{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}

When it fails

StatusCauseWhat to do
400An empty value or an unusable credential id.Send {"value": "..."}.

Side effects

  • Adds a version and activates it. Everything referencing the credential uses the new value on its next call.
  • Writes an audit record.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
  • doc/credential-management.md
POST/v1/system/credentials/{credentialID}/versions/{version}/activateAuthorized

Activates a stored credential version.

Notes

This is the rollback path. Test the provider afterwards — an older key may itself have been revoked at the provider.

Parameters

NameInTypeRequiredDescription
credentialIDpathuuidRequiredCredential identifier.
versionpathintegerRequiredCredential version number.

Call it

Roll back a bad rotationapi-variables-and-secrets request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/credentials/$CREDENTIAL_ID/versions/2/activate" | jq '{active_version}'
Result

The named version becomes active immediately.

Responses

200application/json

Version activated.

{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}

When it fails

StatusCauseWhat to do
400An unknown version or a malformed id.Read the version list first.

Side effects

  • Everything referencing the credential uses this version on its next call.
  • Writes an audit record.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
DELETE/v1/system/credentials/{credentialID}/versions/{version}Authorized

Deletes a credential version.

Parameters

NameInTypeRequiredDescription
credentialIDpathuuidRequiredCredential identifier.
versionpathintegerRequiredCredential version number.

Call it

Remove a superseded versionapi-variables-and-secrets request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/credentials/$CREDENTIAL_ID/versions/1" -w "%{http_code}\n"
Result

The version is gone permanently, which removes it as a rollback target.

Responses

200application/json

Version deleted.

{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}

When it fails

StatusCauseWhat to do
400The version is active, or does not exist.Activate another version before deleting this one.

Side effects

  • Removes a rollback target permanently.
  • Writes an audit record.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
POST/v1/system/credentials/{credentialID}/enableAuthorized

Enables a disabled credential.

Notes

Enabling does not validate the value against the provider. Run the relevant test route afterwards.

Parameters

NameInTypeRequiredDescription
credentialIDpathuuidRequiredCredential identifier.

Call it

Enable a credentialapi-variables-and-secrets request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/credentials/$CREDENTIAL_ID/enable" | jq '{reference, status}'
Result

Resources referencing it start resolving again.

Responses

200application/json

Credential enabled.

{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}

Side effects

  • Resources referencing the credential resolve it again.
  • Writes an audit record.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
POST/v1/system/credentials/{credentialID}/disableAuthorized

Disables a credential without deleting it.

Notes

The containment step for a suspected leak: disable first, rotate second, delete last. Disabling keeps the history an investigation needs.

Parameters

NameInTypeRequiredDescription
credentialIDpathuuidRequiredCredential identifier.

Call it

Disable a leaked credentialapi-variables-and-secrets request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/credentials/$CREDENTIAL_ID/disable" | jq '{reference, status}'
Result

Everything referencing it stops resolving immediately, while the versions stay for investigation.

Responses

200application/json

Credential disabled.

{
  "id": "d61a4b02-7c39-4e58-9a11-40b2c7e35f18",
  "reference": "platform/anthropic",
  "kind": "api_key",
  "status": "enabled",
  "has_value": true,
  "active_version": 3,
  "last_rotated_at": "2026-08-12T09:20:41Z",
  "managed_by_config_repo": false
}

Side effects

  • Model profiles, MCP servers, and Git connections referencing it fail to resolve.
  • Writes an audit record.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
DELETE/v1/system/credentials/{credentialID}Authorized

Deletes a credential and all its versions.

Parameters

NameInTypeRequiredDescription
credentialIDpathuuidRequiredCredential identifier.

Call it

Delete a credentialapi-variables-and-secrets request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/credentials/$CREDENTIAL_ID" -w "%{http_code}\n"
Result

204, unless something still references it.

Responses

204

Credential deleted.

When it fails

StatusCauseWhat to do
409A profile, server, or connection still references the credential.The platform refuses rather than breaking them. Repoint the dependants first.
500The delete failed.Retry.

Side effects

  • Removes every version permanently.
  • Writes an audit record.

Proven by

  • services/nopsai/credential_service_test.go
  • services/nopsai/credential_handlers.go
GET/v1/repositories/{repoOwner}/{repoName}/secretsAuthorized

Lists repository-scoped secret names. Metadata only.

Requires secret.list_metadata

Notes

Repository-scoped values are resolved for runs triggered from that repository. A scope secret and a repository secret with the same name are different values.

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.

Call it

List repository secretsapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/repositories/acme/payments/secrets" | jq
Result

Names and metadata for secrets bound to this repository rather than to a scope.

Responses

200application/json

Repository secret metadata.

[
  { "name": "DEPLOY_KEY", "repository_name": "acme/payments", "source": "database" }
]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
PUT/v1/repositories/{repoOwner}/{repoName}/secrets/{secretName}Authorized

Creates or replaces a repository-scoped secret value.

Requires secret.write_value

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.
secretNamepathstringRequiredSecret name.

Call it

Store a repository secretapi-variables-and-secrets request
curl -sX PUT "$NOPSAI_URL/v1/repositories/acme/payments/secrets/DEPLOY_KEY" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"value\":\"$DEPLOY_KEY\"}"
Result

201. Encrypted with the master key, exactly as a scope secret is.

Responses

201application/json

Stored.

{ "name": "DEPLOY_KEY", "repository_name": "acme/payments", "source": "database" }

When it fails

StatusCauseWhat to do
400The body is not valid JSON or the name is unusable.Send {"value": "..."}.
500Encryption or storage failed.Retry.

Side effects

  • Encrypts with the master key before storage.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
DELETE/v1/repositories/{repoOwner}/{repoName}/secrets/{secretName}Authorized

Deletes a repository-scoped secret.

Requires secret.delete

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.
secretNamepathstringRequiredSecret to delete.

Call it

Delete a repository secretapi-variables-and-secrets request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/repositories/acme/payments/secrets/DEPLOY_KEY" -w "%{http_code}\n"
Result

204. Steps declaring it fail to resolve on their next run from that repository.

Responses

204

Deleted.

When it fails

StatusCauseWhat to do
500The delete failed.Retry.

Side effects

  • Removes the encrypted value permanently.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
GET/v1/repositories/{repoOwner}/{repoName}/variablesAuthorized

Lists repository-scoped variables.

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.

Call it

List repository variablesapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/repositories/acme/payments/variables" | jq
Result

Variables bound to this repository, with their values.

Responses

200application/json

Repository variables.

[
  { "name": "DEPLOY_ENVIRONMENT", "value": "staging", "repository_name": "acme/payments" }
]

When it fails

StatusCauseWhat to do
503Authorization is unavailable.Check AAA.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
GET/v1/repositories/{repoOwner}/{repoName}/variables/{variableName}Authorized

Reads one repository-scoped variable value.

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.
variableNamepathstringRequiredVariable name.

Call it

Read a repository variableapi-variables-and-secrets request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/repositories/acme/payments/variables/DEPLOY_ENVIRONMENT" | jq -r .value
Result

The stored value for that repository.

Responses

200application/json

The variable value.

{"value":"staging"}

When it fails

StatusCauseWhat to do
404No variable with that name for this repository.A scope variable of the same name is a different value.
500The read failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
PUT/v1/repositories/{repoOwner}/{repoName}/variables/{variableName}Authorized

Creates or replaces a repository-scoped variable.

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.
variableNamepathstringRequiredVariable name.

Call it

Set a repository variableapi-variables-and-secrets request
curl -sX PUT "$NOPSAI_URL/v1/repositories/acme/payments/variables/DEPLOY_ENVIRONMENT" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value":"staging"}'
Result

201. The body carries only value; the name and repository come from the path.

Responses

201application/json

Stored.

{ "name": "DEPLOY_ENVIRONMENT", "repository_name": "acme/payments", "source": "database" }

When it fails

StatusCauseWhat to do
400The body is not valid JSON or the name is unusable.Send {"value": "..."}.
500The write failed.Retry.

Side effects

  • Marks the value database-owned, taking it out of GitOps management until reconciled.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go
DELETE/v1/repositories/{repoOwner}/{repoName}/variables/{variableName}Authorized

Deletes a repository-scoped variable.

Parameters

NameInTypeRequiredDescription
repoOwnerpathstringRequiredRepository owner.
repoNamepathstringRequiredRepository name.
variableNamepathstringRequiredVariable to delete.

Call it

Delete a repository variableapi-variables-and-secrets request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/repositories/acme/payments/variables/DEPLOY_ENVIRONMENT" -w "%{http_code}\n"
Result

204, whether or not the variable existed.

Responses

204

Deleted.

When it fails

StatusCauseWhat to do
500The delete failed.Retry.

Side effects

  • Pipelines declaring the variable fail before execution until it resolves again.
  • Writes an audit record.

Proven by

  • services/nopsai/secrets_variables_handlers_test.go
  • services/nopsai/secrets_variables_handlers.go

How it works

The distinction that matters for a security review is not "can secrets be read" but "who holds secret.read_value". Storing a credential here protects it from everyone without that action and from nobody with it, and every read leaves an audit record naming the reader.

Variables are readable by design, because they are configuration. If a value should not be listed by anyone who can list variables, it is a secret, not a variable — the two stores differ in exactly that respect.

A missing scope is created rather than rejected on write. A typo in a scope name produces a value nothing resolves, with no error at the time you make the mistake.

Implementation evidence

  • services/nopsai/secrets_variables_handlers.go

    Variable and secret storage, encryption, and value reads.

  • services/nopsai/pkg/routeauthz/routeauthz.go

    The actions each secret route checks.

  • services/nopsai/credential_handlers.go

    Credential store responses, which carry metadata only.