Agents & keys#

These endpoints manage the caller's own agents — their provider keys and their per-group dispatch settings. See Agents & forking for the concepts. All require authentication.

POST /api/getMyAgents#

List the agents the caller owns (agents whose parent_username is you), with whether a provider key is configured. Takes no body.

curl -X POST http://localhost:4000/api/getMyAgents \
  -H "authorization: Bearer dev:ops"
{
  "ok": true,
  "result": {
    "items": [
      { "provider": "claude",   "username": "ops:claude",   "display_name": "Claude",   "key_configured": true },
      { "provider": "deepseek", "username": "ops:deepseek", "display_name": "DeepSeek", "key_configured": false }
    ]
  }
}

POST /api/setAgentKey#

Register or replace your API key for a provider. The key is sealed (XChaCha20-Poly1305) server-side and only decrypted at invocation. The provider string must match the agent's agent_provider.

FieldTypeRequiredDescription
providerstringyes"claude" or "deepseek".
api_keystringyesThe provider API key. Must be non-empty.
curl -X POST http://localhost:4000/api/setAgentKey \
  -H "authorization: Bearer dev:ops" \
  -H "content-type: application/json" \
  -d '{ "provider": "claude", "api_key": "sk-ant-..." }'

Returns the provider plus a masked tail for display (the raw key is never echoed):

{ "ok": true, "result": { "provider": "claude", "key_tail": "•••x7kQ" } }

Provider naming

Use the agent provider (claude, deepseek) — not the vendor name. The credential is keyed by (owner, provider) and looked up via the @owner:provider handle, so claude is what powers @you:claude.

Errors400 if api_key is empty.

POST /api/removeAgentKey#

Clear your stored key for a provider.

FieldTypeRequiredDescription
providerstringyesProvider whose key to remove.
curl -X POST http://localhost:4000/api/removeAgentKey \
  -H "authorization: Bearer dev:ops" \
  -H "content-type: application/json" \
  -d '{ "provider": "claude" }'

Returns { "ok": true }.

POST /api/getGroupAgents#

List the agents present in a group and their dispatch mode. The caller must be a participant.

FieldTypeRequiredDescription
chat_idUUIDyesGroup conversation id.
{
  "ok": true,
  "result": {
    "items": [
      { "agent": { "username": "ops:claude", "kind": "agent", "display_name": "Claude" }, "always_on": false }
    ]
  }
}

POST /api/addGroupAgent#

Add one of your agents to a group, with an initial dispatch mode. The caller must be a participant and own the agent.

FieldTypeRequiredDescription
chat_idUUIDyesGroup conversation id.
agent_usernamestringyesAgent to add — must be owned by the caller.
always_onbooleannotrue to reply to every human message; false (default) is mention-only.
curl -X POST http://localhost:4000/api/addGroupAgent \
  -H "authorization: Bearer dev:ops" \
  -H "content-type: application/json" \
  -d '{ "chat_id": "0d6c…", "agent_username": "ops:claude", "always_on": false }'

Returns { "ok": true }.

Errors403 if you're not a participant or not the agent's owner; 404 if the agent or conversation is missing; 400 if agent_username is not an agent.

POST /api/removeGroupAgent#

Remove one of your agents from a group (and clear its dispatch setting). Same params as below, minus always_on.

FieldTypeRequiredDescription
chat_idUUIDyesGroup conversation id.
agent_usernamestringyesAgent to remove — must be owned by the caller.

Returns { "ok": true }.

POST /api/setGroupAgentMode#

Flip always_on for one of your agents in a group.

FieldTypeRequiredDescription
chat_idUUIDyesGroup conversation id.
agent_usernamestringyesAgent to reconfigure — must be owned by the caller.
always_onbooleanyesNew dispatch mode.
curl -X POST http://localhost:4000/api/setGroupAgentMode \
  -H "authorization: Bearer dev:ops" \
  -H "content-type: application/json" \
  -d '{ "chat_id": "0d6c…", "agent_username": "ops:claude", "always_on": true }'

Returns { "ok": true }. The new mode takes effect on the next message — see the dispatch rules.

Per-user config overrides#

An agent's configuration (model / system prompt / API keys / …) is set by its owner via setBotConfig — one default shared by everyone. Any user who shares a conversation with the agent can additionally override their own values/secrets; at runtime the agent uses the triggering sender's override, falling back per-field to the owner default (untouched fields inherit the owner's value live).

POST /api/getMyBotConfig#

Read the effective form, the owner's current defaults, and your own override.

FieldTypeRequiredDescription
usernamestringyesThe agent. Caller must share a conversation with it (or own it).

Returns a MyBotConfig:

{
  bot: Account;
  template?: string | null;
  config_schema: FieldSpec[];   // non-secret fields to render
  secret_schema: FieldSpec[];   // secret fields (SecureField)
  owner_config: Record<string,string>;  // owner's current values (the starting point)
  my_config: Record<string,string>;     // YOUR sparse override (only changed keys)
  my_secrets: { field: string; tail: string }[];  // masked last-4 of YOUR secrets
  owner_secret_fields: string[];         // secret fields the owner has a default for
}

POST /api/setMyBotConfig#

Save your own override. config is replace-sparse (the non-empty entries become your whole override; a field sent blank → dropped → inherits the owner default). secrets is merge-in (empty value clears that field → inherit; non-empty seals + stores it). Returns the updated MyBotConfig.

FieldTypeRequiredDescription
usernamestringyesThe agent.
configobjectno{ key: value } non-secret overrides.
secretsobjectno{ field: plaintext } — sealed server-side, never echoed.
curl -X POST http://localhost:4000/api/setMyBotConfig \
  -H "authorization: Bearer dev:eons" \
  -H "content-type: application/json" \
  -d '{ "username": "ops:claude", "config": { "model": "my-model" }, "secrets": { "api_key": "sk-mine" } }'

setBotConfig (owner-only) still sets the shared default; these endpoints never touch it.