Manage Secrets
Polyant stores per-instance secrets (API keys, tokens, bucket names) in the instance_secrets table, encrypted at rest with AES-256-GCM. Values are never returned by the API — only the list of configured keys.
This guide walks through listing, creating, updating, and deleting secrets, plus rotating the master encryption key.
Prerequisites
- An engine running with
ENCRYPTION_KEYset in the environment.- The key MUST be 64 hex characters (32 bytes, AES-256). Generate one with
openssl rand -hex 32. - Changing this value invalidates every existing encrypted row — plan ahead (see Rotate the master key below).
- The key MUST be 64 hex characters (32 bytes, AES-256). Generate one with
- An existing instance, identified by its
slug. - A valid session cookie or
Authorization: Bearer <token>header (the admin API is authenticated).
Step 1 — List the configured secret keys
curl -s http://localhost:4000/api/instances/my-assistant/secrets \
-H "Cookie: authjs.session-token=$TOKEN" | jqResponse (well-known keys are always listed; configured: false means the row does not exist yet):
{
"secrets": [
{ "key": "openai_api_key", "configured": true },
{ "key": "anthropic_api_key", "configured": false },
{ "key": "langsmith_api_key", "configured": false }
]
}Keys outside the well-known set (e.g. hubspot_api_key, serpapi_api_key, slack_*) are NOT in the SECRET_KEYS enum, so they appear in this listing only after they have been explicitly written via the PUT below.
Values are NEVER returned by this endpoint. To verify a value, your only options are the consumers (e.g. send a Playground message and see the LLM respond) or query the DB directly with decrypt() from the engine.
Step 2 — Set one or more secrets
The PUT endpoint accepts a batch payload. Use it to add or update keys in a single call.
curl -s -X PUT http://localhost:4000/api/instances/my-assistant/secrets \
-H "Content-Type: application/json" \
-H "Cookie: authjs.session-token=$TOKEN" \
-d '{
"secrets": [
{ "key": "openai_api_key", "value": "sk-..." },
{ "key": "langsmith_api_key", "value": "ls_..." }
]
}'Key naming: well-known keys are all lowercase with underscores. The recognised set (declared in secrets.store.ts) is:
openai_api_key, anthropic_api_key, aws_access_key_id, aws_secret_access_key,
aws_region, langsmith_api_key, auth_api_key, tavily_api_key, github_token,
s3_bucket_name, http_api_key, deepgram_api_keyYou can also store custom keys (e.g. for skill-specific credentials) using the same endpoint — they will appear in the listing alongside the well-known ones.
Step 3 — Delete a secret
curl -s -X DELETE http://localhost:4000/api/instances/my-assistant/secrets/openai_api_key \
-H "Cookie: authjs.session-token=$TOKEN"The :key path segment is the lowercase secret name. There is no soft-delete: the row is removed immediately and any consumer relying on it (LLM provider, S3 client, etc.) will fail until you set it again.
What is NOT in instance_secrets
The following live in different tables and are managed by separate endpoints — do not try to store them under /secrets:
| Credential | Where it lives | Endpoint |
|---|---|---|
| Telegram bot token | instance_channels (type=telegram) | PUT /api/instances/:slug/channels/telegram |
Slack tokens (xoxb-, xapp-) | instance_channels (type=slack) | PUT /api/instances/:slug/channels/slack |
| Twilio Account SID / Auth Token / WhatsApp From number | instance_channels (type=whatsapp) | PUT /api/instances/:slug/channels/whatsapp |
| Skill-specific env vars | instance_skill_env | PUT /api/instances/:slug/skills/:slug/env |
See Connect a channel for the channel-specific flow.
Rotate the master key
The ENCRYPTION_KEY env var is the single point of failure for all encrypted data. Rotating it is destructive — you must re-enter every secret afterwards.
- Stop the engine so no readers/writers race the rotation.
- Export every instance (
GET /api/instances/:slug/export). The bundle does NOT include secret values, only the list of which keys were configured — that is exactly what you need to know what to re-enter. - Generate a new key with
openssl rand -hex 32and updateENCRYPTION_KEYin your environment. - Wipe the old rows:
DELETE FROM instance_secrets;(and, if you also rotate channel encryption,DELETE FROM instance_channels;). The old ciphertexts cannot be read with the new key, so leaving them creates noise on every read. - Start the engine and re-enter the secrets via the PUT endpoint (or the admin panel Settings tab).
- Rotate the upstream credentials too: anyone who had the old
ENCRYPTION_KEYcould have decrypted the old values, so treat them as compromised.
Verification
After running Step 2 you should see:
curl -s http://localhost:4000/api/instances/my-assistant/secrets | jq '.secrets[] | select(.configured)'- The
openai_api_keyandlangsmith_api_keyentries returnconfigured: true. - In the admin panel Settings tab, the OpenAI field shows the placeholder “configured” instead of an empty input.
- A test message via the Playground or
POST /v1/chat/completionssucceeds without a “missing API key” error.
See also
- Connect a channel — for Telegram / Slack / WhatsApp credentials
- Export and import an instance — bundles list secret keys (without values)
- Observe with LangSmith — uses
langsmith_api_key