> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Update an agent

PATCH https://api.jelliu.co/api/agents/{agentId}
Content-Type: application/json

Partially updates an agent and pushes the change to the voice provider. Only the fields sent are
changed and at least one must be defined; unknown keys are silently ignored. `safety`, `privacy`,
`analysis` and `toolkitAccess` are merged key by key with what is stored (see `UpdateAgentInput`);
`channels`, `skills`, `transferRules` and the other arrays replace the stored value.

The response is the raw updated row (see `AgentPatchedRow`), which differs from the shape returned
by the `GET` endpoints: config blocks keep their column names (`analysis_config`,
`supported_channels`, …) and derived fields are absent. Re-read with `GET /api/agents/{agentId}` if
you need the decorated shape.

**Approval gate.** If the workspace has change approvals enabled and the agent is marked
`high_risk`, a change to any behaviour field is held for review and answers
`409 AGENT_CHANGE_PENDING_APPROVAL` with the id of the change request — nothing is saved. An admin
other than the proposer approves it via the agent change-requests endpoints, which applies it then.
A PATCH that touches only non-behaviour fields (for example `name` or `ttsSpeed`) is applied at once.

**Side effects.** Screens a changed prompt, first message or objection handlers for fraud (LLM
call). Saves the row, re-derives the voice-engine agent configuration (prompt, voice, tuning,
analysis, guardrails, privacy, transfers, duration cap) and records a version in the agent's change
history. `mcpEnabled: false` detaches the MCP servers already attached. A changed
`allowedToolkits`, `disabledTools` or `toolkitAccess` queues a tool-surface sync with the connected
apps provider; the response then shows `tool_surface_status: syncing` (or `pending_provisioning`
while the agent is not provisioned yet). Changing `category` without sending `analysis` replaces the
analysis criteria with the new objective's defaults. Writes an audit entry.

**Consistency.** The provider re-derive runs after the row is saved; if it fails the edit stays
saved and a periodic re-sync reconciles the provider later. For legacy agents that do not derive
from a category base, the provider is updated first and a provider failure answers `502` with
nothing saved.

**Idempotency.** Retrying the same body converges to the same stored state and is safe, but it
re-runs fraud screening and the provider sync. On a high-risk agent each retry opens another pending
change request.

**Webhook events.** `audit.log_recorded` (when subscribed). See [Webhooks](/webhooks).

**Access**

* **Required scope:** `write`. Signed-in users need the owner, admin or member role.
* **Rate limit:** Agent mutations — 10 requests/min per workspace, shared with every agent, A/B variant, tool and knowledge write; the request also counts against the General API limit. See [Rate limits](/rate-limits).
* **Plan:** Available on every plan. Switching to a custom cloned voice requires Enterprise.

Reference: https://developer.jelliu.co/api-reference/agents/patch-agents-by-agent-id

## Authentication

- `Authorization` header (bearer token, required) — Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

## Request

### Path parameters

- `agentId` (string, required) — UUID of the agent to update. An id from another workspace answers `404`; a value that is not a UUID answers `400`.

### Body (application/json)

This endpoint expects an object.

- `name` (string, optional) — New display name. Trimmed; blank is ignored.
- `channels` (list of enum, optional) — Channels the agent may answer on. Replaces the whole list — send the complete set, not the channel you are adding. Behaviour field.
  - Allowed values: `voice`, `whatsapp`, `email`, `webchat`, `instagram`, `messenger`
- `allowedToolkits` (list of string, optional, nullable) — Connected-app slugs this agent may use (up to 300). `null` = no restriction (every connected app, including ones connected later); `[]` = none. A change queues a tool-surface sync. Behaviour field.
- `mcpEnabled` (boolean, optional) — Whether the agent may carry MCP servers. `false` also detaches the servers already attached; `true` attaches nothing by itself. Behaviour field.
- `disabledTools` (map from string to list of string, optional, nullable) — Per-app denylist of tool slugs (up to 500 per app), replacing the stored map. `null` and `{}` both mean nothing is disabled. A change queues a tool-surface sync. Behaviour field.
- `toolkitAccess` (map from string to enum, optional, nullable) — Per-app access level merged into the stored map: `read` (read-only tools) or `read_write`. An app not listed keeps its stored level, and an app never listed has `read_write`. `null` clears the map (every app back to `read_write`). A change queues a tool-surface sync. Behaviour field.
  - Allowed values: `read`, `read_write`
- `voiceId` (string, optional) — New voice id. Trimmed; blank is ignored. A custom cloned voice requires Enterprise.
- `language` (enum, optional) — New main language; the same labels as on create are normalised. See `CreateAgentInput.language`.
  - Allowed values: `es`, `es-CO`, `es-MX`, `es-AR`, `es-neutral`, `en`, `en-US`, `pt`, `pt-BR`
- `templateId` (string, optional, nullable) — Link a different objective template, or `null` to drop the template layer. Must be accessible to the workspace (`400` otherwise). Behaviour field.
- `category` (enum, optional) — New objective (see `CreateAgentInput.category`), or `null` to fall back to the template's category. When it actually changes and `analysis` is not sent in the same request, the analysis criteria are replaced with the new objective's defaults. Behaviour field.
  - Allowed values: `sales`, `support`, `scheduling`, `surveys`, `collections`, `retention`, `notifications`, `interview`, `language_assessment`, `personal`, `general`
