zoxaAI
Homepage
API ReferenceAgents

Update an agent

PATCH /api/v1/agents/{agent_uuid} — partial update via recursive deep-merge.

PATCH /api/v1/agents/{agent_uuid}

Partially update a saved agent. The body is any subset of the Agent Config. It is deep-merged into the stored config, and the whole merged result is re-validated before it's written — an invalid merge result 400s without persisting anything.

Deep-merge rules

  • Two objects at the same key merge recursively — nested keys you didn't touch are preserved. Sending { "llm": { "temperature": 0.7 } } changes only temperature; provider, model, maxTokens, and prewarm stay as they were.
  • Any list, scalar, or null at a key replaces the stored value wholesale. Lists are never element-merged — sending tools swaps the entire array; sending greeting.firstMessages swaps the whole variants list.
  • contextVariables replaces wholesale even though it's an object — it's a user-keyed map, and recursive merging would make it append-only (you could add a key but never remove one). Always send the complete map you want.

Per-call changes use a transient agent, not PATCH

For "use this config for one call only," pass the full config inline as agent on POST /call instead of pointing at a saved agent. PATCH mutates the saved agent permanently and affects every future call — including active inbound numbers bound to it.

Authentication

X-API-Key: zsk_...

Path parameters

ParamTypeDescription
agent_uuidstring (UUID)The uuid of the agent to update.

Request body

Any subset of Agent Config fields. All optional.

BehaviorDetail
webhookSecret (top-level)Write-only. Popped before merge/validation and written straight to the row as the HMAC key for webhook signatures. Never returned.
Merged result fails validation400 with invalid_agent_config details; nothing is persisted.
Nested object block (e.g. llm, stt.soniox)Deep-merged — only the sub-fields you send change.
List field (e.g. tools, languages, userIdleMessages)Replaced wholesale — send the full array you want.
contextVariablesReplaced wholesale — send the complete map.

Concurrent PATCHes are safe: the merge runs against a row-locked read, so two simultaneous updates can't silently drop each other's changes.

Response

200 OK with the full updated agent envelope — same shape as GET /agents/{agent_uuid} (uuid, status, createdAt, updatedAt, config).

Examples

Change the opener

greeting is an object, so this merges — but firstMessages inside it is a list and replaces wholesale:

curl -X PATCH https://dashboard.zoxa.ai/api/v1/agents/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: zsk_..." \
  -H "Content-Type: application/json" \
  -d '{ "greeting": { "firstMessages": ["Hi, this is Aman from Acme."] } }'
await fetch(`https://dashboard.zoxa.ai/api/v1/agents/${agentUuid}`, {
  method: "PATCH",
  headers: { "X-API-Key": "zsk_...", "Content-Type": "application/json" },
  body: JSON.stringify({ greeting: { firstMessages: ["Hi, this is Aman from Acme."] } }),
});
import httpx

httpx.patch(
    f"https://dashboard.zoxa.ai/api/v1/agents/{agent_uuid}",
    headers={"X-API-Key": "zsk_..."},
    json={"greeting": {"firstMessages": ["Hi, this is Aman from Acme."]}},
)

Nudge one LLM knob

{ "llm": { "temperature": 0.7 } }

Only temperature changes — provider, model, maxTokens, and prewarm are preserved by the merge.

Swap the LLM entirely

{ "llm": { "provider": "anthropic", "model": "claude-sonnet-4-6", "temperature": 0.7 } }

Switch the voice provider

tts.provider selects which per-provider block is active — the blocks all coexist, so switching back later restores your previous tuning:

{ "tts": { "provider": "sarvam", "voice": "ishita" } }

Turn on filler words

{ "fillerWords": { "enabled": true, "words": "so, right, okay", "thresholdMs": 400 } }

Errors

StatusdetailWhen
400{ "error": "invalid_agent_config", "details": [...] }The deep-merged config failed validation — nothing is persisted.
400"No organization selected"Auth missing org context.
404"Agent not found"UUID doesn't exist in your org.

On this page