Secrets
Agents can store and retrieve secrets — API keys, tokens, credentials — scoped to the org, a team, a project, or the agent itself. Secrets marked sensitive are encrypted on the client: the platform never sees their plaintext. It hands the agent a one-time data key, the agent encrypts locally, and only the resulting opaque envelope is stored.
All examples use the @alfe.ai/agent-api-client. Access
is enforced per scope — an agent only reaches secrets in scopes it belongs to.
Anatomy of a secret
Section titled “Anatomy of a secret”A secret has metadata (name, category, tags) and one or more fields. Each field is either:
- Plaintext — a non-sensitive value stored as-is (for example a username or a base URL), or
- Encrypted — a sensitive value the agent seals locally before sending.
Discover scopes
Section titled “Discover scopes”const scopes = await client.listSecretScopes();// GET /agent/secrets/scopes// [{ scope: "org" | "team" | "project" | "agent", scopeId, name }, ...]List and read
Section titled “List and read”const secrets = await client.listSecrets({ scope: "agent", scopeId, category: "api_key" });// GET /agent/secrets/{scope}/{scopeId}
const { aggregate, envelopes } = await client.getSecret({ scope: "agent", scopeId, secretId });// GET /agent/secrets/{scope}/{scopeId}/{secretId}listSecrets returns metadata only. getSecret returns the secret plus its
per-field envelopes; you decrypt the sensitive ones locally (see below).
Writing a plaintext field
Section titled “Writing a plaintext field”Non-sensitive values need no encryption — ship the value inline:
await client.createSecret({ scope: "agent", scopeId, secretId: "acme-api", secretName: "Acme API", category: "api_key", fields: [ { key: "base_url", sensitivity: "plaintext", value: "https://api.acme.example" }, ],});// PUT /agent/secrets/{scope}/{scopeId}/{secretId}Writing an encrypted field
Section titled “Writing an encrypted field”For a sensitive field, seal the value before it leaves the machine. The flow is:
- Mint a data key bound to this exact secret + field.
- Encrypt the value locally with that key (AES-256-GCM).
- Send the envelope — never the plaintext.
// 1. Mint a one-time data key for the (secret, field) pair.const dataKey = await client.generateSecretDataKey({ scope: "agent", scopeId, secretId: "acme-api", fieldKey: "token",});// POST /agent/secrets/generate-data-key
// 2. Decode dataKey.plaintextKey (base64) to a Buffer, AES-256-GCM encrypt// your value locally, and build an EncryptedEnvelopeV1:// { version: 1, iv, ciphertext, authTag, dataKeyCiphertext }.// dataKeyCiphertext is the wrapped key returned in step 1. Zero the// plaintext key buffer after use.
// 3. Store the sealed field.await client.setSecretField({ scope: "agent", scopeId, secretId: "acme-api", fieldKey: "token", sensitivity: "encrypted", envelope: sealedEnvelope,});// PUT /agent/secrets/{scope}/{scopeId}/{secretId}/fields/{fieldKey}To read it back, fetch the field’s envelope, then unwrap its data key so you can decrypt locally:
const field = await client.getSecretField({ scope: "agent", scopeId, secretId: "acme-api", fieldKey: "token" });// GET /agent/secrets/{scope}/{scopeId}/{secretId}/fields/{fieldKey}
const { plaintextKey } = await client.decryptSecretDataKey({ scope: "agent", scopeId, secretId: "acme-api", fieldKey: "token", dataKeyCiphertext: field.envelope!.dataKeyCiphertext,});// POST /agent/secrets/decrypt-data-key// Decode plaintextKey (base64) to a Buffer, decrypt the envelope, then wipe it.Other operations
Section titled “Other operations”| Method | Endpoint | Purpose |
|---|---|---|
updateSecretMetadata(args) |
PATCH /agent/secrets/{scope}/{scopeId}/{secretId} |
Rename, re-tag, or re-categorize. |
removeSecretField(args) |
DELETE …/{secretId}/fields/{fieldKey} |
Remove one field. |
getSecretHistory(args) |
GET …/{secretId}/history |
Read the metadata-only audit trail. |
deleteSecret(args) |
DELETE …/{secretId} |
Delete a secret and all its fields. |
Scope precedence
Section titled “Scope precedence”Secrets follow the same org → team → project → agent precedence as the rest of the platform — see Connection scopes for how that hierarchy works.