Skip to main content
Version: Next

Secrets Manager API

The Secrets Manager API is used by machine clients (CI servers, application servers, Kubernetes operators) to fetch and manage secrets. It uses a separate authentication mechanism from the user-facing API: ECDSA P-256 signed JWTs.

Client Authentication

Secrets Manager clients authenticate by signing a JWT with their ECDSA P-256 private key. The JWT is included in the Authorization header as a Bearer token.

JWT Structure

Header:

{
"alg": "ES256",
"typ": "JWT"
}

Payload:

{
"sub": "c9a1b2d3-4e5f-6789-abcd-ef0123456789",
"aud": "https://vault.example.com",
"iat": 1712440800,
"exp": 1712444400
}
ClaimDescription
subThe client_id assigned during client creation
audThe SyVault server URL
iatIssued-at timestamp (Unix seconds)
expExpiration timestamp (max 1 hour from iat)

The JWT is signed with the client's ECDSA P-256 private key. The server validates the signature against the public key stored during bootstrap.

Example (using curl with a pre-signed JWT)

export SM_TOKEN="eyJhbGciOiJFUzI1NiIs..."

curl https://vault.example.com/api/sm/v1/secrets \
-H "Authorization: Bearer $SM_TOKEN"

Bootstrap

Exchange a one-time access token for persistent client key material. This endpoint is called once per client, typically by sy init.

POST /api/sm/v1/bootstrap
curl -X POST https://vault.example.com/api/sm/v1/bootstrap \
-H "Content-Type: application/json" \
-d '{
"token": "vft_eyJhbGciOiJFUzI1NiIs...",
"public_key": "base64-encoded-ecdsa-p256-public-key"
}'

The client generates an ECDSA P-256 key pair locally and sends the public key. The server stores the public key for future JWT verification and returns the encrypted folder keys the client is authorized to access.

Response (200):

{
"client_id": "c9a1b2d3-4e5f-6789-abcd-ef0123456789",
"application_id": "app-uuid-1",
"encrypted_folder_keys": {
"folder-uuid-1": "base64-encrypted-folder-key",
"folder-uuid-2": "base64-encrypted-folder-key"
}
}

Response (401) — token already used or invalid:

{
"error": "invalid_token",
"message": "This access token has already been used or is invalid."
}

List Secrets

Retrieve all secrets the client is authorized to access.

GET /api/sm/v1/secrets
curl https://vault.example.com/api/sm/v1/secrets \
-H "Authorization: Bearer $SM_TOKEN"

Response:

{
"data": [
{
"uid": "7Kj9mNpQ2xRs",
"folder_id": "folder-uuid-1",
"title_encrypted": "base64-encrypted-title",
"data_encrypted": "base64-encrypted-data",
"updated_at": "2026-04-05T14:22:00Z"
},
{
"uid": "Xp4qRtY8mNwK",
"folder_id": "folder-uuid-2",
"title_encrypted": "base64-encrypted-title",
"data_encrypted": "base64-encrypted-data",
"updated_at": "2026-04-03T09:15:00Z"
}
]
}

The client decrypts title_encrypted and data_encrypted using the corresponding folder key received during bootstrap.

Get a Single Secret

GET /api/sm/v1/secrets/{uid}
curl https://vault.example.com/api/sm/v1/secrets/7Kj9mNpQ2xRs \
-H "Authorization: Bearer $SM_TOKEN"

Response:

{
"uid": "7Kj9mNpQ2xRs",
"folder_id": "folder-uuid-1",
"title_encrypted": "base64-encrypted-title",
"data_encrypted": "base64-encrypted-data",
"updated_at": "2026-04-05T14:22:00Z"
}

Resolve Notation

Resolve a sy:// notation URI to a single field value server-side. The server looks up the folder, title, and field, then returns the encrypted field value.

POST /api/sm/v1/notation
curl -X POST https://vault.example.com/api/sm/v1/notation \
-H "Authorization: Bearer $SM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"notation": "sy://Production/Database/field/password"
}'

Response:

{
"uid": "7Kj9mNpQ2xRs",
"folder": "Production",
"title": "Database",
"field": "password",
"value_encrypted": "base64-encrypted-value"
}

The client decrypts value_encrypted using the folder key for the "Production" folder.

Error Responses

StatusError CodeDescription
400invalid_requestMalformed request body or invalid notation
401invalid_tokenJWT signature invalid, expired, or client not found
403access_deniedClient does not have access to the requested folder
404secret_not_foundNo secret matches the UID or notation
429rate_limitedToo many requests (server-side per-IP bucket exhausted)