- `systemPrompt` (string, optional) — New workspace prompt layer, 10-8000 characters after trimming. Blank is ignored (it cannot be emptied here). Screened for fraud. Behaviour field.
- `firstMessage` (string, optional) — New opening line. Trimmed; blank is ignored. Screened for fraud. Behaviour field.
- `dynamicVariables` (map from string to string, optional) — Replaces the stored key/value strings (at most 50). Not currently applied to conversations.
- `maxCallDurationSeconds` (double, optional) — New hard cap on voice call length, in seconds (30-3600). Behaviour field.
- `responseDelaySeconds` (integer, optional) — Fixed pause before replying on text channels, in seconds (0-86400). Above 0 it overrides human pacing; 0 uses human pacing. Voice is unaffected.
- `humanPacingEnabled` (boolean, optional) — Text channels reply on a growing delay ladder (about 10 s, then 1 min, up to 2 min) instead of instantly. Turn off for lines where speed matters (OTP relays, outage notices). Voice is unaffected.
- `objectionHandlers` (map from string to string, optional) — Replaces the stored objection map (at most 50). Screened for fraud. Not currently applied to conversations. Behaviour field.
- `escalationRules` (list of object, optional) — Replaces the stored escalation rules. Not currently applied to conversations. Behaviour field.
  - `trigger` (enum, required) — Condition that fires the rule: `negative_sentiment`, `explicit_request` (the contact asks for a human), `repeated_objection`, `timeout`.
    - Allowed values: `negative_sentiment`, `explicit_request`, `repeated_objection`, `timeout`
  - `threshold` (double, required) — Numeric threshold for the trigger. Must be 0 or greater — negative values are rejected with `400`.
  - `action` (enum, required) — What to do when the rule fires: `transfer_to_human`, `end_call_politely`, `schedule_callback`.
    - Allowed values: `transfer_to_human`, `end_call_politely`, `schedule_callback`
- `transferPhoneNumber` (string, optional, nullable) — New E.164 transfer destination (checked against the dial policy), or `null` to remove it. Behaviour field.
- `discloseAi` (boolean, optional, nullable) — `true`/`false` sets this agent's AI-disclosure decision; `null` clears it back to the country policy.
- `enableVoicemailDetection` (boolean, optional) — Detect voicemail on outbound calls. Behaviour field.
- `transferRules` (list of object, optional) — Replaces the in-call hand-off rules (up to 10). Behaviour field.
  - `targetAgentId` (string, required) — Id of the agent that takes over. It must be provisioned; otherwise the rule is skipped when the provider configuration is built.
  - `condition` (string, required) — Natural-language condition the agent evaluates to decide the hand-off (1-500 characters).
  - `transferMessage` (string, optional) — What the agent says right before handing off (up to 500 characters).
- `department` (string, optional, nullable) — Routing department. Trimmed; `null` clears it, blank is ignored.
- `skills` (list of string, optional) — Replaces the routing skills (up to 50).
- `safety` (object, optional, nullable) — Guardrail topics, merged key by key (`null` on a key clears it; `null` for the block clears both). Behaviour field.
  - `blockedInputTopics` (list of string, optional, nullable) — Topics the agent refuses when the contact raises them (up to 20, 200 characters each).
  - `blockedOutputTopics` (list of string, optional, nullable) — Topics the agent must never talk about (up to 20, 200 characters each).
- `privacy` (object, optional, nullable) — Privacy settings, merged key by key. No defaults are re-applied on update. Behaviour field.
  - `redactPiiAudio` (boolean, optional) — Delete the transcript and personal data after processing.
  - `deleteAudioAfterProcessing` (boolean, optional) — Delete the call audio after processing.
  - `retentionDays` (integer, optional) — Retention window in days (0-365).
- `analysis` (object, optional, nullable) — Post-call analysis, merged per half: send only `successEvaluation` or only `dataCollection` to replace that half; `null` on a half clears it. New criterion ids starting with `qa_` are rejected with `400`. Behaviour field.
  - `successEvaluation` (list of object, optional, nullable) — Replaces the success criteria (up to 30).
    - `id` (string, required) — Stable identifier (1-100 characters). Must not start with `qa_` unless already stored.
    - `description` (string, required) — What the evaluator checks in the transcript (5-1000 characters). Write it in the agent's language.
  - `dataCollection` (list of object, optional, nullable) — Replaces the extraction fields (up to 40).
    - `id` (string, required) — Stable identifier (1-100 characters); results are keyed by it.
    - `dataType` (enum, required) — Type of the extracted value: `string`, `boolean`, `integer` or `number`.
      - Allowed values: `string`, `boolean`, `integer`, `number`
    - `description` (string, required) — Extraction instruction for the evaluator (5-1000 characters).
    - `enum` (list of string, optional) — Closed vocabulary for a `string` field (1-100 characters each after trimming, no duplicates, up to 50). Sending it on any other `dataType` is rejected with `400`. An empty array means no constraint.
- `expressiveMode` (boolean, optional) — Expressive voice delivery. See `CreateAgentInput.expressiveMode`.
- `toolCallSoundEnabled` (boolean, optional) — Sound while a tool runs on voice calls.
- `textNormalisationType` (enum, optional) — How numbers and symbols become speakable text. See `CreateAgentInput.textNormalisationType`.
  - Allowed values: `system_prompt`, `elevenlabs`
- `turnTimeout` (double, optional) — Voice only — seconds of silence before the agent takes the turn (1-10).
- `turnEagerness` (enum, optional) — How eagerly the voice agent takes the turn. See `CreateAgentInput.turnEagerness`.
  - Allowed values: `patient`, `normal`, `eager`, `low`, `medium`, `high`
- `ttsSpeed` (double, optional) — Speech rate, 0.5-2.
- `ttsStability` (double, optional) — Voice stability, 0-1.
- `ttsSimilarityBoost` (double, optional) — Adherence to the original voice, 0-1.
- `highRisk` (boolean, optional) — Marks the agent for change approval (effective only when the workspace has approvals enabled). Marking an agent applies at once; unmarking an agent that is already marked is itself held for approval.

## Response

### 200

The updated row (raw, not decorated).

