How to store API keys
for an AI agent.
Not in .env. Not in a JSON config. Not in a shared secrets manager with wildcard scopes. Here's what actually works for autonomous agents.
Every agent needs credentials for something — a database, a payment processor, a CRM, a downstream LLM. Where those credentials live determines how bad the worst-case scenario is when something goes wrong.
The right answer for autonomous agents is a per-identity vault: encrypted storage scoped to one agent, readable only by that agent's API key, revoked when the agent is revoked. This is not what most tutorials show; most tutorials show .env because it's the shortest path to a working demo.
What 'per-identity vault' means
The vault is bound to an agent identity. The agent's API key authenticates reads; another agent's key can't read this vault even if both run in the same process. When the agent identity is revoked, its vault entries are destroyed in the same operation.
This differs from a generic secrets manager (AWS Secrets Manager, HashiCorp Vault) where access is granted by IAM role and the vault outlives the things that use it. Per-identity vaults make orphaned credentials structurally impossible — the credential lifetime is tied to the agent lifetime.
Store a key (one-time, from your dev box)
Pre-load credentials when you set up the agent, not at runtime. The pattern: POST to /v0/vault with a label and the secret value. Labels are how the agent refers to the secret without ever seeing its value in code.
# Store a Stripe restricted key under the label 'stripe-refund'
curl -X POST https://api.loomal.ai/v0/vault \
-H "Authorization: Bearer $LOOMAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "stripe-refund",
"value": "rk_live_..."
}'Read a key from the agent (Python)
In the agent, fetch secrets by label. Don't cache in memory longer than necessary; a per-request fetch is typically fine and avoids leaking secrets through long-lived memory.
import os, requests
def get_secret(label: str) -> str:
res = requests.get(
f"https://api.loomal.ai/v0/vault/{label}",
headers={"Authorization": f"Bearer {os.environ['LOOMAL_API_KEY']}"},
timeout=5,
)
res.raise_for_status()
return res.json()["value"]
# Use at the moment of the API call, not at process start
stripe_key = get_secret("stripe-refund")Read from TypeScript
Same endpoint, same pattern. For agents using an MCP-aware framework, skip the wrapper — the model calls vault.get as a typed tool and the value never passes through your code.
async function getSecret(label: string): Promise<string> {
const res = await fetch(
`https://api.loomal.ai/v0/vault/${label}`,
{ headers: { Authorization: `Bearer ${process.env.LOOMAL_API_KEY}` } },
);
if (!res.ok) throw new Error(`vault: ${res.status}`);
return (await res.json()).value as string;
}Rotation and revocation
Rotate by overwriting the vault entry. The next read returns the new value; no redeploy needed. For TOTP secrets, the same pattern applies — re-enroll on the target service, overwrite the otpauth URL in the vault.
Revocation is where the design pays off. To retire the agent, revoke the identity in the Loomal console. Every vault entry, TOTP secret, and email credential dies in the same operation. No manual cleanup across twelve services.
FAQ
Why not just use AWS Secrets Manager?
You can — the difference is scoping. Secrets Manager is typically used with IAM roles that grant broader access than the agent strictly needs. Per-identity vaults tie the credential's lifetime to the agent's lifetime, which is the property you want.
Does the vault support versioning?
The vault returns the current value. For history, audit logs show every write with a timestamp, so you can reconstruct what the vault held at any past time — but you can't roll back to a prior value automatically.
How do I store structured credentials (username + password)?
Either store them as a JSON-encoded string in one label, or use two labels (crm-username, crm-password). The JSON-encoded pattern is simpler when the credential is always used together.
Related reading
Last updated: 2026-04-15