Skip to content

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.

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.
secrets.ts
const scopes = await client.listSecretScopes();
// GET /agent/secrets/scopes
// [{ scope: "org" | "team" | "project" | "agent", scopeId, name }, ...]
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).

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}

For a sensitive field, seal the value before it leaves the machine. The flow is:

  1. Mint a data key bound to this exact secret + field.
  2. Encrypt the value locally with that key (AES-256-GCM).
  3. 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.
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.

Secrets follow the same org → team → project → agent precedence as the rest of the platform — see Connection scopes for how that hierarchy works.