- `data` (object, required) — Response of `PATCH /api/agents/{agentId}`: the raw updated row. Unlike the other agent responses it is NOT decorated — the JSON config columns keep their column names (`supported_channels`, `analysis_config`, `safety_config`, `privacy_config`) and the derived fields (`channels`, `objective`, `analysis`, `safety`, `privacy`, `mcpEnabled`, `toolkitAccessEnforced`, `total_calls`, …) are absent. Every column of `AgentRowBase` is present.
  - `id` (string, optional) — Unique identifier of the agent.
  - `tenant_id` (string, optional) — Workspace that owns the agent.
  - `name` (string, optional) — Display name.
  - `voice_id` (string, optional) — Voice id.
  - `language` (enum, optional) — Main language (see `CreateAgentInput.language` for each value).
    - Allowed values: `es`, `es-CO`, `es-MX`, `es-AR`, `es-neutral`, `en`, `en-US`, `pt`, `pt-BR`
  - `category` (enum, optional) — The agent's objective (see `CreateAgentInput.category` for each value); `null` when neither the agent nor its template sets one.
    - Allowed values: `sales`, `support`, `scheduling`, `surveys`, `collections`, `retention`, `notifications`, `interview`, `language_assessment`, `personal`, `general`
  - `template_id` (string, optional, nullable) — Linked objective template, or `null`.
  - `system_prompt` (string, optional) — The workspace's own prompt layer (may be empty).
  - `first_message` (string, optional) — Opening line (generated from the agent and business name when none was given).
  - `chat_system_prompt` (string, optional, nullable) — Legacy text-channel prompt; not writable through this API.
  - `dynamic_variables` (map from string to string, optional) — Stored key/value strings. Not currently applied to conversations.
  - `objection_handlers` (map from string to string, optional) — Stored objection → answer map. Not currently applied to conversations.
  - `escalation_rules` (list of object, optional) — Stored escalation rules. Not currently applied to conversations.
    - `trigger` (enum, required) — Condition that fires the rule: `negative_sentiment`, `explicit_request` (the contact asks for a human), `repeated_objection`, `timeout`.
      - Allowed values: `negative_sentiment`, `explicit_request`, `repeated_objection`, `timeout`
    - `threshold` (double, required) — Numeric threshold for the trigger. Must be 0 or greater — negative values are rejected with `400`.
    - `action` (enum, required) — What to do when the rule fires: `transfer_to_human`, `end_call_politely`, `schedule_callback`.
      - Allowed values: `transfer_to_human`, `end_call_politely`, `schedule_callback`
  - `transfer_rules` (list of object, optional) — In-call hand-offs to other agents.
    - `targetAgentId` (string, required) — Id of the agent that takes over. It must be provisioned; otherwise the rule is skipped when the provider configuration is built.
    - `condition` (string, required) — Natural-language condition the agent evaluates to decide the hand-off (1-500 characters).
    - `transferMessage` (string, optional) — What the agent says right before handing off (up to 500 characters).
  - `transfer_phone_number` (string, optional, nullable) — E.164 number live calls may be transferred to.
  - `max_call_duration_seconds` (integer, optional) — Hard cap on voice call length, in seconds.
  - `response_delay_seconds` (integer, optional) — Fixed pause before replying on text channels (0 = use human pacing).
  - `human_pacing_enabled` (boolean, optional) — Whether text replies use the growing human-pacing delay.
  - `enable_voicemail_detection` (boolean, optional) — Whether outbound calls detect voicemail and hang up.
  - `disclose_ai` (boolean, optional, nullable) — `null` means the country policy decides whether the agent says it is an AI.
  - `expressive_mode` (boolean, optional) — Expressive voice delivery.
  - `tool_call_sound_enabled` (boolean, optional) — Sound while a tool runs on voice calls.
  - `text_normalisation_type` (enum, optional) — How numbers and symbols become speakable text: `elevenlabs` or `system_prompt`.
    - Allowed values: `system_prompt`, `elevenlabs`
  - `turn_timeout` (double, optional) — Seconds of silence before the voice agent takes the turn.
  - `turn_eagerness` (enum, optional) — Turn-taking eagerness: `patient`, `normal`, `eager`. Legacy values `low`, `medium` and `high` may still appear on older agents.
    - Allowed values: `patient`, `normal`, `eager`, `low`, `medium`, `high`
  - `tts_speed` (double, optional) — Speech rate (0.5-2).
  - `tts_stability` (double, optional) — Voice stability (0-1).
  - `tts_similarity_boost` (double, optional) — Adherence to the original voice (0-1).
  - `department` (string, optional, nullable) — Routing department.
  - `skills` (list of string, optional, nullable) — Routing skills.
  - `allowed_toolkits` (list of string, optional, nullable) — Connected-app slugs this agent may use. `null` = no restriction; `[]` = none.
  - `disabled_tools` (map from string to list of string, optional, nullable) — Per-app denylist of tool slugs.
  - `toolkit_access` (map from string to enum, optional, nullable) — Per-app access level (`read` or `read_write`). An app not listed has `read_write`.
    - Allowed values: `read`, `read_write`
  - `mcp_enabled` (boolean, optional) — Whether the agent may carry MCP servers.
  - `tool_surface_status` (enum, optional) — State of the agent's connected-app tool surface at the providers: `synced` (applied), `syncing` (a change is being applied), `error` (the last sync failed; see `tool_surface_error` — re-send the same value to retry), `pending_provisioning` (saved, applied once the agent is provisioned).
    - Allowed values: `syncing`, `synced`, `error`, `pending_provisioning`
  - `tool_surface_error` (string, optional, nullable) — Reason the last tool-surface sync failed, or `null`.
  - `tool_surface_updated_at` (datetime, optional, nullable) — When the tool-surface status last changed.
  - `paused_at` (datetime, optional, nullable) — When the agent was paused (it stops taking actions and conversations), or `null` when running. Managed by the pause endpoints.
  - `created_at` (datetime, optional) — Creation time (UTC). Also the list pagination cursor.
  - `updated_at` (datetime, optional) — Last change to the row (UTC).
  - `tool_profile` (string, optional, nullable) — Retired; kept for older agents.
  - `base_prompt_version` (string, optional, nullable) — Fingerprint of the platform prompt layers last pushed to the provider; `null` before provisioning.
  - `workflow` (object, optional, nullable) — Stored conversation graph, or `null` (see `GET /api/agents/{agentId}/workflow`). Only returned by `GET /api/agents/{agentId}` and `PATCH`.
    - `version` (enum, required) — Schema version. Always `1`.
      - Allowed values: `1`
    - `entryNodeId` (string, required) — Node the conversation enters first; must be one of `nodes`.
    - `nodes` (list of object, required) — Phases (`subagent`) and terminal nodes (`end`).
      - `id` (string, required) — Node id, unique in the graph; `start` and `start_node` are reserved.
      - `type` (enum, required) — Node kind.
        - Allowed values: `subagent`, `end`
      - `label` (string, required) — Human-readable name shown in the dashboard and the provider's editor.
      - `prompt` (string, optional) — Subagent only — phase instructions appended to the agent's base prompt.
      - `llm` (string, optional) — Subagent only — provider LLM id for this phase. Not validated by Jelliu; an unknown id makes the push fail (502) and the previous graph is kept.
      - `turn` (object, optional) — Subagent only — per-phase turn taking. Must set at least one field; unknown keys are rejected.
        - `eagerness` (enum, optional) — How quickly the agent takes its turn.
          - Allowed values: `patient`, `normal`, `eager`
        - `timeoutSeconds` (double, optional) — Seconds of silence after which the agent takes the turn.
      - `entryBehavior` (enum, optional) — Subagent only — what happens when the phase is entered. Omit to leave the provider default.
        - Allowed values: `auto`, `generate_immediately`
      - `toolIds` (list of string, optional) — Subagent only — voice-engine tool ids added for this phase. Not Jelliu tool UUIDs.
      - `knowledgeBaseIds` (list of string, optional) — Subagent only — provider knowledge-base document ids added for this phase.
      - `requires` (list of string, optional) — Subagent only — manifest of tool names the phase cannot work without; alternatives joined with `|`. Stored for readiness checks; not sent to the provider and not enforced when saving.
    - `edges` (list of object, required) — Transitions between nodes, evaluated by the provider during the conversation.
      - `id` (string, required) — Edge id, unique in the graph.
      - `source` (string, required) — Node the edge leaves; must not be an `end` node.
      - `target` (string, required) — Node the edge enters; must differ from `source`.
      - `condition` (object, required) — `unconditional` (taken immediately; not allowed into an `end` node); `llm` with `text` (3–500 chars) judged by the model; or `expression` with `expression` and `textFallback` (20–600 chars, the same decision in words). Expression edges are sent to the provider using `textFallback`.
        - `type` (enum, required) — Condition kind.
          - Allowed values: `unconditional`, `llm`, `expression`
        - `text` (string, optional) — `llm` only. The condition in plain language.
        - `expression` (object, optional) — Expression tree over `state` variables and literals, used by `expression` edges. Leaves are `variable`, `number`, `boolean`, `string` and `llm`; `and`/`or` take 2–6 `children`; comparisons (`lt`, `lte`, `gt`, `gte`, `eq`, `neq`) take `left` and `right`. Every `variable` must name a variable declared in the workflow's `state`. Stored as authored. When pushed through `PUT /api/agents/{agentId}/workflow`, the edge is rendered with its `textFallback` instead, so this tree documents the intended rule rather than being evaluated by the provider.
          - `type` (enum, required) — Node kind: `variable`, `number`, `boolean`, `string`, `llm`, `and`, `or`, `lt`, `lte`, `gt`, `gte`, `eq`, `neq`.
            - Allowed values: `variable`, `number`, `boolean`, `string`, `llm`, `and`, `or`, `lt`, `lte`, `gt`, `gte`, `eq`, `neq`
          - `name` (string, optional) — `variable` only. A name declared in `state`.
          - `value` (any, optional) — `number`, `boolean` or `string` literal (strings max 200 chars).
          - `text` (string, optional) — `llm` only. The question the model answers.
          - `returns` (enum, optional) — `llm` only. Type of the model's answer.
            - Allowed values: `boolean`, `number`
          - `children` (list of object, optional) — `and`/`or` only. Operands.
          - `left` (object, optional) — Expression tree over `state` variables and literals, used by `expression` edges. Leaves are `variable`, `number`, `boolean`, `string` and `llm`; `and`/`or` take 2–6 `children`; comparisons (`lt`, `lte`, `gt`, `gte`, `eq`, `neq`) take `left` and `right`. Every `variable` must name a variable declared in the workflow's `state`. Stored as authored. When pushed through `PUT /api/agents/{agentId}/workflow`, the edge is rendered with its `textFallback` instead, so this tree documents the intended rule rather than being evaluated by the provider.
          - `right` (object, optional) — Expression tree over `state` variables and literals, used by `expression` edges. Leaves are `variable`, `number`, `boolean`, `string` and `llm`; `and`/`or` take 2–6 `children`; comparisons (`lt`, `lte`, `gt`, `gte`, `eq`, `neq`) take `left` and `right`. Every `variable` must name a variable declared in the workflow's `state`. Stored as authored. When pushed through `PUT /api/agents/{agentId}/workflow`, the edge is rendered with its `textFallback` instead, so this tree documents the intended rule rather than being evaluated by the provider.
        - `textFallback` (string, optional) — `expression` only. The same decision written as a judgement; this is what the provider evaluates.
    - `templateId` (string, optional) — Informational — the template this graph started from. Stored, not sent to the provider.
    - `state` (list of object, optional) — Variables the agent scores during the conversation through the `update_state` system tool, for `expression` edges to read. Declaring any attaches that tool and seeds each variable with a placeholder value at the provider.
      - `name` (string, required) — Variable name referenced by `variable` expressions.
      - `description` (string, required) — The scoring rubric the model follows to set the value. Be concrete about the scale.
      - `valueType` (enum, required) — Type of the value.
        - Allowed values: `number`, `boolean`, `string`
  - `tool_surface_version` (integer, optional) — Counter bumped on every queued tool-surface sync.
  - `high_risk` (boolean, optional) — When true (and the workspace enabled approvals) behaviour changes need a second user's approval.
  - `paused_by` (string, optional, nullable) — Identity (user or API key) that paused the agent, or `null`.
  - `paused_reason` (string, optional, nullable) — Reason given when pausing, or `null`.
  - `action_limits` (map from string to any, optional, nullable) — Per-agent action budget (`perMinute`, `perHour`, `perDay`, `perConversation`, `maxSpendPerDayCents`), managed by the agent controls endpoints; `null` = no limits.
  - `deleted_at` (any, optional) — Always `null` on returned agents (deleted agents are never returned).
  - `supported_channels` (list of enum, optional, nullable) — Channels the agent answers on (the `channels` of the decorated shape).
    - Allowed values: `voice`, `whatsapp`, `email`, `webchat`, `instagram`, `messenger`
  - `analysis_config` (object, optional, nullable) — Stored post-call analysis (the `analysis` of the decorated shape), or `null`.
    - `successEvaluation` (list of object, optional) — Success criteria (up to 30).
      - `id` (string, required) — Stable identifier (1-100 characters). Must not start with `qa_` unless already stored.
      - `description` (string, required) — What the evaluator checks in the transcript (5-1000 characters). Write it in the agent's language.
    - `dataCollection` (list of object, optional) — Fields to extract (up to 40).
      - `id` (string, required) — Stable identifier (1-100 characters); results are keyed by it.
      - `dataType` (enum, required) — Type of the extracted value: `string`, `boolean`, `integer` or `number`.
        - Allowed values: `string`, `boolean`, `integer`, `number`
      - `description` (string, required) — Extraction instruction for the evaluator (5-1000 characters).
      - `enum` (list of string, optional) — Closed vocabulary for a `string` field (1-100 characters each after trimming, no duplicates, up to 50). Sending it on any other `dataType` is rejected with `400`. An empty array means no constraint.
  - `safety_config` (object, optional, nullable) — Stored guardrails (the `safety` of the decorated shape), or `null`.
    - `blockedInputTopics` (list of string, optional) — Topics the agent declines when the contact raises them (up to 20, 200 characters each).
    - `blockedOutputTopics` (list of string, optional) — Topics the agent must never talk about (up to 20, 200 characters each).
  - `privacy_config` (object, optional, nullable) — Stored privacy settings (the `privacy` of the decorated shape), or `null`.
    - `redactPiiAudio` (boolean, optional, default: true) — Delete the transcript and personal data at the voice provider after processing.
    - `deleteAudioAfterProcessing` (boolean, optional, default: true) — Delete the call audio at the voice provider after processing.
    - `retentionDays` (integer, optional, default: 90) — Retention window in days (0-365) stored with the agent. For agents derived from a category base (every provisioned agent today) the provider-side audio retention follows the workspace plan.

