GUIDES
The per-user vault
Every user brings their own API key or token. Store it once, encrypted, and let their tools use it without the model ever seeing it.
What the vault is for
Your tools often act on behalf of the person chatting, not on behalf of you. Alice's GitHub token, Bob's Stripe key, each customer's CRM credential. The vault holds one secret per user, encrypted with Fernet at rest, and injects it into tool auth at call time. The raw secret is placed in the outbound request, never in the prompt, so the model can trigger the action without ever reading the credential.
| Field | Type | Description |
|---|---|---|
Encryption | Fernet | Symmetric authenticated encryption. Values are encrypted at rest and decrypted only to build the outbound tool request. |
Name | string | Up to 64 chars, [A-Za-z0-9_.-]. This is the handle you reference in a token template. |
Value size | <= 8 KB | Enough for tokens, keys, even short PEM blocks. |
Per-user cap | 50 creds | Fifty named secrets per user. |
Provision a secret from your backend
You write secrets server to server, never from the browser. Send the user's signed identity plus the name and value to PUT /v1/credentials. Signed identity proves the request is really for that user, so a client cannot write into someone else's vault.
/v1/credentialsimport { createHmac } from "crypto";
function stableStringify(v) {
if (Array.isArray(v)) return "[" + v.map(stableStringify).join(",") + "]";
if (v && typeof v === "object") {
return "{" + Object.keys(v).sort()
.map(k => JSON.stringify(k) + ":" + stableStringify(v[k])).join(",") + "}";
}
return JSON.stringify(v);
}
function signUserContext(identity, secret) {
const ctx = Object.assign({}, identity, { _ts: Math.floor(Date.now() / 1000) });
ctx._sig = createHmac("sha256", secret).update(stableStringify(ctx)).digest("hex");
return ctx;
}
const ctx = signUserContext({ id: "u_123" }, process.env.ERA_IDENTITY_SECRET);
const res = await fetch("https://eerraa.online/v1/credentials", {
method: "PUT",
headers: {
Authorization: "Bearer era_your_project_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
user_context: ctx,
name: "github_token",
value: "ghp_xxx",
}),
});
const { name, secret_hint } = await res.json();The response only ever hands back a masked hint, never the value:
{
"name": "github_token",
"secret_hint": "ghp_...xxx"
}List the masked hints or revoke a secret with the same signed identity:
/v1/credentials/v1/credentials/{name}Reference it in a tool or MCP token
Once a secret is stored, reference it by name in any tool auth header or MCP connection token with the {{user.creds.<name>}} placeholder. At call time EERRAA swaps the placeholder for the decrypted value. The name in the placeholder must match the name you provisioned, exactly.
Authorization: Bearer {{user.creds.github_token}}For a webhook tool, drop it in the auth config. For a remote MCP server that uses bearer or header auth, drop it in the token field. Either way, when Alice chats her token is injected; when Bob chats his is. Same tool, per-user credentials.
{{user.auth_token}}. That injects the JWT you passed in signed identity, handy for calling your own API as the current user.