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

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

Registers a webhook tool that the agent's language model may call during conversations,
on voice calls and text channels alike. The model decides when to call it from `name` and
`description`, so write the description as an instruction: what the tool does and when to
use it.

The request is processed in this order: the agent must exist and be fully provisioned at the
voice provider (else `409 AGENT_PROVISIONING`, with `Retry-After: 5`); `url` and
`authConfig.tokenUrl` are resolved through DNS and must point to a public address; the name
must be unique on the agent (case-insensitive); the agent must have fewer than 50 tools. The
tool is then stored and the agent's complete tool list is pushed to the provider.

**Side effects.** Updates the agent in the voice engine (the agent's system tools such as
`end_call` are preserved). If the push fails the new tool is rolled back and the request
answers 500; nothing is left stored. `authConfig` is encrypted at rest. The mutation is
written to the audit log.

**Idempotency.** No `Idempotency-Key` support, but a retry cannot create a duplicate: if the
first attempt succeeded, the retry answers `409 TOOL_NAME_CONFLICT`. Confirm with
`GET /api/agents/{agentId}/tools`.

**Webhook events.** `audit.log_recorded` for endpoints subscribed to it. See [Webhooks](/webhooks).

**Access**

* **Required scope:** `write`.
* **Rate limit:** Configuration mutations — 10 requests/min per workspace, shared with other agent configuration writes, in addition to the General API limit. See [Rate limits](/rate-limits).
* **Plan:** Available on every plan.

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

## 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 that will own the tool.

### Body (application/json)

This endpoint expects an object.

- `name` (string, required) — Function name the model sees. Lowercase letters, digits and underscores, starting with a letter. Unique per agent, compared case-insensitively (409 `TOOL_NAME_CONFLICT` otherwise).
- `description` (string, required) — What the tool does and when to use it. The model relies on this text alone to decide whether to call the tool, so be explicit about the trigger and the limits.
- `url` (string, required) — Endpoint the provider calls, `http` or `https`. May contain `{identifier}` placeholders filled from `pathParams`. Must not target private, loopback or reserved addresses: the syntax is checked by validation and the hostname is also resolved through DNS (400 when it resolves to a private IP).
- `type` (enum, optional, default: webhook) — Tool kind. Only `webhook` exists: the provider calls your HTTP endpoint.
  - Allowed values: `webhook`
- `method` (enum, optional, default: GET) — HTTP method of the request. `bodyParams` are only sent with `POST`, `PUT` and `PATCH`; with `GET` and `DELETE` they are stored but ignored.
  - Allowed values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
- `headers` (list of object, optional, default: []) — Request headers. Only entries with `value_type: fixed` and a non-empty `fixed_value` are sent; `llm_prompt` headers are ignored. Header values are returned by the read endpoints, so treat them as visible to anyone with `read` access.
  - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
  - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
    - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
  - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
  - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
    - Allowed values: `llm_prompt`, `fixed`
  - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
  - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
  - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
- `pathParams` (list of object, optional, default: []) — Values substituted into the matching `{identifier}` placeholders of `url`.
  - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
  - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
    - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
  - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
  - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
    - Allowed values: `llm_prompt`, `fixed`
  - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
  - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
  - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
- `queryParams` (list of object, optional, default: []) — Query-string parameters. Model-filled ones are required unless `required` is `false`.
  - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
  - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
    - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
  - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
  - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
    - Allowed values: `llm_prompt`, `fixed`
  - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
  - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
  - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
- `bodyParams` (list of object, optional, default: []) — Top-level properties of the JSON request body (POST, PUT and PATCH only).
  - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
  - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
    - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
  - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
  - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
    - Allowed values: `llm_prompt`, `fixed`
  - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
  - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
  - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
- `authType` (enum, optional, default: none) — Declared authentication scheme, stored with `authConfig`. These two fields are not currently part of the configuration pushed to the voice provider, so they do not authenticate live calls today; send credentials as a `fixed` header instead.
  - Allowed values: `none`, `bearer`, `basic`, `oauth2_client_credentials`, `oauth2_jwt`, `custom_header`
- `authConfig` (object, optional) — Credentials matching `authType`. Encrypted at rest (AES-256-GCM, bound to the workspace), never returned by any endpoint and excluded from agent version snapshots. On update the whole object is replaced, and blank strings count as absent. Not currently part of the configuration pushed to the voice provider.
  - `token` (string, optional) — Bearer token (`bearer`).
  - `username` (string, optional) — User name (`basic`).
  - `password` (string, optional) — Password (`basic`).
  - `clientId` (string, optional) — OAuth client id (`oauth2_client_credentials`, `oauth2_jwt`).
  - `clientSecret` (string, optional) — OAuth client secret.
  - `tokenUrl` (string, optional) — OAuth token endpoint. Must not target private/internal addresses (checked syntactically and through DNS).
  - `scopes` (string, optional) — Space-separated OAuth scopes.
  - `headerName` (string, optional) — Header name (`custom_header`).
  - `headerValue` (string, optional) — Header value (`custom_header`).
