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.

FieldTypeDescription
EncryptionFernetSymmetric authenticated encryption. Values are encrypted at rest and decrypted only to build the outbound tool request.
NamestringUp to 64 chars, [A-Za-z0-9_.-]. This is the handle you reference in a token template.
Value size<= 8 KBEnough for tokens, keys, even short PEM blocks.
Per-user cap50 credsFifty 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.

PUT/v1/credentials
javascript
import { 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:

json
{
  "name": "github_token",
  "secret_hint": "ghp_...xxx"
}

List the masked hints or revoke a secret with the same signed identity:

GET/v1/credentials
DELETE/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.

text
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.

To forward a live session token instead of a stored secret, use {{user.auth_token}}. That injects the JWT you passed in signed identity, handy for calling your own API as the current user.
The model never sees the secret. It is substituted into the outbound request after the model has decided to call the tool, so it stays out of the prompt, out of the transcript, and out of the logs (only the hint is logged).