API REFERENCE
The credential vault
A per-user vault for secrets. Store a token once, reference it in tool auth by name, and the raw value never reaches the model.
user_context must be signed, provisioning is server to server. There is no browser path: the widget never calls these routes, and an end user cannot add a credential from the chat UI.Origin header is served normally even when the project has an allowlist configured. Signed identity is the only control on these routes.| Field | Type | Description |
|---|---|---|
SIGNED_IDENTITY_REQUIRED | 403 | The project itself is not in signed identity mode. Switch the project over; no request body will fix this. |
IDENTITY_NOT_CONFIGURED | 401 | The project is in signed mode but has no signing secret yet. |
IDENTITY_SIGNATURE_REQUIRED | 401 | The user_context carried no _sig or no _ts. |
IDENTITY_SIGNATURE_INVALID | 401 | The HMAC did not verify, or _ts was not an integer. Usually a canonical-JSON mismatch in your signer. |
IDENTITY_SIGNATURE_EXPIRED | 401 | _ts is more than 300 seconds from server time, in either direction. Sign at request time, not at session start. |
IDENTITY_ID_REQUIRED | 401 | The signature verified but the context has no id. |
USER_ID_REQUIRED | 400 | The id is missing or the literal string "anonymous". |
Store a credential
/v1/credentialsStores a secret for one user, encrypted at rest with Fernet. PUT is upsert: reusing a name replaces the value. The raw secret is write-only, once stored you can only read a masked hint.
| Field | Type | Description |
|---|---|---|
user_contextrequired | object | The signed end-user identity the secret belongs to. |
namerequired | string | Reference name. Up to 64 chars, [A-Za-z0-9_.-] only. |
valuerequired | string | The secret. Up to 8 KB. |
| Field | Type | Description |
|---|---|---|
INVALID_NAME | 400 | The name is empty, longer than 64 characters, or contains something other than letters, digits, _ . or -. |
VALUE_REQUIRED | 400 | The value was empty. There is no way to store a blank secret; delete it instead. |
VALUE_TOO_LARGE | 413 | The value exceeds 8192 bytes of UTF-8. |
CRED_CAP_REACHED | 409 | This user already holds 50 credentials and the name is a new one. Overwriting an existing name is still allowed at the cap. |
RATE_LIMIT_EXCEEDED | 429 | More than 30 writes in a minute or 300 in a day for this user on this project. The response carries a Retry-After header. GET and DELETE are not throttled. |
curl -X PUT https://agentifys.ai/v1/credentials \
-H "Authorization: Bearer era_your_project_key" \
-H "Content-Type: application/json" \
-d '{
"user_context": { "id": "u_42", "_ts": 1735689600, "_sig": "..." },
"name": "github_token",
"value": "ghp_xxxxxxxxxxxxxxxx"
}'Response, the name plus a masked hint (never the value). The hint is the first eight characters, an ellipsis, and the last four — a value of eight characters or fewer masks to **** instead.
{ "name": "github_token", "secret_hint": "ghp_ABCD...wxyz" }List and revoke
/v1/credentialsLists the calling user's credentials as masked hints. Pass the signed identity as a user_context query param (URL-encoded JSON).
# List one user's masked credentials. Identity in the user_context query param on a GET.
curl "https://agentifys.ai/v1/credentials?user_context=$(python -c 'import json,urllib.parse; print(urllib.parse.quote(json.dumps({"id":"u_42","_ts":1735689600,"_sig":"..."})))')" \
-H "Authorization: Bearer era_your_project_key"Response, each credential as its name, masked hint, active flag and timestamps, ordered by name:
{
"credentials": [
{
"name": "github_token",
"secret_hint": "ghp_ABCD...wxyz",
"is_active": true,
"created_at": "2026-08-14 09:12:44+00:00",
"updated_at": "2026-09-01 17:03:10+00:00"
}
]
}user_context in the request line, so it is written to every access log between you and Agentifys. Sign the smallest identity that works — an id and nothing else — for these two calls. If your standard identity payload includes an auth_token, a live bearer token ends up in those logs./v1/credentials/{name}Revokes one credential by name. The name is required (there is no clear-all route), and the signed identity is passed as a user_context query param.
curl -X DELETE "https://agentifys.ai/v1/credentials/github_token?user_context=%7B%22id%22%3A%22u_42%22%2C%22_ts%22%3A1735689600%2C%22_sig%22%3A%22...%22%7D" \ -H "Authorization: Bearer era_your_project_key"
{ "success": true }Use it in a tool
A webhook tool has no auth object. Reference a stored secret from the tool's webhook_headers (or webhook_params) with {{user.creds.<name>}}. Those two dictionaries are the only place a placeholder resolves — webhook_url is used literally. At call time Agentifys decrypts the value and substitutes it into the outgoing request. The model sees neither.
A tool that authenticates with the stored GitHub token:
{
"executor_type": "webhook",
"webhook_url": "https://api.github.com/user/repos",
"webhook_method": "GET",
"webhook_headers": {
"Authorization": "Bearer {{user.creds.github_token}}"
}
}For a remote MCP server, the placeholder goes in the server's token field on its own, with no scheme prefix.
Authorization: Bearer with nothing after it and a 401 from your API. MCP auth behaves the other way and refuses the call with TOOL_DISABLED. Neither surfaces as “credential missing” on its own — check the resolved value at your endpoint.{{user.auth_token}}. It carries the token from the current request rather than the vault, which also means it is empty in a scheduled run: the scheduler rebuilds the user from a snapshot of the id, name, email, role and company, with no token. Vault credentials still resolve there.Provisioning secrets from your backend is the common pattern. See the per-user credentials guide for the end-to-end flow.