- `responseTimeoutSecs` (integer, optional, default: 20) — Seconds the provider waits for your response before telling the model the tool failed.
- `toolCallSound` (enum, optional) — Sound played on voice calls while the request is in flight. Omit for silence.
  - Allowed values: `ringing`, `typing`, `ambient`
- `toolCallSoundBehavior` (enum, optional, default: auto) — When `toolCallSound` plays.
  - Allowed values: `auto`, `always`, `never`
- `dynamicVariableAssignments` (list of object, optional, default: []) — Values to copy from the tool's JSON response into conversation dynamic variables. Stored and returned, but not currently included in the configuration pushed to the provider, so they have no effect on live conversations today.
  - `variable_name` (string, required) — Dynamic variable to set, without braces.
  - `json_path` (string, required) — Path to the value inside the response body.
  - `description` (string, optional) — Free-text note for your team.
- `position` (integer, optional, default: 0) — Sort key. Tools are listed and pushed by `position`, then by creation time.

## Response

### 201

Tool created and live at the voice provider.

- `data` (object, required) — A webhook tool as returned by the API. Keys are snake_case (the request bodies use camelCase). `auth_config` is never returned.
  - `id` (string, optional) — Tool id.
  - `tenant_id` (string, optional) — Workspace that owns the tool.
  - `agent_id` (string, optional) — Agent the tool belongs to.
  - `name` (string, optional) — Function name the model sees; unique per agent.
  - `description` (string, optional) — Instruction the model uses to decide when to call the tool.
  - `type` (enum, optional) — Tool kind. Always `webhook`.
    - Allowed values: `webhook`
  - `method` (enum, optional) — HTTP method. Body parameters only travel with POST, PUT and PATCH.
    - Allowed values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
  - `url` (string, optional) — Endpoint the provider calls, with `{placeholders}` for path parameters.
  - `headers` (list of object, optional) — Request headers, including their `fixed_value`. Only `fixed` headers are sent.
    - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
    - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
      - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
    - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
    - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
      - Allowed values: `llm_prompt`, `fixed`
    - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
    - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
    - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
  - `path_params` (list of object, optional) — URL placeholder parameters.
    - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
    - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
      - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
    - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
    - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
      - Allowed values: `llm_prompt`, `fixed`
    - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
    - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
    - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
  - `query_params` (list of object, optional) — Query-string parameters.
    - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
    - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
      - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
    - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
    - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
      - Allowed values: `llm_prompt`, `fixed`
    - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
    - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
    - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
  - `body_params` (list of object, optional) — JSON body properties.
    - `identifier` (string, required) — Parameter name as sent on the wire (header name, `{placeholder}`, query key or body property).
    - `type` (enum, required) — JSON type the provider sends. A `fixed_value` is coerced to it: `integer` and `number` are parsed, `boolean` is `true` only for the text `true`.
      - Allowed values: `string`, `number`, `integer`, `boolean`, `object`, `array`
    - `description` (string, required) — For `llm_prompt` parameters, the instruction the model follows to extract the value. Not sent to the provider for `fixed` parameters.
    - `value_type` (enum, required) — Who supplies the value. `llm_prompt`: the model fills it from the conversation. `fixed`: the platform injects `fixed_value` and the model never chooses it; a `fixed_value` of the form `{{variable}}` is replaced by that session dynamic variable.
      - Allowed values: `llm_prompt`, `fixed`
    - `fixed_value` (string, optional) — Value for `fixed` parameters: a literal (`agente_ia`, `"true"`) or a dynamic variable such as `{{contact_phone}}`. A `fixed` parameter without a value is treated as model-filled.
    - `required` (boolean, optional, default: true) — Whether the model must supply the value (query and body parameters). `fixed` parameters are never asked of the model regardless of this flag.
    - `enum_values` (list of string, optional) — For `llm_prompt` parameters, the only values the model may send.
  - `auth_type` (enum, optional) — Declared authentication scheme (credentials are stored separately and never returned).
    - Allowed values: `none`, `bearer`, `basic`, `oauth2_client_credentials`, `oauth2_jwt`, `custom_header`
  - `response_timeout_secs` (integer, optional) — Seconds the provider waits for the response (1–120).
  - `tool_call_sound` (enum, optional) — Sound played on voice while waiting, or `null` for silence.
    - Allowed values: `ringing`, `typing`, `ambient`
  - `tool_call_sound_behavior` (enum, optional) — When the sound plays.
    - Allowed values: `auto`, `always`, `never`
  - `dynamic_variable_assignments` (list of object, optional) — Stored response-to-variable mappings (not currently forwarded to the provider).
    - `variable_name` (string, required) — Dynamic variable to set, without braces.
    - `json_path` (string, required) — Path to the value inside the response body.
    - `description` (string, optional) — Free-text note for your team.
  - `position` (integer, optional) — Sort key for listing and provider order.
  - `created_at` (datetime, optional) — When the tool was created.
  - `updated_at` (datetime, optional) — Last change to the tool.

