API Reference

Secret Stores

Configure Vault/OpenBao, SOPS age keys, and ESO references via the API

Secret Stores API

Secret stores tell Wardn how to reach your secret providers. Each store is a type/provider pair backed by a registered provider — credentials are sealed at rest (enc:v1: AES-256-GCM via WARDN_CREDENTIALS_KEY) and never returned by the API.

Providers

The registry is self-describing — GET /secret-stores/providers returns every type/provider with its config + credential field schema so the UI renders forms without hardcoding:

typeproviderStatusNotes
vaultvault✅HashiCorp Vault — token or AppRole auth; Test does lookup-self, Fetch reads KV v2 for direct env injection
vaultopenbao✅Same implementation, separate registration
sopsage✅Age private key — Test does an encrypt/decrypt round-trip
esoeso🚧Registered for reference; Test returns untested — ESO manages its own credentials in-cluster
sopsaws-kms/gcp-kms/pgp/vault-transit📋Not registered — 400 on create

vault stores validate config server-side — public endpoints require https:// when the server runs in production mode. Test/Fetch calls are rate-limited per user.

Endpoints

MethodPathPermissionDescription
GET/api/v1/secret-stores/providerssecretstore:readProvider schemas for form rendering
GET/api/v1/secret-storessecretstore:readList stores — {items:[…]}
GET/api/v1/secret-stores/{id}secretstore:readOne store
GET/api/v1/secret-stores/{id}/referencessecretstore:readApp references — 📋 always returns {items:[]} (not wired)
POST/api/v1/secret-storessecretstore:manageCreate — {name,type,provider,config,credentials}
PATCH/api/v1/secret-stores/{id}secretstore:manageUpdate name/config/credentials — omitted fields unchanged
DELETE/api/v1/secret-stores/{id}secretstore:manageDelete
POST/api/v1/secret-stores/testsecretstore:manageTest an unsaved {type,provider,config,credentials} before creating
POST/api/v1/secret-stores/{id}/testsecretstore:manageRe-test a saved store (rate-limited)

secretstore:manage is session-only — API keys cannot create or modify stores, so a leaked wrd_ key can't register credentials.

The store object

{
  "id": "sts_01H8X2K3…",
  "name": "prod-vault",
  "type": "vault",
  "provider": "vault",
  "status": "connected",
  "statusDetail": "",
  "lastTestedAt": "2026-01-15T10:00:00Z",
  "credentialsExpireAt": "2026-01-15T11:00:00Z",
  "config": { "address": "https://vault.example.com:8200", "mountPath": "secret" },
  "references": 0,
  "createdAt": "…",
  "updatedAt": "…"
}

status is connected | error | untested (errorClass on the test result buckets failures: dns/tls/connect/timeout/auth/permission/notfound/provider). credentialsExpireAt surfaces provider-reported TTLs (e.g. Vault token expiry) so the UI can warn before auth breaks.

Create examples

# Vault — token auth
curl -X POST http://localhost:8080/api/v1/secret-stores -b cookies.txt \
  -H "Content-Type: application/json" -d '{
    "name": "prod-vault", "type": "vault", "provider": "vault",
    "config": {"address": "https://vault.example.com:8200", "mountPath": "secret"},
    "credentials": {"token": "hvs.…"}}'

# SOPS age key
curl -X POST http://localhost:8080/api/v1/secret-stores -b cookies.txt \
  -H "Content-Type: application/json" -d '{
    "name": "dev-age", "type": "sops", "provider": "age",
    "credentials": {"privateKey": "AGE-SECRET-KEY-1XYZ…"}}'

201 → the store object (credentials stripped). 409 on duplicate name; 400 on failed validation (including SSRF checks on the address).

Test a store

curl -X POST http://localhost:8080/api/v1/secret-stores/sts_01H8…/test -b cookies.txt
{ "status": "connected", "latency": "118ms", "details": "authenticated; token ttl 1h" }

A failed test sets the store's status to error and returns the structured result:

{ "status": "error", "error": "authentication failed", "errorClass": "auth" }

On this page