## Errors

### 400 Bad Request Error

Invalid id or body (`details` is Zod's flattened error; an empty body or one whose fields are all blank lands in `formErrors`), inaccessible template, or a new `qa_` analysis criterion.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 401 Unauthorized Error

No usable credential. Either the `Authorization` header is missing or is not a well-formed `Bearer jl_…` key, or the key is unknown, revoked or expired. Do not retry with the same key. See [Authentication](/authentication#401-unauthorized).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 403 Forbidden Error

Content blocked by fraud screening or transfer destination refused by the dial policy (`COMPLIANCE_BLOCKED`), cloned voice outside Enterprise (`BILLING_ERROR`), API-key scope or user role (`FORBIDDEN`), or suspended workspace (`TENANT_SUSPENDED`).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 404 Not Found Error

No live agent with this id in the workspace.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 409 Conflict Error

The change was held for approval (high-risk agent on a workspace with approvals enabled). Nothing was saved. `metadata.changeRequestId` identifies the pending request and `metadata.fields` lists the held behaviour fields, comma-separated.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 429 Too Many Requests Error

Agent-mutation budget (10/min) or the general API limit exhausted.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 502 Bad Gateway Error

The voice provider rejected or could not receive the update (legacy agents only); nothing was saved.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

## Examples

### Agents_patchAgentsByAgentId_example

**Request**

```json
undefined
```

**Response**

```json
{
  "data": {
    "id": "7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "name": "Sofía — Ventas",
    "voice_id": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales",
    "template_id": null,
    "system_prompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": "+573001234567",
    "max_call_duration_seconds": 240,
    "response_delay_seconds": 0,
    "human_pacing_enabled": true,
    "enable_voicemail_detection": true,
    "disclose_ai": null,
    "expressive_mode": false,
    "tool_call_sound_enabled": false,
    "text_normalisation_type": "elevenlabs",
    "turn_timeout": 1.5,
    "turn_eagerness": "normal",
    "tts_speed": 0.96,
    "tts_stability": 0.5,
    "tts_similarity_boost": 0.75,
    "department": "Ventas",
    "skills": [
      "suscripciones",
      "facturación"
    ],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": "2026-09-10T18:22:55.000Z",
    "paused_at": null,
    "created_at": "2026-09-01T15:04:05.000Z",
    "updated_at": "2026-09-15T14:30:02.000Z",
    "tool_profile": null,
    "base_prompt_version": "9f2c4e7a1b3d5f6e8a0c2b4d6f8e0a1c3b5d7f9e1a2c4b6d8f0e2a4c6b8d0f1e",
    "workflow": null,
    "tool_surface_version": 3,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "supported_channels": [
      "voice",
      "whatsapp"
    ],
    "analysis_config": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
        }
      ]
    },
    "safety_config": null,
    "privacy_config": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    }
  }
}
```

**SDK Code**

```python Agents_patchAgentsByAgentId_example
import requests

url = "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

headers = {"Authorization": "Bearer <token>"}

response = requests.patch(url, headers=headers)

print(response.json())
```

```javascript Agents_patchAgentsByAgentId_example
const url = 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c';
const options = {method: 'PATCH', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Agents_patchAgentsByAgentId_example
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

	req, _ := http.NewRequest("PATCH", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Agents_patchAgentsByAgentId_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Agents_patchAgentsByAgentId_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Agents_patchAgentsByAgentId_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Agents_patchAgentsByAgentId_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Agents_patchAgentsByAgentId_example
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Change the prompt and first message

**Request**

```json
{
  "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
  "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?"
}
```

**Response**

```json
{
  "data": {
    "id": "7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "name": "Sofía — Ventas",
    "voice_id": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales",
    "template_id": null,
    "system_prompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": "+573001234567",
    "max_call_duration_seconds": 240,
    "response_delay_seconds": 0,
    "human_pacing_enabled": true,
    "enable_voicemail_detection": true,
    "disclose_ai": null,
    "expressive_mode": false,
    "tool_call_sound_enabled": false,
    "text_normalisation_type": "elevenlabs",
    "turn_timeout": 1.5,
    "turn_eagerness": "normal",
    "tts_speed": 0.96,
    "tts_stability": 0.5,
    "tts_similarity_boost": 0.75,
    "department": "Ventas",
    "skills": [
      "suscripciones",
      "facturación"
    ],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": "2026-09-10T18:22:55.000Z",
    "paused_at": null,
    "created_at": "2026-09-01T15:04:05.000Z",
    "updated_at": "2026-09-15T14:30:02.000Z",
    "tool_profile": null,
    "base_prompt_version": "9f2c4e7a1b3d5f6e8a0c2b4d6f8e0a1c3b5d7f9e1a2c4b6d8f0e2a4c6b8d0f1e",
    "workflow": null,
    "tool_surface_version": 3,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "supported_channels": [
      "voice",
      "whatsapp"
    ],
    "analysis_config": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
        }
      ]
    },
    "safety_config": null,
    "privacy_config": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    }
  }
}
```

**SDK Code**

```python Change the prompt and first message
import requests

