> 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.

# Create an agent

POST https://api.jelliu.co/api/agents
Content-Type: application/json

Creates an agent. The row is stored immediately and returned with `201` in about 1-2 seconds;
provisioning with the voice engine continues in the background for about 30-45
seconds. Meanwhile operations that need the provider (`GET /api/agents/{agentId}/signed-url`,
tools, workflow, knowledge, MCP servers) answer `409 AGENT_PROVISIONING` with `Retry-After: 5`.
Edits sent with `PATCH /api/agents/{agentId}` during that window are picked up by provisioning.

Only `name`, `voiceId` and `language` are required. Runtime tuning you omit is seeded from the
objective (`category`), and when `analysis` is omitted a default set of success criteria and
extraction fields for that objective is stored. When `firstMessage` is empty a greeting is built
from the agent name and the workspace's business name.

Checks run in this order, and the first failure answers: role and plan agent cap, body validation,
workspace still exists, reserved `qa_` criterion ids, fraud screening of the prompt, first message
and objection handlers, cloned-voice plan check, dial policy for `transferPhoneNumber`, template
access, and a final atomic agent-cap check at insert time.

With `?async=true` only the role, plan cap and body validation run in the request; the call answers
`202` with a job id and everything else runs in a worker. Poll `GET /api/agents/jobs/{jobId}`
(kept for one hour) for the result or the error.

**Side effects.** Calls an LLM to screen the prompt for fraud (a blocked prompt is recorded as a
fraud flag on the workspace). Inserts the agent and version 1 of its change history, then enqueues
the voice-engine provisioning job, which creates the voice agent and attaches the post-call webhook,
tools and MCP gateway. Writes an audit entry. An in-app notification is posted when provisioning
finishes or fails.

**Idempotency.** Not idempotent. Retrying after a timeout creates a second agent (and a second voice
agent) and consumes another slot of the plan's agent cap. Check `GET /api/agents?search=` before
retrying.

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

**Access**

* **Required scope:** `write`. Signed-in users need the owner, admin or member role; viewers get `403 Insufficient permissions`.
* **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:** Agent cap by plan — Starter 2, Growth 5, Business 12, Enterprise unlimited, plus purchased agent add-ons; a workspace without an active plan or trial cannot create agents. Custom cloned voices require Enterprise.

Reference: https://developer.jelliu.co/api-reference/agents/post-agents

## 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

### Query parameters

- `async` (string, optional) — Send `true` to queue the creation and receive a job id (`202`) instead of the agent. Only the literal string `true` is recognised; any other value (`1`, `yes`) is ignored and the creation runs synchronously.

### Body (application/json)

This endpoint expects an object.

- `name` (string, required) — Display name of the agent, also used in the generated greeting. 1-100 characters.
- `voiceId` (string, required) — Voice id (see the voices endpoints). A custom cloned voice requires the Enterprise plan (`403 BILLING_ERROR` otherwise).
- `language` (enum, required) — Main language of the agent: `es` (Spanish), `es-CO` (Colombia), `es-MX` (Mexico), `es-AR` (Argentina), `es-neutral` (neutral Latin American Spanish), `en`, `en-US`, `pt`, `pt-BR`. Common labels are also accepted case-insensitively and normalised (for example `Español (LATAM)` → `es-neutral`, `Portuguese` → `pt`, `México` → `es-MX`); anything else is rejected with `400`.
  - Allowed values: `es`, `es-CO`, `es-MX`, `es-AR`, `es-neutral`, `en`, `en-US`, `pt`, `pt-BR`
- `channels` (list of enum, optional) — Channels the agent may answer on. Omitted = every channel. At least one. The list is stored as sent and later edits replace it — it is never a delta.
  - Allowed values: `voice`, `whatsapp`, `email`, `webchat`, `instagram`, `messenger`
- `templateId` (string, optional) — Objective template (per-objective playbook layered under the workspace prompt). Must be a system template or one owned by the workspace; otherwise `400`.
- `category` (enum, optional) — The agent's objective. It selects the base agent, default tuning and default analysis, and wins over the template's category. Omit to inherit the template's category. Values: `sales`, `support`, `scheduling`, `surveys`, `collections`, `retention`, `notifications`, `interview` (candidate screening), `language_assessment` (scored spoken evaluation), `personal` (personal assistant), `general`.
  - Allowed values: `sales`, `support`, `scheduling`, `surveys`, `collections`, `retention`, `notifications`, `interview`, `language_assessment`, `personal`, `general`