## Errors

### 400 Bad Request Error

`agentId` is not a UUID (`Invalid agent ID`); the body fails validation (`Invalid tool input`, with Zod's flattened `details`); the URL resolves to a private address (message prefixed `tool endpoint URL:` or `OAuth token URL:`); or the agent already has 50 tools.

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

The API key lacks the `write` scope, or the workspace is 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 agent does not exist or belongs to another 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

A tool with that name (case-insensitive) already exists on the agent, or the agent is still being provisioned at the voice provider (retry after the `Retry-After` seconds).

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

Too many agent configuration writes in the current minute (or the General API limit).

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

### 500 Internal Server Error

The push to the voice provider failed; the tool was rolled back and is not stored.

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

### Agent Tools_postAgentsByAgentIdTools_example

**Request**

```json
undefined
```

**Response**

```json
{
  "data": {
    "id": "c41d8e2b-6f3a-4b9c-a7d5-e2f10b3c4d6e",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "agent_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "name": "crear_ticket_soporte",
    "description": "Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.",
    "type": "webhook",
    "method": "POST",
    "url": "https://api.tiendaandina.co/v1/tickets",
    "headers": [
      {
        "identifier": "X-Api-Key",
        "type": "string",
        "description": "Clave de la API de tickets",
        "value_type": "fixed",
        "fixed_value": "ta_live_4f9c2e71",
        "required": true
      }
    ],
    "path_params": [],
    "query_params": [
      {
        "identifier": "canal",
        "type": "string",
        "description": "Canal de origen",
        "value_type": "fixed",
        "fixed_value": "agente_ia",
        "required": true
      }
    ],
    "body_params": [
      {
        "identifier": "telefono",
        "type": "string",
        "description": "Teléfono verificado de quien llama",
        "value_type": "fixed",
        "fixed_value": "{{contact_phone}}",
        "required": true
      },
      {
        "identifier": "numero_pedido",
        "type": "string",
        "description": "Número del pedido afectado",
        "value_type": "llm_prompt",
        "required": true
      },
      {
        "identifier": "tipo_problema",
        "type": "string",
        "description": "Tipo de problema reportado",
        "value_type": "llm_prompt",
        "required": true,
        "enum_values": [
          "danado",
          "incompleto",
          "equivocado"
        ]
      },
      {
        "identifier": "comentario",
        "type": "string",
        "description": "Detalle adicional que dé el cliente, si lo hay",
        "value_type": "llm_prompt",
        "required": false
      }
    ],
    "auth_type": "none",
    "response_timeout_secs": 30,
    "tool_call_sound": "typing",
    "tool_call_sound_behavior": "auto",
    "dynamic_variable_assignments": [],
    "position": 1,
    "created_at": "2026-09-15T14:32:10.000Z",
    "updated_at": "2026-09-15T14:32:10.000Z"
  }
}
```

**SDK Code**

```python Agent Tools_postAgentsByAgentIdTools_example
import requests

url = "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools"

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

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

print(response.json())
```

```javascript Agent Tools_postAgentsByAgentIdTools_example
const url = 'https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools';
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 Agent Tools_postAgentsByAgentIdTools_example
package main

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

func main() {

	url := "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools"

	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 Agent Tools_postAgentsByAgentIdTools_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")

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 Agent Tools_postAgentsByAgentIdTools_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp Agent Tools_postAgentsByAgentIdTools_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Agent Tools_postAgentsByAgentIdTools_example
import Foundation

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")! 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()
```

### GET lookup with a path parameter filled by the model

**Request**

```json
{
  "name": "consultar_pedido",
  "description": "Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.",
  "url": "https://api.tiendaandina.co/v1/pedidos/{numero_pedido}",
  "pathParams": [
    {
      "identifier": "numero_pedido",
      "type": "string",
      "description": "Número del pedido que menciona el cliente, solo dígitos",
      "value_type": "llm_prompt"
    }
  ]
}
```

**Response**

```json
{
  "data": {
    "id": "c41d8e2b-6f3a-4b9c-a7d5-e2f10b3c4d6e",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "agent_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "name": "crear_ticket_soporte",
    "description": "Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.",
    "type": "webhook",
    "method": "POST",
    "url": "https://api.tiendaandina.co/v1/tickets",
    "headers": [
      {
        "identifier": "X-Api-Key",
        "type": "string",
        "description": "Clave de la API de tickets",
        "value_type": "fixed",
        "fixed_value": "ta_live_4f9c2e71",
        "required": true
      }
    ],
    "path_params": [],
    "query_params": [
      {
        "identifier": "canal",
        "type": "string",
        "description": "Canal de origen",
        "value_type": "fixed",
        "fixed_value": "agente_ia",
        "required": true
      }
    ],
    "body_params": [
      {
        "identifier": "telefono",
        "type": "string",
        "description": "Teléfono verificado de quien llama",
        "value_type": "fixed",
        "fixed_value": "{{contact_phone}}",
        "required": true
      },
      {
        "identifier": "numero_pedido",
        "type": "string",
        "description": "Número del pedido afectado",
        "value_type": "llm_prompt",
        "required": true
      },
      {
        "identifier": "tipo_problema",
        "type": "string",
        "description": "Tipo de problema reportado",
        "value_type": "llm_prompt",
        "required": true,
        "enum_values": [
          "danado",
          "incompleto",
          "equivocado"
        ]
      },
      {
        "identifier": "comentario",
        "type": "string",
        "description": "Detalle adicional que dé el cliente, si lo hay",
        "value_type": "llm_prompt",
        "required": false
      }
    ],
    "auth_type": "none",
    "response_timeout_secs": 30,
    "tool_call_sound": "typing",
    "tool_call_sound_behavior": "auto",
    "dynamic_variable_assignments": [],
    "position": 1,
    "created_at": "2026-09-15T14:32:10.000Z",
    "updated_at": "2026-09-15T14:32:10.000Z"
  }
}
```

**SDK Code**

```python GET lookup with a path parameter filled by the model
import requests

url = "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools"

payload = {
    "name": "consultar_pedido",
    "description": "Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.",
    "url": "https://api.tiendaandina.co/v1/pedidos/{numero_pedido}",
    "pathParams": [
        {
            "identifier": "numero_pedido",
            "type": "string",
            "description": "Número del pedido que menciona el cliente, solo dígitos",
            "value_type": "llm_prompt"
        }
    ]
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

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

print(response.json())
```

```javascript GET lookup with a path parameter filled by the model
const url = 'https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"name":"consultar_pedido","description":"Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.","url":"https://api.tiendaandina.co/v1/pedidos/{numero_pedido}","pathParams":[{"identifier":"numero_pedido","type":"string","description":"Número del pedido que menciona el cliente, solo dígitos","value_type":"llm_prompt"}]}'
};

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

```go GET lookup with a path parameter filled by the model
package main

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

func main() {

	url := "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools"

	payload := strings.NewReader("{\n  \"name\": \"consultar_pedido\",\n  \"description\": \"Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.\",\n  \"url\": \"https://api.tiendaandina.co/v1/pedidos/{numero_pedido}\",\n  \"pathParams\": [\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido que menciona el cliente, solo dígitos\",\n      \"value_type\": \"llm_prompt\"\n    }\n  ]\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 GET lookup with a path parameter filled by the model
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")

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\": \"consultar_pedido\",\n  \"description\": \"Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.\",\n  \"url\": \"https://api.tiendaandina.co/v1/pedidos/{numero_pedido}\",\n  \"pathParams\": [\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido que menciona el cliente, solo dígitos\",\n      \"value_type\": \"llm_prompt\"\n    }\n  ]\n}"

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

```java GET lookup with a path parameter filled by the model
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"consultar_pedido\",\n  \"description\": \"Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.\",\n  \"url\": \"https://api.tiendaandina.co/v1/pedidos/{numero_pedido}\",\n  \"pathParams\": [\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido que menciona el cliente, solo dígitos\",\n      \"value_type\": \"llm_prompt\"\n    }\n  ]\n}")
  .asString();
```

```php GET lookup with a path parameter filled by the model
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools', [
  'body' => '{
  "name": "consultar_pedido",
  "description": "Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.",
  "url": "https://api.tiendaandina.co/v1/pedidos/{numero_pedido}",
  "pathParams": [
    {
      "identifier": "numero_pedido",
      "type": "string",
      "description": "Número del pedido que menciona el cliente, solo dígitos",
      "value_type": "llm_prompt"
    }
  ]
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp GET lookup with a path parameter filled by the model
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"consultar_pedido\",\n  \"description\": \"Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.\",\n  \"url\": \"https://api.tiendaandina.co/v1/pedidos/{numero_pedido}\",\n  \"pathParams\": [\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido que menciona el cliente, solo dígitos\",\n      \"value_type\": \"llm_prompt\"\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift GET lookup with a path parameter filled by the model
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "name": "consultar_pedido",
  "description": "Consulta el estado y la fecha estimada de entrega de un pedido a partir de su número.",
  "url": "https://api.tiendaandina.co/v1/pedidos/{numero_pedido}",
  "pathParams": [
    [
      "identifier": "numero_pedido",
      "type": "string",
      "description": "Número del pedido que menciona el cliente, solo dígitos",
      "value_type": "llm_prompt"
    ]
  ]
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")! 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()
```

### POST with a fixed header, platform-injected caller id and model-filled body fields

**Request**

```json
{
  "name": "crear_ticket_soporte",
  "description": "Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.",
  "url": "https://api.tiendaandina.co/v1/tickets",
  "type": "webhook",
  "method": "POST",
  "headers": [
    {
      "identifier": "X-Api-Key",
      "type": "string",
      "description": "Clave de la API de tickets",
      "value_type": "fixed",
      "fixed_value": "ta_live_4f9c2e71"
    }
  ],
  "queryParams": [
    {
      "identifier": "canal",
      "type": "string",
      "description": "Canal de origen",
      "value_type": "fixed",
      "fixed_value": "agente_ia"
    }
  ],
  "bodyParams": [
    {
      "identifier": "telefono",
      "type": "string",
      "description": "Teléfono verificado de quien llama",
      "value_type": "fixed",
      "fixed_value": "{{contact_phone}}"
    },
    {
      "identifier": "numero_pedido",
      "type": "string",
      "description": "Número del pedido afectado",
      "value_type": "llm_prompt"
    },
    {
      "identifier": "tipo_problema",
      "type": "string",
      "description": "Tipo de problema reportado",
      "value_type": "llm_prompt",
      "enum_values": [
        "danado",
        "incompleto",
        "equivocado"
      ]
    },
    {
      "identifier": "comentario",
      "type": "string",
      "description": "Detalle adicional que dé el cliente, si lo hay",
      "value_type": "llm_prompt",
      "required": false
    }
  ],
  "responseTimeoutSecs": 30,
  "toolCallSound": "typing",
  "toolCallSoundBehavior": "auto",
  "position": 1
}
```

**Response**

```json
{
  "data": {
    "id": "c41d8e2b-6f3a-4b9c-a7d5-e2f10b3c4d6e",
    "tenant_id": "2d9f6c1e-3b4a-4c5d-9e8f-7a6b5c4d3e2f",
    "agent_id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "name": "crear_ticket_soporte",
    "description": "Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.",
    "type": "webhook",
    "method": "POST",
    "url": "https://api.tiendaandina.co/v1/tickets",
    "headers": [
      {
        "identifier": "X-Api-Key",
        "type": "string",
        "description": "Clave de la API de tickets",
        "value_type": "fixed",
        "fixed_value": "ta_live_4f9c2e71",
        "required": true
      }
    ],
    "path_params": [],
    "query_params": [
      {
        "identifier": "canal",
        "type": "string",
        "description": "Canal de origen",
        "value_type": "fixed",
        "fixed_value": "agente_ia",
        "required": true
      }
    ],
    "body_params": [
      {
        "identifier": "telefono",
        "type": "string",
        "description": "Teléfono verificado de quien llama",
        "value_type": "fixed",
        "fixed_value": "{{contact_phone}}",
        "required": true
      },
      {
        "identifier": "numero_pedido",
        "type": "string",
        "description": "Número del pedido afectado",
        "value_type": "llm_prompt",
        "required": true
      },
      {
        "identifier": "tipo_problema",
        "type": "string",
        "description": "Tipo de problema reportado",
        "value_type": "llm_prompt",
        "required": true,
        "enum_values": [
          "danado",
          "incompleto",
          "equivocado"
        ]
      },
      {
        "identifier": "comentario",
        "type": "string",
        "description": "Detalle adicional que dé el cliente, si lo hay",
        "value_type": "llm_prompt",
        "required": false
      }
    ],
    "auth_type": "none",
    "response_timeout_secs": 30,
    "tool_call_sound": "typing",
    "tool_call_sound_behavior": "auto",
    "dynamic_variable_assignments": [],
    "position": 1,
    "created_at": "2026-09-15T14:32:10.000Z",
    "updated_at": "2026-09-15T14:32:10.000Z"
  }
}
```

**SDK Code**

```python POST with a fixed header, platform-injected caller id and model-filled body fields
import requests

url = "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools"

payload = {
    "name": "crear_ticket_soporte",
    "description": "Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.",
    "url": "https://api.tiendaandina.co/v1/tickets",
    "type": "webhook",
    "method": "POST",
    "headers": [
        {
            "identifier": "X-Api-Key",
            "type": "string",
            "description": "Clave de la API de tickets",
            "value_type": "fixed",
            "fixed_value": "ta_live_4f9c2e71"
        }
    ],
    "queryParams": [
        {
            "identifier": "canal",
            "type": "string",
            "description": "Canal de origen",
            "value_type": "fixed",
            "fixed_value": "agente_ia"
        }
    ],
    "bodyParams": [
        {
            "identifier": "telefono",
            "type": "string",
            "description": "Teléfono verificado de quien llama",
            "value_type": "fixed",
            "fixed_value": "{{contact_phone}}"
        },
        {
            "identifier": "numero_pedido",
            "type": "string",
            "description": "Número del pedido afectado",
            "value_type": "llm_prompt"
        },
        {
            "identifier": "tipo_problema",
            "type": "string",
            "description": "Tipo de problema reportado",
            "value_type": "llm_prompt",
            "enum_values": ["danado", "incompleto", "equivocado"]
        },
        {
            "identifier": "comentario",
            "type": "string",
            "description": "Detalle adicional que dé el cliente, si lo hay",
            "value_type": "llm_prompt",
            "required": False
        }
    ],
    "responseTimeoutSecs": 30,
    "toolCallSound": "typing",
    "toolCallSoundBehavior": "auto",
    "position": 1
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

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

print(response.json())
```

```javascript POST with a fixed header, platform-injected caller id and model-filled body fields
const url = 'https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"name":"crear_ticket_soporte","description":"Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.","url":"https://api.tiendaandina.co/v1/tickets","type":"webhook","method":"POST","headers":[{"identifier":"X-Api-Key","type":"string","description":"Clave de la API de tickets","value_type":"fixed","fixed_value":"ta_live_4f9c2e71"}],"queryParams":[{"identifier":"canal","type":"string","description":"Canal de origen","value_type":"fixed","fixed_value":"agente_ia"}],"bodyParams":[{"identifier":"telefono","type":"string","description":"Teléfono verificado de quien llama","value_type":"fixed","fixed_value":"{{contact_phone}}"},{"identifier":"numero_pedido","type":"string","description":"Número del pedido afectado","value_type":"llm_prompt"},{"identifier":"tipo_problema","type":"string","description":"Tipo de problema reportado","value_type":"llm_prompt","enum_values":["danado","incompleto","equivocado"]},{"identifier":"comentario","type":"string","description":"Detalle adicional que dé el cliente, si lo hay","value_type":"llm_prompt","required":false}],"responseTimeoutSecs":30,"toolCallSound":"typing","toolCallSoundBehavior":"auto","position":1}'
};

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

```go POST with a fixed header, platform-injected caller id and model-filled body fields
package main

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

func main() {

	url := "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools"

	payload := strings.NewReader("{\n  \"name\": \"crear_ticket_soporte\",\n  \"description\": \"Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.\",\n  \"url\": \"https://api.tiendaandina.co/v1/tickets\",\n  \"type\": \"webhook\",\n  \"method\": \"POST\",\n  \"headers\": [\n    {\n      \"identifier\": \"X-Api-Key\",\n      \"type\": \"string\",\n      \"description\": \"Clave de la API de tickets\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"ta_live_4f9c2e71\"\n    }\n  ],\n  \"queryParams\": [\n    {\n      \"identifier\": \"canal\",\n      \"type\": \"string\",\n      \"description\": \"Canal de origen\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"agente_ia\"\n    }\n  ],\n  \"bodyParams\": [\n    {\n      \"identifier\": \"telefono\",\n      \"type\": \"string\",\n      \"description\": \"Teléfono verificado de quien llama\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"{{contact_phone}}\"\n    },\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido afectado\",\n      \"value_type\": \"llm_prompt\"\n    },\n    {\n      \"identifier\": \"tipo_problema\",\n      \"type\": \"string\",\n      \"description\": \"Tipo de problema reportado\",\n      \"value_type\": \"llm_prompt\",\n      \"enum_values\": [\n        \"danado\",\n        \"incompleto\",\n        \"equivocado\"\n      ]\n    },\n    {\n      \"identifier\": \"comentario\",\n      \"type\": \"string\",\n      \"description\": \"Detalle adicional que dé el cliente, si lo hay\",\n      \"value_type\": \"llm_prompt\",\n      \"required\": false\n    }\n  ],\n  \"responseTimeoutSecs\": 30,\n  \"toolCallSound\": \"typing\",\n  \"toolCallSoundBehavior\": \"auto\",\n  \"position\": 1\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 POST with a fixed header, platform-injected caller id and model-filled body fields
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")

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\": \"crear_ticket_soporte\",\n  \"description\": \"Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.\",\n  \"url\": \"https://api.tiendaandina.co/v1/tickets\",\n  \"type\": \"webhook\",\n  \"method\": \"POST\",\n  \"headers\": [\n    {\n      \"identifier\": \"X-Api-Key\",\n      \"type\": \"string\",\n      \"description\": \"Clave de la API de tickets\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"ta_live_4f9c2e71\"\n    }\n  ],\n  \"queryParams\": [\n    {\n      \"identifier\": \"canal\",\n      \"type\": \"string\",\n      \"description\": \"Canal de origen\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"agente_ia\"\n    }\n  ],\n  \"bodyParams\": [\n    {\n      \"identifier\": \"telefono\",\n      \"type\": \"string\",\n      \"description\": \"Teléfono verificado de quien llama\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"{{contact_phone}}\"\n    },\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido afectado\",\n      \"value_type\": \"llm_prompt\"\n    },\n    {\n      \"identifier\": \"tipo_problema\",\n      \"type\": \"string\",\n      \"description\": \"Tipo de problema reportado\",\n      \"value_type\": \"llm_prompt\",\n      \"enum_values\": [\n        \"danado\",\n        \"incompleto\",\n        \"equivocado\"\n      ]\n    },\n    {\n      \"identifier\": \"comentario\",\n      \"type\": \"string\",\n      \"description\": \"Detalle adicional que dé el cliente, si lo hay\",\n      \"value_type\": \"llm_prompt\",\n      \"required\": false\n    }\n  ],\n  \"responseTimeoutSecs\": 30,\n  \"toolCallSound\": \"typing\",\n  \"toolCallSoundBehavior\": \"auto\",\n  \"position\": 1\n}"

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

```java POST with a fixed header, platform-injected caller id and model-filled body fields
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"crear_ticket_soporte\",\n  \"description\": \"Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.\",\n  \"url\": \"https://api.tiendaandina.co/v1/tickets\",\n  \"type\": \"webhook\",\n  \"method\": \"POST\",\n  \"headers\": [\n    {\n      \"identifier\": \"X-Api-Key\",\n      \"type\": \"string\",\n      \"description\": \"Clave de la API de tickets\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"ta_live_4f9c2e71\"\n    }\n  ],\n  \"queryParams\": [\n    {\n      \"identifier\": \"canal\",\n      \"type\": \"string\",\n      \"description\": \"Canal de origen\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"agente_ia\"\n    }\n  ],\n  \"bodyParams\": [\n    {\n      \"identifier\": \"telefono\",\n      \"type\": \"string\",\n      \"description\": \"Teléfono verificado de quien llama\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"{{contact_phone}}\"\n    },\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido afectado\",\n      \"value_type\": \"llm_prompt\"\n    },\n    {\n      \"identifier\": \"tipo_problema\",\n      \"type\": \"string\",\n      \"description\": \"Tipo de problema reportado\",\n      \"value_type\": \"llm_prompt\",\n      \"enum_values\": [\n        \"danado\",\n        \"incompleto\",\n        \"equivocado\"\n      ]\n    },\n    {\n      \"identifier\": \"comentario\",\n      \"type\": \"string\",\n      \"description\": \"Detalle adicional que dé el cliente, si lo hay\",\n      \"value_type\": \"llm_prompt\",\n      \"required\": false\n    }\n  ],\n  \"responseTimeoutSecs\": 30,\n  \"toolCallSound\": \"typing\",\n  \"toolCallSoundBehavior\": \"auto\",\n  \"position\": 1\n}")
  .asString();
```

```php POST with a fixed header, platform-injected caller id and model-filled body fields
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools', [
  'body' => '{
  "name": "crear_ticket_soporte",
  "description": "Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.",
  "url": "https://api.tiendaandina.co/v1/tickets",
  "type": "webhook",
  "method": "POST",
  "headers": [
    {
      "identifier": "X-Api-Key",
      "type": "string",
      "description": "Clave de la API de tickets",
      "value_type": "fixed",
      "fixed_value": "ta_live_4f9c2e71"
    }
  ],
  "queryParams": [
    {
      "identifier": "canal",
      "type": "string",
      "description": "Canal de origen",
      "value_type": "fixed",
      "fixed_value": "agente_ia"
    }
  ],
  "bodyParams": [
    {
      "identifier": "telefono",
      "type": "string",
      "description": "Teléfono verificado de quien llama",
      "value_type": "fixed",
      "fixed_value": "{{contact_phone}}"
    },
    {
      "identifier": "numero_pedido",
      "type": "string",
      "description": "Número del pedido afectado",
      "value_type": "llm_prompt"
    },
    {
      "identifier": "tipo_problema",
      "type": "string",
      "description": "Tipo de problema reportado",
      "value_type": "llm_prompt",
      "enum_values": [
        "danado",
        "incompleto",
        "equivocado"
      ]
    },
    {
      "identifier": "comentario",
      "type": "string",
      "description": "Detalle adicional que dé el cliente, si lo hay",
      "value_type": "llm_prompt",
      "required": false
    }
  ],
  "responseTimeoutSecs": 30,
  "toolCallSound": "typing",
  "toolCallSoundBehavior": "auto",
  "position": 1
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp POST with a fixed header, platform-injected caller id and model-filled body fields
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"crear_ticket_soporte\",\n  \"description\": \"Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.\",\n  \"url\": \"https://api.tiendaandina.co/v1/tickets\",\n  \"type\": \"webhook\",\n  \"method\": \"POST\",\n  \"headers\": [\n    {\n      \"identifier\": \"X-Api-Key\",\n      \"type\": \"string\",\n      \"description\": \"Clave de la API de tickets\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"ta_live_4f9c2e71\"\n    }\n  ],\n  \"queryParams\": [\n    {\n      \"identifier\": \"canal\",\n      \"type\": \"string\",\n      \"description\": \"Canal de origen\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"agente_ia\"\n    }\n  ],\n  \"bodyParams\": [\n    {\n      \"identifier\": \"telefono\",\n      \"type\": \"string\",\n      \"description\": \"Teléfono verificado de quien llama\",\n      \"value_type\": \"fixed\",\n      \"fixed_value\": \"{{contact_phone}}\"\n    },\n    {\n      \"identifier\": \"numero_pedido\",\n      \"type\": \"string\",\n      \"description\": \"Número del pedido afectado\",\n      \"value_type\": \"llm_prompt\"\n    },\n    {\n      \"identifier\": \"tipo_problema\",\n      \"type\": \"string\",\n      \"description\": \"Tipo de problema reportado\",\n      \"value_type\": \"llm_prompt\",\n      \"enum_values\": [\n        \"danado\",\n        \"incompleto\",\n        \"equivocado\"\n      ]\n    },\n    {\n      \"identifier\": \"comentario\",\n      \"type\": \"string\",\n      \"description\": \"Detalle adicional que dé el cliente, si lo hay\",\n      \"value_type\": \"llm_prompt\",\n      \"required\": false\n    }\n  ],\n  \"responseTimeoutSecs\": 30,\n  \"toolCallSound\": \"typing\",\n  \"toolCallSoundBehavior\": \"auto\",\n  \"position\": 1\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift POST with a fixed header, platform-injected caller id and model-filled body fields
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "name": "crear_ticket_soporte",
  "description": "Abre un ticket de soporte cuando el cliente reporta un producto dañado o incompleto. Úsala solo después de confirmar el número de pedido.",
  "url": "https://api.tiendaandina.co/v1/tickets",
  "type": "webhook",
  "method": "POST",
  "headers": [
    [
      "identifier": "X-Api-Key",
      "type": "string",
      "description": "Clave de la API de tickets",
      "value_type": "fixed",
      "fixed_value": "ta_live_4f9c2e71"
    ]
  ],
  "queryParams": [
    [
      "identifier": "canal",
      "type": "string",
      "description": "Canal de origen",
      "value_type": "fixed",
      "fixed_value": "agente_ia"
    ]
  ],
  "bodyParams": [
    [
      "identifier": "telefono",
      "type": "string",
      "description": "Teléfono verificado de quien llama",
      "value_type": "fixed",
      "fixed_value": "{{contact_phone}}"
    ],
    [
      "identifier": "numero_pedido",
      "type": "string",
      "description": "Número del pedido afectado",
      "value_type": "llm_prompt"
    ],
    [
      "identifier": "tipo_problema",
      "type": "string",
      "description": "Tipo de problema reportado",
      "value_type": "llm_prompt",
      "enum_values": ["danado", "incompleto", "equivocado"]
    ],
    [
      "identifier": "comentario",
      "type": "string",
      "description": "Detalle adicional que dé el cliente, si lo hay",
      "value_type": "llm_prompt",
      "required": false
    ]
  ],
  "responseTimeoutSecs": 30,
  "toolCallSound": "typing",
  "toolCallSoundBehavior": "auto",
  "position": 1
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/agents/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/tools")! 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()
```