url = "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

payload = {
    "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript Change the prompt and first message
const url = 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"systemPrompt":"Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.","firstMessage":"Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Change the prompt and first message
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

	payload := strings.NewReader("{\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?\"\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Change the prompt and first message
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?\"\n}"

response = http.request(request)
puts response.read_body
```

```java Change the prompt and first message
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?\"\n}")
  .asString();
```

```php Change the prompt and first message
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c', [
  'body' => '{
  "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
  "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Change the prompt and first message
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Change the prompt and first message
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
  "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Voice tuning only (never held for approval)

**Request**

```json
{
  "turnEagerness": "patient",
  "ttsSpeed": 0.92,
  "ttsStability": 0.6
}
```

**Response**

```json
{
  "data": {
    "id": "7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "name": "Sofía — Ventas",
    "voice_id": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales",
    "template_id": null,
    "system_prompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": "+573001234567",
    "max_call_duration_seconds": 240,
    "response_delay_seconds": 0,
    "human_pacing_enabled": true,
    "enable_voicemail_detection": true,
    "disclose_ai": null,
    "expressive_mode": false,
    "tool_call_sound_enabled": false,
    "text_normalisation_type": "elevenlabs",
    "turn_timeout": 1.5,
    "turn_eagerness": "normal",
    "tts_speed": 0.96,
    "tts_stability": 0.5,
    "tts_similarity_boost": 0.75,
    "department": "Ventas",
    "skills": [
      "suscripciones",
      "facturación"
    ],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": "2026-09-10T18:22:55.000Z",
    "paused_at": null,
    "created_at": "2026-09-01T15:04:05.000Z",
    "updated_at": "2026-09-15T14:30:02.000Z",
    "tool_profile": null,
    "base_prompt_version": "9f2c4e7a1b3d5f6e8a0c2b4d6f8e0a1c3b5d7f9e1a2c4b6d8f0e2a4c6b8d0f1e",
    "workflow": null,
    "tool_surface_version": 3,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "supported_channels": [
      "voice",
      "whatsapp"
    ],
    "analysis_config": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
        }
      ]
    },
    "safety_config": null,
    "privacy_config": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    }
  }
}
```

**SDK Code**

```python Voice tuning only (never held for approval)
import requests

url = "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

payload = {
    "turnEagerness": "patient",
    "ttsSpeed": 0.92,
    "ttsStability": 0.6
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript Voice tuning only (never held for approval)
const url = 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"turnEagerness":"patient","ttsSpeed":0.92,"ttsStability":0.6}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Voice tuning only (never held for approval)
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

	payload := strings.NewReader("{\n  \"turnEagerness\": \"patient\",\n  \"ttsSpeed\": 0.92,\n  \"ttsStability\": 0.6\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Voice tuning only (never held for approval)
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"turnEagerness\": \"patient\",\n  \"ttsSpeed\": 0.92,\n  \"ttsStability\": 0.6\n}"

response = http.request(request)
puts response.read_body
```

```java Voice tuning only (never held for approval)
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"turnEagerness\": \"patient\",\n  \"ttsSpeed\": 0.92,\n  \"ttsStability\": 0.6\n}")
  .asString();
```

```php Voice tuning only (never held for approval)
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c', [
  'body' => '{
  "turnEagerness": "patient",
  "ttsSpeed": 0.92,
  "ttsStability": 0.6
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Voice tuning only (never held for approval)
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"turnEagerness\": \"patient\",\n  \"ttsSpeed\": 0.92,\n  \"ttsStability\": 0.6\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Voice tuning only (never held for approval)
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "turnEagerness": "patient",
  "ttsSpeed": 0.92,
  "ttsStability": 0.6
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Serve voice and WhatsApp only (replaces the list)

**Request**

```json
{
  "channels": [
    "voice",
    "whatsapp"
  ]
}
```

**Response**

```json
{
  "data": {
    "id": "7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "name": "Sofía — Ventas",
    "voice_id": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales",
    "template_id": null,
    "system_prompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": "+573001234567",
    "max_call_duration_seconds": 240,
    "response_delay_seconds": 0,
    "human_pacing_enabled": true,
    "enable_voicemail_detection": true,
    "disclose_ai": null,
    "expressive_mode": false,
    "tool_call_sound_enabled": false,
    "text_normalisation_type": "elevenlabs",
    "turn_timeout": 1.5,
    "turn_eagerness": "normal",
    "tts_speed": 0.96,
    "tts_stability": 0.5,
    "tts_similarity_boost": 0.75,
    "department": "Ventas",
    "skills": [
      "suscripciones",
      "facturación"
    ],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": "2026-09-10T18:22:55.000Z",
    "paused_at": null,
    "created_at": "2026-09-01T15:04:05.000Z",
    "updated_at": "2026-09-15T14:30:02.000Z",
    "tool_profile": null,
    "base_prompt_version": "9f2c4e7a1b3d5f6e8a0c2b4d6f8e0a1c3b5d7f9e1a2c4b6d8f0e2a4c6b8d0f1e",
    "workflow": null,
    "tool_surface_version": 3,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "supported_channels": [
      "voice",
      "whatsapp"
    ],
    "analysis_config": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
        }
      ]
    },
    "safety_config": null,
    "privacy_config": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    }
  }
}
```

**SDK Code**

```python Serve voice and WhatsApp only (replaces the list)
import requests