- `systemPrompt` (string, optional, default: ) — The workspace's own prompt layer, placed on top of the platform and objective layers. Trimmed. Must be empty (the agent introduces itself generically and never invents a product) or 10-8000 characters. Screened for fraud. Catalogues and price lists belong in the knowledge base.
- `firstMessage` (string, optional, default: ) — Opening line. Trimmed. When empty a greeting is generated from the agent name and the workspace's business name (for example `Hola, le habla Sofía de Cafés del Huila. ¿Cómo está?`). Screened for fraud.
- `dynamicVariables` (map from string to string, optional, default: {}) — Key/value strings stored with the agent (at most 50 entries; keys up to 100 characters, values up to 500). Stored and versioned; not currently applied to conversations.
- `objectionHandlers` (map from string to string, optional, default: {}) — Objection → suggested answer (at most 50 entries; keys up to 100 characters, values up to 2000). Each value is screened for fraud. Stored and versioned; not currently applied to conversations — put objection guidance in `systemPrompt`.
- `escalationRules` (list of object, optional, default: []) — Escalation rules. Stored and versioned; not currently applied to conversations — escalation to a human is handled by the platform's own escalation tool.
  - `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`
- `maxCallDurationSeconds` (double, optional) — Hard cap on a voice call, in seconds (30-3600). Omitted = the objective's cap. The objective's cap also acts as a ceiling when the provider configuration is derived.
- `transferPhoneNumber` (string, optional) — E.164 number the voice agent may transfer live calls to. The destination must be allowed by the workspace dial policy (`403 COMPLIANCE_BLOCKED` otherwise).
- `discloseAi` (boolean, optional, nullable) — Whether the agent opens by saying it is an AI. `true`/`false` is this agent's decision and overrides the country policy; omitted or `null` lets the country policy decide.
- `enableVoicemailDetection` (boolean, optional, default: true) — Detect voicemail on outbound calls and hang up instead of talking to the machine.
- `transferRules` (list of object, optional, default: []) — In-call hand-offs to other agents of the workspace (up to 10). Targets that do not exist or are not provisioned yet are skipped silently when the provider configuration is built.
  - `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) — Department used by inbound skill-based routing. Trimmed; blank is treated as absent.
- `skills` (list of string, optional, default: []) — Skills matched by inbound skill-based routing (up to 50, each trimmed, 1-100 characters).
- `safety` (object, optional) — Guardrails — topics the agent refuses to discuss. Pushed to the voice provider's guardrails.
  - `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` (object, optional) — PII redaction and data retention. On create the defaults below are stored even when the block is omitted; on update nothing is defaulted.
  - `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.
