# Secret managers API

5 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /secret-managers

List the team's external secret manager integrations

Variables reference a secret as `${{vault:<name>/<path>#<key>}}` or, for Doppler, `${{doppler:<name>/<project>/<config>#<key>}}`. The value is fetched when a deploy sends its command and is never stored. Credentials are never returned (`secretSet` only). `scope` and `grants` say which projects and environments may reference the integration.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (ListOutputBody)
  - `items` (SecretManager[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 500 Internal Server Error

## POST /secret-managers

Add a secret manager integration

HashiCorp Vault / OpenBao: KV v2 with a token or AppRole. Infisical: a machine identity (universal auth). Doppler: a service token (bound to one config) or a personal / service account token. Nothing contacts the manager until the test or the first deploy that references it. Admin scope.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): CreateInputBody
  - `address` (string): Vault or OpenBao address, e.g. https://vault.example.com:8200
  - `approleMount` (string): AppRole auth mount (default approle)
  - `authMethod` (enum: token | approle): token (default) or approle
  - `caCert` (string): PEM certificate(s) to trust for a private CA (public material)
  - `clientId` (string): Machine identity client id (universal auth)
  - `defaultConfig` (string): Doppler config used by references that name none (with defaultProject)
  - `defaultProject` (string): Doppler project used by references that name none, like doppler:<name>#KEY. Set with defaultConfig, or leave both empty (a service token is bound to one config)
  - `grants` (ScopeGrant[] | null): With scope selected: the projects (or single environments) allowed to reference the integration. At least one.
  - `kind` (enum: vault | infisical | doppler, required): vault covers HashiCorp Vault and OpenBao (KV v2); doppler is Doppler (service token, or a personal / service account token with a project and config)
  - `mount` (string): KV v2 mount path (default secret)
  - `name` (string, required): Lowercase letters, digits and dashes; it appears in references as vault:<name>/… or doppler:<name>/…
  - `namespace` (string): Vault Enterprise / OpenBao namespace (X-Vault-Namespace)
  - `organizationSlug` (string): Optional organization slug to scope the login to
  - `projectId` (string): Infisical project id
  - `roleId` (string): AppRole role id
  - `scope` (enum: team | selected): team (default on create): every project may reference the integration. selected: only the projects and environments in grants. Omit on update to keep the stored scope.
  - `secret` (string): Write-only: the Vault token (token auth), the AppRole secret id, the Infisical client secret or the Doppler token. Omit on update to keep the stored one.
  - `siteUrl` (string): Infisical URL (default https://us.infisical.com) or Doppler API URL (default https://api.doppler.com)

Response 201: Created (SecretManager)
  - `config` (SecretManagerConfig, required)
  - `createdAt` (string (date-time), required)
  - `example` (string, required): A reference in this integration's syntax
  - `grants` (ScopeGrant[] | null, required): With scope selected: the projects and environments that may reference the integration
  - `id` (string, required)
  - `kind` (enum: vault | infisical | doppler, required)
  - `lastTest` (SecretManagerLastTest, required)
  - `name` (string, required): Used in references: ${{vault:<name>/<path>#<key>}} or ${{doppler:<name>/<project>/<config>#<key>}}
  - `scope` (enum: team | selected, required)
  - `secretSet` (boolean, required)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## PUT /secret-managers/{id}

Update a secret manager integration

The name and kind are fixed. Changing the address, auth method or identity requires the credential again. Omitting `scope` keeps the stored scope and grants. Admin scope.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `id` (path, string, required)

Request body (application/json, required): SecretManagerBody
  - `address` (string): Vault or OpenBao address, e.g. https://vault.example.com:8200
  - `approleMount` (string): AppRole auth mount (default approle)
  - `authMethod` (enum: token | approle): token (default) or approle
  - `caCert` (string): PEM certificate(s) to trust for a private CA (public material)
  - `clientId` (string): Machine identity client id (universal auth)
  - `defaultConfig` (string): Doppler config used by references that name none (with defaultProject)
  - `defaultProject` (string): Doppler project used by references that name none, like doppler:<name>#KEY. Set with defaultConfig, or leave both empty (a service token is bound to one config)
  - `grants` (ScopeGrant[] | null): With scope selected: the projects (or single environments) allowed to reference the integration. At least one.
  - `mount` (string): KV v2 mount path (default secret)
  - `namespace` (string): Vault Enterprise / OpenBao namespace (X-Vault-Namespace)
  - `organizationSlug` (string): Optional organization slug to scope the login to
  - `projectId` (string): Infisical project id
  - `roleId` (string): AppRole role id
  - `scope` (enum: team | selected): team (default on create): every project may reference the integration. selected: only the projects and environments in grants. Omit on update to keep the stored scope.
  - `secret` (string): Write-only: the Vault token (token auth), the AppRole secret id, the Infisical client secret or the Doppler token. Omit on update to keep the stored one.
  - `siteUrl` (string): Infisical URL (default https://us.infisical.com) or Doppler API URL (default https://api.doppler.com)

Response 200: OK (SecretManager)
  - `config` (SecretManagerConfig, required)
  - `createdAt` (string (date-time), required)
  - `example` (string, required): A reference in this integration's syntax
  - `grants` (ScopeGrant[] | null, required): With scope selected: the projects and environments that may reference the integration
  - `id` (string, required)
  - `kind` (enum: vault | infisical | doppler, required)
  - `lastTest` (SecretManagerLastTest, required)
  - `name` (string, required): Used in references: ${{vault:<name>/<path>#<key>}} or ${{doppler:<name>/<project>/<config>#<key>}}
  - `scope` (enum: team | selected, required)
  - `secretSet` (boolean, required)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /secret-managers/{id}

Remove a secret manager integration

Refused with 409 `integration_in_use` while a variable references it.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `id` (path, string, required)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict (`integration_in_use`); 422 Unprocessable Entity; 500 Internal Server Error

## POST /secret-managers/{id}/test

Test a secret manager integration

Signs in and, when `path` and `key` are given, reads that secret. The value is discarded and never returned.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `id` (path, string, required)

Request body (application/json, required): TestInputBody
  - `key` (string): Optional field or secret name to read
  - `path` (string): Optional secret path to read (without the integration name); for Doppler the project and config, like backend/prd, or empty for the integration's defaults

Response 200: OK (SecretManagerTestResult)
  - `message` (string, required)
  - `ok` (boolean, required)
  - `read` (boolean, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error