url = "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

payload = { "channels": ["voice", "whatsapp"] }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript Serve voice and WhatsApp only (replaces the list)
const url = 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"channels":["voice","whatsapp"]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Serve voice and WhatsApp only (replaces the list)
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

	payload := strings.NewReader("{\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ]\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Serve voice and WhatsApp only (replaces the list)
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java Serve voice and WhatsApp only (replaces the list)
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ]\n}")
  .asString();
```

```php Serve voice and WhatsApp only (replaces the list)
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c', [
  'body' => '{
  "channels": [
    "voice",
    "whatsapp"
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Serve voice and WhatsApp only (replaces the list)
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Serve voice and WhatsApp only (replaces the list)
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["channels": ["voice", "whatsapp"]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Restrict connected apps

**Request**

```json
{
  "allowedToolkits": [
    "hubspot",
    "gmail"
  ],
  "disabledTools": {
    "hubspot": [
      "HUBSPOT_DELETE_DEAL"
    ]
  },
  "toolkitAccess": {
    "gmail": "read"
  }
}
```

**Response**

```json
{
  "data": {
    "id": "7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "name": "Sofía — Ventas",
    "voice_id": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales",
    "template_id": null,
    "system_prompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": "+573001234567",
    "max_call_duration_seconds": 240,
    "response_delay_seconds": 0,
    "human_pacing_enabled": true,
    "enable_voicemail_detection": true,
    "disclose_ai": null,
    "expressive_mode": false,
    "tool_call_sound_enabled": false,
    "text_normalisation_type": "elevenlabs",
    "turn_timeout": 1.5,
    "turn_eagerness": "normal",
    "tts_speed": 0.96,
    "tts_stability": 0.5,
    "tts_similarity_boost": 0.75,
    "department": "Ventas",
    "skills": [
      "suscripciones",
      "facturación"
    ],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": "2026-09-10T18:22:55.000Z",
    "paused_at": null,
    "created_at": "2026-09-01T15:04:05.000Z",
    "updated_at": "2026-09-15T14:30:02.000Z",
    "tool_profile": null,
    "base_prompt_version": "9f2c4e7a1b3d5f6e8a0c2b4d6f8e0a1c3b5d7f9e1a2c4b6d8f0e2a4c6b8d0f1e",
    "workflow": null,
    "tool_surface_version": 3,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "supported_channels": [
      "voice",
      "whatsapp"
    ],
    "analysis_config": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
        }
      ]
    },
    "safety_config": null,
    "privacy_config": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    }
  }
}
```

**SDK Code**

```python Restrict connected apps
import requests

url = "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

payload = {
    "allowedToolkits": ["hubspot", "gmail"],
    "disabledTools": { "hubspot": ["HUBSPOT_DELETE_DEAL"] },
    "toolkitAccess": { "gmail": "read" }
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript Restrict connected apps
const url = 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"allowedToolkits":["hubspot","gmail"],"disabledTools":{"hubspot":["HUBSPOT_DELETE_DEAL"]},"toolkitAccess":{"gmail":"read"}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Restrict connected apps
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

	payload := strings.NewReader("{\n  \"allowedToolkits\": [\n    \"hubspot\",\n    \"gmail\"\n  ],\n  \"disabledTools\": {\n    \"hubspot\": [\n      \"HUBSPOT_DELETE_DEAL\"\n    ]\n  },\n  \"toolkitAccess\": {\n    \"gmail\": \"read\"\n  }\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Restrict connected apps
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"allowedToolkits\": [\n    \"hubspot\",\n    \"gmail\"\n  ],\n  \"disabledTools\": {\n    \"hubspot\": [\n      \"HUBSPOT_DELETE_DEAL\"\n    ]\n  },\n  \"toolkitAccess\": {\n    \"gmail\": \"read\"\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java Restrict connected apps
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"allowedToolkits\": [\n    \"hubspot\",\n    \"gmail\"\n  ],\n  \"disabledTools\": {\n    \"hubspot\": [\n      \"HUBSPOT_DELETE_DEAL\"\n    ]\n  },\n  \"toolkitAccess\": {\n    \"gmail\": \"read\"\n  }\n}")
  .asString();
```

```php Restrict connected apps
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c', [
  'body' => '{
  "allowedToolkits": [
    "hubspot",
    "gmail"
  ],
  "disabledTools": {
    "hubspot": [
      "HUBSPOT_DELETE_DEAL"
    ]
  },
  "toolkitAccess": {
    "gmail": "read"
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Restrict connected apps
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"allowedToolkits\": [\n    \"hubspot\",\n    \"gmail\"\n  ],\n  \"disabledTools\": {\n    \"hubspot\": [\n      \"HUBSPOT_DELETE_DEAL\"\n    ]\n  },\n  \"toolkitAccess\": {\n    \"gmail\": \"read\"\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Restrict connected apps
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "allowedToolkits": ["hubspot", "gmail"],
  "disabledTools": ["hubspot": ["HUBSPOT_DELETE_DEAL"]],
  "toolkitAccess": ["gmail": "read"]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Replace only the extraction fields; success criteria are kept

**Request**

```json
{
  "analysis": {
    "dataCollection": [
      {
        "id": "plan_elegido",
        "dataType": "string",
        "description": "Plan que eligió el cliente.",
        "enum": [
          "mensual",
          "trimestral",
          "anual"
        ]
      }
    ]
  }
}
```

**Response**

```json
{
  "data": {
    "id": "7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "name": "Sofía — Ventas",
    "voice_id": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales",
    "template_id": null,
    "system_prompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": "+573001234567",
    "max_call_duration_seconds": 240,
    "response_delay_seconds": 0,
    "human_pacing_enabled": true,
    "enable_voicemail_detection": true,
    "disclose_ai": null,
    "expressive_mode": false,
    "tool_call_sound_enabled": false,
    "text_normalisation_type": "elevenlabs",
    "turn_timeout": 1.5,
    "turn_eagerness": "normal",
    "tts_speed": 0.96,
    "tts_stability": 0.5,
    "tts_similarity_boost": 0.75,
    "department": "Ventas",
    "skills": [
      "suscripciones",
      "facturación"
    ],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": "2026-09-10T18:22:55.000Z",
    "paused_at": null,
    "created_at": "2026-09-01T15:04:05.000Z",
    "updated_at": "2026-09-15T14:30:02.000Z",
    "tool_profile": null,
    "base_prompt_version": "9f2c4e7a1b3d5f6e8a0c2b4d6f8e0a1c3b5d7f9e1a2c4b6d8f0e2a4c6b8d0f1e",
    "workflow": null,
    "tool_surface_version": 3,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "supported_channels": [
      "voice",
      "whatsapp"
    ],
    "analysis_config": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
        }
      ]
    },
    "safety_config": null,
    "privacy_config": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    }
  }
}
```

**SDK Code**

```python Replace only the extraction fields; success criteria are kept
import requests