- `analysis` (object, optional) — Post-call analysis run by the voice provider on the transcript. `successEvaluation` criteria decide whether a conversation succeeded (they drive outcomes and conversion metrics); `dataCollection` fields are extracted from the transcript. Criterion ids starting with `qa_` are reserved for platform quality evaluation and a NEW one is rejected with `400` (ids already stored keep saving).
  - `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.
- `expressiveMode` (boolean, optional, default: false) — Lets the voice model use expressive delivery (audio tags such as laughter or emphasis).
- `toolCallSoundEnabled` (boolean, optional, default: false) — Play a short sound on voice calls while a tool is running.
- `textNormalisationType` (enum, optional, default: elevenlabs) — How numbers, dates and symbols are turned into speakable text: `elevenlabs` (the provider normalises before speech) or `system_prompt` (the model is instructed to write them out).
  - Allowed values: `system_prompt`, `elevenlabs`
- `turnTimeout` (double, optional) — Voice only — seconds of silence before the agent takes the turn (1-10). Omitted = the objective's value.
- `turnEagerness` (enum, optional) — How eagerly the voice agent takes the turn: `patient`, `normal`, `eager`. The legacy spellings `low`, `medium` and `high` are still accepted. Omitted = the objective's value.
  - Allowed values: `patient`, `normal`, `eager`, `low`, `medium`, `high`
- `ttsSpeed` (double, optional) — Speech rate, 0.5-2 (1 = the voice's natural speed). Omitted = the objective's value.
- `ttsStability` (double, optional) — Voice stability, 0-1. Lower is more expressive, higher more consistent. Omitted = the objective's value.
- `ttsSimilarityBoost` (double, optional, default: 0.75) — How closely synthesis adheres to the original voice, 0-1.

## Response

### 201

Agent created. It is still being provisioned with the voice provider.

- `data` (object, required) — An agent as returned by `GET /api/agents`, `GET /api/agents/{agentId}` and `POST /api/agents`. Stored columns are snake\_case (see `AgentRowBase`); the fields the API adds on top of them (`channels`, `objective`, `analysis`, `safety`, `privacy`, `mcpEnabled`, `toolkitAccessEnforced`, `total_calls`, `conversion_rate`, `calls_today`, `avg_sentiment`) are listed below, and the raw JSON columns they come from (`supported_channels`, `analysis_config`, `safety_config`, `privacy_config`) are not returned. `analysis`, `safety` and `privacy` are omitted when nothing is stored. `GET /api/agents` returns a projection (see that operation). The provider's internal voice agent id is never returned. A newly created agent is provisioned with the voice provider in the background for about 30-45 seconds; operations that need it answer `409 AGENT_PROVISIONING` meanwhile.
  - `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).
  - `channels` (list of enum, optional) — Channels this agent answers on (from the `supported_channels` column): `voice` (phone and web voice), `whatsapp`, `email`, `webchat`, `instagram`, `messenger`. Empty array if the column is null.
    - Allowed values: `voice`, `whatsapp`, `email`, `webchat`, `instagram`, `messenger`
  - `objective` (string, optional, nullable) — Same value as `category` (kept for older clients).
  - `analysis` (object, optional) — Post-call analysis run by the voice provider on the transcript. `successEvaluation` criteria decide whether a conversation succeeded (they drive outcomes and conversion metrics); `dataCollection` fields are extracted from the transcript. Criterion ids starting with `qa_` are reserved for platform quality evaluation and a NEW one is rejected with `400` (ids already stored keep saving).
    - `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` (object, optional) — Guardrails — topics the agent refuses to discuss. Pushed to the voice provider's guardrails.
    - `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` (object, optional) — PII redaction and data retention. On create the defaults below are stored even when the block is omitted; on update nothing is defaulted.
    - `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.
  - `mcpEnabled` (boolean, optional) — Mirror of `mcp_enabled` (`true` when the column is absent).
  - `toolkitAccessEnforced` (boolean, optional) — Whether per-app `toolkit_access` restrictions are enforced by the platform (currently always `true`).
  - `total_calls` (integer, optional) — Currently always 0 on these endpoints; use the analytics endpoints for call counts.
  - `conversion_rate` (double, optional) — Currently always 0 on these endpoints; use the analytics endpoints.
  - `calls_today` (integer, optional) — Currently always 0 on these endpoints; use the analytics endpoints.
  - `avg_sentiment` (double, optional, nullable) — Currently always `null` on these endpoints (null means no data, never neutral).

### 202

Creation queued (`?async=true`). Poll `GET /api/agents/jobs/{jobId}` for the outcome.

- `data` (object, optional) — The queued job.
  - `jobId` (string, optional) — Id to poll the job with.
  - `status` (enum, optional) — Always `pending` when accepted.
    - Allowed values: `pending`

## Errors

### 400 Bad Request Error

The body failed validation (`details` is Zod's flattened error), the template is not a system template nor one of the workspace's, or a success criterion id uses the reserved `qa_` prefix.

- `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

Plan agent cap reached or cloned voice outside Enterprise (`BILLING_ERROR`, with `metadata`), prompt blocked by fraud screening or transfer destination refused by the dial policy (`COMPLIANCE_BLOCKED`), or the API-key scope / user role does not allow it (`FORBIDDEN`), or the workspace is suspended (`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

The workspace has been deleted, so no agent can be created in it.

- `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).

## Examples

