Skip to Content
Polyant is open source under AGPL-3.0 — star us on GitHub.
How-toManage Per-Instance Secrets

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_KEY set 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).
  • 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" | jq

Response (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_key

You 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:

CredentialWhere it livesEndpoint
Telegram bot tokeninstance_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 numberinstance_channels (type=whatsapp)PUT /api/instances/:slug/channels/whatsapp
Skill-specific env varsinstance_skill_envPUT /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.

  1. Stop the engine so no readers/writers race the rotation.
  2. 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.
  3. Generate a new key with openssl rand -hex 32 and update ENCRYPTION_KEY in your environment.
  4. 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.
  5. Start the engine and re-enter the secrets via the PUT endpoint (or the admin panel Settings tab).
  6. Rotate the upstream credentials too: anyone who had the old ENCRYPTION_KEY could 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_key and langsmith_api_key entries return configured: 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/completions succeeds without a “missing API key” error.

See also

Last updated on