url = "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

payload = { "analysis": { "dataCollection": [
            {
                "id": "plan_elegido",
                "dataType": "string",
                "description": "Plan que eligió el cliente.",
                "enum": ["mensual", "trimestral", "anual"]
            }
        ] } }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript Replace only the extraction fields; success criteria are kept
const url = 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"analysis":{"dataCollection":[{"id":"plan_elegido","dataType":"string","description":"Plan que eligió el cliente.","enum":["mensual","trimestral","anual"]}]}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Replace only the extraction fields; success criteria are kept
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

	payload := strings.NewReader("{\n  \"analysis\": {\n    \"dataCollection\": [\n      {\n        \"id\": \"plan_elegido\",\n        \"dataType\": \"string\",\n        \"description\": \"Plan que eligió el cliente.\",\n        \"enum\": [\n          \"mensual\",\n          \"trimestral\",\n          \"anual\"\n        ]\n      }\n    ]\n  }\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Replace only the extraction fields; success criteria are kept
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"analysis\": {\n    \"dataCollection\": [\n      {\n        \"id\": \"plan_elegido\",\n        \"dataType\": \"string\",\n        \"description\": \"Plan que eligió el cliente.\",\n        \"enum\": [\n          \"mensual\",\n          \"trimestral\",\n          \"anual\"\n        ]\n      }\n    ]\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java Replace only the extraction fields; success criteria are kept
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"analysis\": {\n    \"dataCollection\": [\n      {\n        \"id\": \"plan_elegido\",\n        \"dataType\": \"string\",\n        \"description\": \"Plan que eligió el cliente.\",\n        \"enum\": [\n          \"mensual\",\n          \"trimestral\",\n          \"anual\"\n        ]\n      }\n    ]\n  }\n}")
  .asString();