Rate Limits

SyVault applies two global per-IP rate-limit buckets (see crates/server/src/api/rate_limit.rs):

  • Strict bucket (5 requests/minute/IP): applied to auth-sensitive endpoints such as POST /api/sm/v1/bootstrap. Designed for brute-force resistance.
  • Standard bucket (60 requests/minute/IP): applied to all other Secrets Manager client endpoints (GET /api/sm/v1/secrets, GET /api/sm/v1/secrets/{uid}, POST /api/sm/v1/notation, etc.).

Limits are enforced per source IP address, not per client ID. High-volume applications behind a NAT or shared egress should request a dedicated allowlist.

note

Custom per-endpoint rate limits (e.g. 100 req/min on list, 300 on get) are not currently implemented. A future release may introduce per-client token buckets with per-endpoint policies.


Write Operations

In addition to reading secrets, Secrets Manager clients can create, update, delete, and search secrets. These operations require that the client's access token was created with write permissions on the target folder.

Create a Secret

POST /api/sm/v1/secrets
curl -X POST https://vault.example.com/api/sm/v1/secrets \
-H "Authorization: Bearer $SM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"folder_id": "folder-uuid-1",
"record_type": "login",
"encrypted_payload": "base64-client-encrypted-payload",
"wrapped_dek": "base64-folder-key-wrapped-dek"
}'
FieldTypeRequiredDescription
folder_idstringYesThe folder to create the secret in. The client must have write access.
record_typestringYesRecord type: login, card, note, identity, ssh_key, api_credential, database, env_file.
encrypted_payloadstringYesThe record data encrypted with a DEK (AES-256-GCM).
wrapped_dekstringYesThe DEK wrapped (encrypted) with the folder key.

Response (201):

{
"uid": "Nw8kLmPq3xTs",
"folder_id": "folder-uuid-1",
"record_type": "login",
"created_at": "2026-04-06T12:00:00Z",
"updated_at": "2026-04-06T12:00:00Z"
}

Update a Secret

PUT /api/sm/v1/secrets/{uid}
curl -X PUT https://vault.example.com/api/sm/v1/secrets/Nw8kLmPq3xTs \
-H "Authorization: Bearer $SM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"encrypted_payload": "base64-updated-encrypted-payload",
"wrapped_dek": "base64-folder-key-wrapped-dek"
}'

Response (200):

{
"uid": "Nw8kLmPq3xTs",
"folder_id": "folder-uuid-1",
"record_type": "login",
"created_at": "2026-04-06T12:00:00Z",
"updated_at": "2026-04-06T12:05:00Z"
}

Delete a Secret

DELETE /api/sm/v1/secrets/{uid}
curl -X DELETE https://vault.example.com/api/sm/v1/secrets/Nw8kLmPq3xTs \
-H "Authorization: Bearer $SM_TOKEN"

Returns 204 No Content. The encrypted payload and wrapped DEK are permanently removed from the database. This action is irreversible.

Search Secrets

GET /api/sm/v1/secrets/search?title={query}

Search secrets by encrypted title. The server performs the search against title metadata indexed during creation. Only secrets the client has access to are returned.

curl "https://vault.example.com/api/sm/v1/secrets/search?title=production-db" \
-H "Authorization: Bearer $SM_TOKEN"

Response (200):

{
"data": [
{
"uid": "7Kj9mNpQ2xRs",
"folder_id": "folder-uuid-1",
"title_encrypted": "base64-encrypted-title",
"data_encrypted": "base64-encrypted-data",
"updated_at": "2026-04-05T14:22:00Z"
}
]
}

Get a Specific Field

GET /api/sm/v1/secrets/{uid}/fields/{field}

Retrieve a single encrypted field from a secret without fetching the entire record. Useful when you only need the password or a single API key from a multi-field record.

curl https://vault.example.com/api/sm/v1/secrets/7Kj9mNpQ2xRs/fields/password \
-H "Authorization: Bearer $SM_TOKEN"

Response (200):

{
"uid": "7Kj9mNpQ2xRs",
"field": "password",
"value_encrypted": "base64-encrypted-field-value",
"updated_at": "2026-04-05T14:22:00Z"
}

The client decrypts value_encrypted using the folder key.

Write Operation Rate Limits

Write endpoints share the same standard bucket (60 req/min per IP) described in the main Rate Limits section. Per-endpoint custom limits are not currently implemented.