### Agents_postAgents_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": "",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Cómo está?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": null,
    "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": null,
    "skills": [],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": null,
    "paused_at": null,
    "created_at": "2026-09-15T14:02:11.000Z",
    "updated_at": "2026-09-15T14:02:11.000Z",
    "tool_profile": null,
    "base_prompt_version": null,
    "workflow": null,
    "tool_surface_version": 0,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "channels": [
      "voice",
      "whatsapp",
      "email",
      "webchat",
      "instagram",
      "messenger"
    ],
    "objective": "sales",
    "analysis": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó comprar o contratar durante la llamada."
        }
      ],
      "dataCollection": [
        {
          "id": "monto_vendido",
          "dataType": "number",
          "description": "Monto total de la venta en moneda local. Vacío si no hubo venta."
        }
      ]
    },
    "privacy": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    },
    "mcpEnabled": true,
    "toolkitAccessEnforced": true,
    "total_calls": 0,
    "conversion_rate": 0,
    "calls_today": 0,
    "avg_sentiment": null
  }
}
```

**SDK Code**

```python Agents_postAgents_example
import requests

url = "https://api.jelliu.co/api/agents"

querystring = {"async":"true"}

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

response = requests.post(url, headers=headers, params=querystring)

print(response.json())
```

```javascript Agents_postAgents_example
const url = 'https://api.jelliu.co/api/agents?async=true';
const options = {method: 'POST', 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_postAgents_example
package main

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

func main() {

	url := "https://api.jelliu.co/api/agents?async=true"

	req, _ := http.NewRequest("POST", 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_postAgents_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents?async=true")

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

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

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

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

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/agents?async=true")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/agents?async=true', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp Agents_postAgents_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents?async=true");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Agents_postAgents_example
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents?async=true")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
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()
```

### Agents_postAgents_example

**Request**

```json
undefined
```

**Response**

```json
{
  "data": {
    "jobId": "3f1c9a52-8d7e-4b0a-9f39-2b8a4e6d1c77",
    "status": "pending"
  }
}
```

**SDK Code**

```python Agents_postAgents_example
import requests

url = "https://api.jelliu.co/api/agents"

querystring = {"async":"true"}

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

response = requests.post(url, headers=headers, params=querystring)

print(response.json())
```

```javascript Agents_postAgents_example
const url = 'https://api.jelliu.co/api/agents?async=true';
const options = {method: 'POST', 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_postAgents_example
package main

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

func main() {

	url := "https://api.jelliu.co/api/agents?async=true"

	req, _ := http.NewRequest("POST", 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_postAgents_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents?async=true")

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

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

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

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

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/agents?async=true")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/agents?async=true', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp Agents_postAgents_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents?async=true");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Agents_postAgents_example
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents?async=true")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
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()
```

### Minimal agent — objective defaults fill the rest

**Request**

```json
{
  "name": "Sofía — Ventas",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "language": "es-CO",
  "category": "sales"
}
```

**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": "",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Cómo está?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": null,
    "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": null,
    "skills": [],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": null,
    "paused_at": null,
    "created_at": "2026-09-15T14:02:11.000Z",
    "updated_at": "2026-09-15T14:02:11.000Z",
    "tool_profile": null,
    "base_prompt_version": null,
    "workflow": null,
    "tool_surface_version": 0,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "channels": [
      "voice",
      "whatsapp",
      "email",
      "webchat",
      "instagram",
      "messenger"
    ],
    "objective": "sales",
    "analysis": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó comprar o contratar durante la llamada."
        }
      ],
      "dataCollection": [
        {
          "id": "monto_vendido",
          "dataType": "number",
          "description": "Monto total de la venta en moneda local. Vacío si no hubo venta."
        }
      ]
    },
    "privacy": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    },
    "mcpEnabled": true,
    "toolkitAccessEnforced": true,
    "total_calls": 0,
    "conversion_rate": 0,
    "calls_today": 0,
    "avg_sentiment": null
  }
}
```

**SDK Code**

```python Minimal agent — objective defaults fill the rest
import requests

url = "https://api.jelliu.co/api/agents"

querystring = {"async":"true"}

payload = {
    "name": "Sofía — Ventas",
    "voiceId": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "category": "sales"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers, params=querystring)

print(response.json())
```

```javascript Minimal agent — objective defaults fill the rest
const url = 'https://api.jelliu.co/api/agents?async=true';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"name":"Sofía — Ventas","voiceId":"21m00Tcm4TlvDq8ikWAM","language":"es-CO","category":"sales"}'
};

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

```go Minimal agent — objective defaults fill the rest
package main

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

func main() {

	url := "https://api.jelliu.co/api/agents?async=true"

	payload := strings.NewReader("{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"category\": \"sales\"\n}")

	req, _ := http.NewRequest("POST", 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 Minimal agent — objective defaults fill the rest
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents?async=true")

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

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"category\": \"sales\"\n}"

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

```java Minimal agent — objective defaults fill the rest
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/agents?async=true")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"category\": \"sales\"\n}")
  .asString();
```

```php Minimal agent — objective defaults fill the rest
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/agents?async=true', [
  'body' => '{
  "name": "Sofía — Ventas",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "language": "es-CO",
  "category": "sales"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp Minimal agent — objective defaults fill the rest
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents?async=true");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"category\": \"sales\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Minimal agent — objective defaults fill the rest
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "name": "Sofía — Ventas",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "language": "es-CO",
  "category": "sales"
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents?async=true")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
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()
```

### Fully configured sales agent

**Request**

```json
{
  "name": "Sofía — Ventas",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "language": "es-CO",
  "channels": [
    "voice",
    "whatsapp"
  ],
  "category": "sales",
  "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de\nsuscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y\nmenciona el envío gratis en Bogotá, Medellín y Cali.\n",
  "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?",
  "maxCallDurationSeconds": 300,
  "transferPhoneNumber": "+573001234567",
  "discloseAi": true,
  "transferRules": [
    {
      "targetAgentId": "4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f",
      "condition": "El cliente pide hablar de un pedido ya realizado o de una factura.",
      "transferMessage": "Le comunico con Andrés, de soporte, que le ayuda con su pedido."
    }
  ],
  "department": "Ventas",
  "skills": [
    "suscripciones",
    "facturación"
  ],
  "safety": {
    "blockedOutputTopics": [
      "política",
      "competidores"
    ]
  },
  "privacy": {
    "redactPiiAudio": true,
    "deleteAudioAfterProcessing": false,
    "retentionDays": 30
  },
  "analysis": {
    "successEvaluation": [
      {
        "id": "venta_concretada",
        "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
      }
    ],
    "dataCollection": [
      {
        "id": "plan_elegido",
        "dataType": "string",
        "description": "Plan que eligió el cliente.",
        "enum": [
          "mensual",
          "trimestral",
          "anual"
        ]
      }
    ]
  },
  "turnEagerness": "normal",
  "ttsSpeed": 0.95
}
```

**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": "",
    "first_message": "Hola, le habla Sofía de Cafés del Huila. ¿Cómo está?",
    "chat_system_prompt": null,
    "dynamic_variables": {},
    "objection_handlers": {},
    "escalation_rules": [],
    "transfer_rules": [],
    "transfer_phone_number": null,
    "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": null,
    "skills": [],
    "allowed_toolkits": null,
    "disabled_tools": {},
    "toolkit_access": {},
    "mcp_enabled": true,
    "tool_surface_status": "synced",
    "tool_surface_error": null,
    "tool_surface_updated_at": null,
    "paused_at": null,
    "created_at": "2026-09-15T14:02:11.000Z",
    "updated_at": "2026-09-15T14:02:11.000Z",
    "tool_profile": null,
    "base_prompt_version": null,
    "workflow": null,
    "tool_surface_version": 0,
    "high_risk": false,
    "paused_by": null,
    "paused_reason": null,
    "action_limits": {},
    "deleted_at": null,
    "channels": [
      "voice",
      "whatsapp",
      "email",
      "webchat",
      "instagram",
      "messenger"
    ],
    "objective": "sales",
    "analysis": {
      "successEvaluation": [
        {
          "id": "venta_concretada",
          "description": "El cliente aceptó comprar o contratar durante la llamada."
        }
      ],
      "dataCollection": [
        {
          "id": "monto_vendido",
          "dataType": "number",
          "description": "Monto total de la venta en moneda local. Vacío si no hubo venta."
        }
      ]
    },
    "privacy": {
      "redactPiiAudio": true,
      "deleteAudioAfterProcessing": true,
      "retentionDays": 90
    },
    "mcpEnabled": true,
    "toolkitAccessEnforced": true,
    "total_calls": 0,
    "conversion_rate": 0,
    "calls_today": 0,
    "avg_sentiment": null
  }
}
```

**SDK Code**

```python Fully configured sales agent
import requests

url = "https://api.jelliu.co/api/agents"

querystring = {"async":"true"}

payload = {
    "name": "Sofía — Ventas",
    "voiceId": "21m00Tcm4TlvDq8ikWAM",
    "language": "es-CO",
    "channels": ["voice", "whatsapp"],
    "category": "sales",
    "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de
suscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y
menciona el envío gratis en Bogotá, Medellín y Cali.
",
    "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?",
    "maxCallDurationSeconds": 300,
    "transferPhoneNumber": "+573001234567",
    "discloseAi": True,
    "transferRules": [
        {
            "targetAgentId": "4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f",
            "condition": "El cliente pide hablar de un pedido ya realizado o de una factura.",
            "transferMessage": "Le comunico con Andrés, de soporte, que le ayuda con su pedido."
        }
    ],
    "department": "Ventas",
    "skills": ["suscripciones", "facturación"],
    "safety": { "blockedOutputTopics": ["política", "competidores"] },
    "privacy": {
        "redactPiiAudio": True,
        "deleteAudioAfterProcessing": False,
        "retentionDays": 30
    },
    "analysis": {
        "successEvaluation": [
            {
                "id": "venta_concretada",
                "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
            }
        ],
        "dataCollection": [
            {
                "id": "plan_elegido",
                "dataType": "string",
                "description": "Plan que eligió el cliente.",
                "enum": ["mensual", "trimestral", "anual"]
            }
        ]
    },
    "turnEagerness": "normal",
    "ttsSpeed": 0.95
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers, params=querystring)

print(response.json())
```

```javascript Fully configured sales agent
const url = 'https://api.jelliu.co/api/agents?async=true';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"name":"Sofía — Ventas","voiceId":"21m00Tcm4TlvDq8ikWAM","language":"es-CO","channels":["voice","whatsapp"],"category":"sales","systemPrompt":"Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de\nsuscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y\nmenciona el envío gratis en Bogotá, Medellín y Cali.\n","firstMessage":"Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?","maxCallDurationSeconds":300,"transferPhoneNumber":"+573001234567","discloseAi":true,"transferRules":[{"targetAgentId":"4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f","condition":"El cliente pide hablar de un pedido ya realizado o de una factura.","transferMessage":"Le comunico con Andrés, de soporte, que le ayuda con su pedido."}],"department":"Ventas","skills":["suscripciones","facturación"],"safety":{"blockedOutputTopics":["política","competidores"]},"privacy":{"redactPiiAudio":true,"deleteAudioAfterProcessing":false,"retentionDays":30},"analysis":{"successEvaluation":[{"id":"venta_concretada","description":"El cliente aceptó contratar un plan de suscripción durante la llamada."}],"dataCollection":[{"id":"plan_elegido","dataType":"string","description":"Plan que eligió el cliente.","enum":["mensual","trimestral","anual"]}]},"turnEagerness":"normal","ttsSpeed":0.95}'
};

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

```go Fully configured sales agent
package main

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

func main() {

	url := "https://api.jelliu.co/api/agents?async=true"

	payload := strings.NewReader("{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ],\n  \"category\": \"sales\",\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de\\nsuscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y\\nmenciona el envío gratis en Bogotá, Medellín y Cali.\\n\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?\",\n  \"maxCallDurationSeconds\": 300,\n  \"transferPhoneNumber\": \"+573001234567\",\n  \"discloseAi\": true,\n  \"transferRules\": [\n    {\n      \"targetAgentId\": \"4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f\",\n      \"condition\": \"El cliente pide hablar de un pedido ya realizado o de una factura.\",\n      \"transferMessage\": \"Le comunico con Andrés, de soporte, que le ayuda con su pedido.\"\n    }\n  ],\n  \"department\": \"Ventas\",\n  \"skills\": [\n    \"suscripciones\",\n    \"facturación\"\n  ],\n  \"safety\": {\n    \"blockedOutputTopics\": [\n      \"política\",\n      \"competidores\"\n    ]\n  },\n  \"privacy\": {\n    \"redactPiiAudio\": true,\n    \"deleteAudioAfterProcessing\": false,\n    \"retentionDays\": 30\n  },\n  \"analysis\": {\n    \"successEvaluation\": [\n      {\n        \"id\": \"venta_concretada\",\n        \"description\": \"El cliente aceptó contratar un plan de suscripción durante la llamada.\"\n      }\n    ],\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  \"turnEagerness\": \"normal\",\n  \"ttsSpeed\": 0.95\n}")

	req, _ := http.NewRequest("POST", 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 Fully configured sales agent
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents?async=true")

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

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ],\n  \"category\": \"sales\",\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de\\nsuscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y\\nmenciona el envío gratis en Bogotá, Medellín y Cali.\\n\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?\",\n  \"maxCallDurationSeconds\": 300,\n  \"transferPhoneNumber\": \"+573001234567\",\n  \"discloseAi\": true,\n  \"transferRules\": [\n    {\n      \"targetAgentId\": \"4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f\",\n      \"condition\": \"El cliente pide hablar de un pedido ya realizado o de una factura.\",\n      \"transferMessage\": \"Le comunico con Andrés, de soporte, que le ayuda con su pedido.\"\n    }\n  ],\n  \"department\": \"Ventas\",\n  \"skills\": [\n    \"suscripciones\",\n    \"facturación\"\n  ],\n  \"safety\": {\n    \"blockedOutputTopics\": [\n      \"política\",\n      \"competidores\"\n    ]\n  },\n  \"privacy\": {\n    \"redactPiiAudio\": true,\n    \"deleteAudioAfterProcessing\": false,\n    \"retentionDays\": 30\n  },\n  \"analysis\": {\n    \"successEvaluation\": [\n      {\n        \"id\": \"venta_concretada\",\n        \"description\": \"El cliente aceptó contratar un plan de suscripción durante la llamada.\"\n      }\n    ],\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  \"turnEagerness\": \"normal\",\n  \"ttsSpeed\": 0.95\n}"

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

```java Fully configured sales agent
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/agents?async=true")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ],\n  \"category\": \"sales\",\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de\\nsuscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y\\nmenciona el envío gratis en Bogotá, Medellín y Cali.\\n\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?\",\n  \"maxCallDurationSeconds\": 300,\n  \"transferPhoneNumber\": \"+573001234567\",\n  \"discloseAi\": true,\n  \"transferRules\": [\n    {\n      \"targetAgentId\": \"4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f\",\n      \"condition\": \"El cliente pide hablar de un pedido ya realizado o de una factura.\",\n      \"transferMessage\": \"Le comunico con Andrés, de soporte, que le ayuda con su pedido.\"\n    }\n  ],\n  \"department\": \"Ventas\",\n  \"skills\": [\n    \"suscripciones\",\n    \"facturación\"\n  ],\n  \"safety\": {\n    \"blockedOutputTopics\": [\n      \"política\",\n      \"competidores\"\n    ]\n  },\n  \"privacy\": {\n    \"redactPiiAudio\": true,\n    \"deleteAudioAfterProcessing\": false,\n    \"retentionDays\": 30\n  },\n  \"analysis\": {\n    \"successEvaluation\": [\n      {\n        \"id\": \"venta_concretada\",\n        \"description\": \"El cliente aceptó contratar un plan de suscripción durante la llamada.\"\n      }\n    ],\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  \"turnEagerness\": \"normal\",\n  \"ttsSpeed\": 0.95\n}")
  .asString();
```

```php Fully configured sales agent
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/agents?async=true', [
  'body' => '{
  "name": "Sofía — Ventas",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "language": "es-CO",
  "channels": [
    "voice",
    "whatsapp"
  ],
  "category": "sales",
  "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de\\nsuscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y\\nmenciona el envío gratis en Bogotá, Medellín y Cali.\\n",
  "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?",
  "maxCallDurationSeconds": 300,
  "transferPhoneNumber": "+573001234567",
  "discloseAi": true,
  "transferRules": [
    {
      "targetAgentId": "4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f",
      "condition": "El cliente pide hablar de un pedido ya realizado o de una factura.",
      "transferMessage": "Le comunico con Andrés, de soporte, que le ayuda con su pedido."
    }
  ],
  "department": "Ventas",
  "skills": [
    "suscripciones",
    "facturación"
  ],
  "safety": {
    "blockedOutputTopics": [
      "política",
      "competidores"
    ]
  },
  "privacy": {
    "redactPiiAudio": true,
    "deleteAudioAfterProcessing": false,
    "retentionDays": 30
  },
  "analysis": {
    "successEvaluation": [
      {
        "id": "venta_concretada",
        "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
      }
    ],
    "dataCollection": [
      {
        "id": "plan_elegido",
        "dataType": "string",
        "description": "Plan que eligió el cliente.",
        "enum": [
          "mensual",
          "trimestral",
          "anual"
        ]
      }
    ]
  },
  "turnEagerness": "normal",
  "ttsSpeed": 0.95
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp Fully configured sales agent
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents?async=true");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"Sofía — Ventas\",\n  \"voiceId\": \"21m00Tcm4TlvDq8ikWAM\",\n  \"language\": \"es-CO\",\n  \"channels\": [\n    \"voice\",\n    \"whatsapp\"\n  ],\n  \"category\": \"sales\",\n  \"systemPrompt\": \"Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de\\nsuscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y\\nmenciona el envío gratis en Bogotá, Medellín y Cali.\\n\",\n  \"firstMessage\": \"Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?\",\n  \"maxCallDurationSeconds\": 300,\n  \"transferPhoneNumber\": \"+573001234567\",\n  \"discloseAi\": true,\n  \"transferRules\": [\n    {\n      \"targetAgentId\": \"4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f\",\n      \"condition\": \"El cliente pide hablar de un pedido ya realizado o de una factura.\",\n      \"transferMessage\": \"Le comunico con Andrés, de soporte, que le ayuda con su pedido.\"\n    }\n  ],\n  \"department\": \"Ventas\",\n  \"skills\": [\n    \"suscripciones\",\n    \"facturación\"\n  ],\n  \"safety\": {\n    \"blockedOutputTopics\": [\n      \"política\",\n      \"competidores\"\n    ]\n  },\n  \"privacy\": {\n    \"redactPiiAudio\": true,\n    \"deleteAudioAfterProcessing\": false,\n    \"retentionDays\": 30\n  },\n  \"analysis\": {\n    \"successEvaluation\": [\n      {\n        \"id\": \"venta_concretada\",\n        \"description\": \"El cliente aceptó contratar un plan de suscripción durante la llamada.\"\n      }\n    ],\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  \"turnEagerness\": \"normal\",\n  \"ttsSpeed\": 0.95\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Fully configured sales agent
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "name": "Sofía — Ventas",
  "voiceId": "21m00Tcm4TlvDq8ikWAM",
  "language": "es-CO",
  "channels": ["voice", "whatsapp"],
  "category": "sales",
  "systemPrompt": "Eres Sofía, asesora comercial de Cafés del Huila. Ayudas a los clientes a elegir el plan de
suscripción de café que mejor se ajusta a su consumo. Ofrece primero el plan mensual y
menciona el envío gratis en Bogotá, Medellín y Cali.
",
  "firstMessage": "Hola, le habla Sofía de Cafés del Huila. ¿Tiene un minuto para contarle de nuestros planes?",
  "maxCallDurationSeconds": 300,
  "transferPhoneNumber": "+573001234567",
  "discloseAi": true,
  "transferRules": [
    [
      "targetAgentId": "4b8d2f6a-1c3e-4a5b-9d7f-6e2c8a0b1d3f",
      "condition": "El cliente pide hablar de un pedido ya realizado o de una factura.",
      "transferMessage": "Le comunico con Andrés, de soporte, que le ayuda con su pedido."
    ]
  ],
  "department": "Ventas",
  "skills": ["suscripciones", "facturación"],
  "safety": ["blockedOutputTopics": ["política", "competidores"]],
  "privacy": [
    "redactPiiAudio": true,
    "deleteAudioAfterProcessing": false,
    "retentionDays": 30
  ],
  "analysis": [
    "successEvaluation": [
      [
        "id": "venta_concretada",
        "description": "El cliente aceptó contratar un plan de suscripción durante la llamada."
      ]
    ],
    "dataCollection": [
      [
        "id": "plan_elegido",
        "dataType": "string",
        "description": "Plan que eligió el cliente.",
        "enum": ["mensual", "trimestral", "anual"]
      ]
    ]
  ],
  "turnEagerness": "normal",
  "ttsSpeed": 0.95
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents?async=true")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
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()
```