```

```php Replace only the extraction fields; success criteria are kept
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c', [
  'body' => '{
  "analysis": {
    "dataCollection": [
      {
        "id": "plan_elegido",
        "dataType": "string",
        "description": "Plan que eligió el cliente.",
        "enum": [
          "mensual",
          "trimestral",
          "anual"
        ]
      }
    ]
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Replace only the extraction fields; success criteria are kept
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"analysis\": {\n    \"dataCollection\": [\n      {\n        \"id\": \"plan_elegido\",\n        \"dataType\": \"string\",\n        \"description\": \"Plan que eligió el cliente.\",\n        \"enum\": [\n          \"mensual\",\n          \"trimestral\",\n          \"anual\"\n        ]\n      }\n    ]\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Replace only the extraction fields; success criteria are kept
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["analysis": ["dataCollection": [
      [
        "id": "plan_elegido",
        "dataType": "string",
        "description": "Plan que eligió el cliente.",
        "enum": ["mensual", "trimestral", "anual"]
      ]
    ]]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Clear values

**Request**

```json
{
  "transferPhoneNumber": null,
  "discloseAi": null,
  "department": null,
  "safety": null
}
```

**Response**

```json
{
  "data": {
    "id": "7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "name": "Sofía — Ventas",
    "voice_id": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales",
    "template_id": null,
    "system_prompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ofrece primero el plan mensual y, si el cliente duda, el trimestral con 10 % de descuento.",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Le cuento la promoción de este mes?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": "+573001234567",
    "max_call_duration_seconds": 240,
    "response_delay_seconds": 0,
    "human_pacing_enabled": true,
    "enable_voicemail_detection": true,
    "disclose_ai": null,
    "expressive_mode": false,
    "tool_call_sound_enabled": false,
    "text_normalisation_type": "elevenlabs",
    "turn_timeout": 1.5,
    "turn_eagerness": "normal",
    "tts_speed": 0.96,
    "tts_stability": 0.5,
    "tts_similarity_boost": 0.75,
    "department": "Ventas",
    "skills": [
      "suscripciones",
      "facturación"
    ],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": "2026-09-10T18:22:55.000Z",
    "paused_at": null,
    "created_at": "2026-09-01T15:04:05.000Z",
    "updated_at": "2026-09-15T14:30:02.000Z",
    "tool_profile": null,
    "base_prompt_version": "9f2c4e7a1b3d5f6e8a0c2b4d6f8e0a1c3b5d7f9e1a2c4b6d8f0e2a4c6b8d0f1e",
    "workflow": null,
    "tool_surface_version": 3,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "supported_channels": [
      "voice",
      "whatsapp"
    ],
    "analysis_config": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
        }
      ]
    },
    "safety_config": null,
    "privacy_config": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    }
  }
}
```

**SDK Code**

```python Clear values
import requests

url = "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

payload = {
    "transferPhoneNumber": None,
    "discloseAi": None,
    "department": None,
    "safety": None
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript Clear values
const url = 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"transferPhoneNumber":null,"discloseAi":null,"department":null,"safety":null}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Clear values
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c"

	payload := strings.NewReader("{\n  \"transferPhoneNumber\": null,\n  \"discloseAi\": null,\n  \"department\": null,\n  \"safety\": null\n}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Clear values
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"transferPhoneNumber\": null,\n  \"discloseAi\": null,\n  \"department\": null,\n  \"safety\": null\n}"

response = http.request(request)
puts response.read_body
```

```java Clear values
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"transferPhoneNumber\": null,\n  \"discloseAi\": null,\n  \"department\": null,\n  \"safety\": null\n}")
  .asString();
```

```php Clear values
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c', [
  'body' => '{
  "transferPhoneNumber": null,
  "discloseAi": null,
  "department": null,
  "safety": null
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Clear values
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"transferPhoneNumber\": null,\n  \"discloseAi\": null,\n  \"department\": null,\n  \"safety\": null\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Clear values
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "transferPhoneNumber": ,
  "discloseAi": ,
  "department": ,
  "safety": 
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/7c1e4b2a-9d3f-4e8a-b5c6-2f1a0d9e8b7c")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```