---
title: "Crea una integración de herramientas (API)"
description: "Permite que tu agente llame a tus API durante una conversación: busca en una base de datos, crea un ticket, consulta un pedido."
---

Una **integración de herramientas** es un endpoint HTTP reutilizable que un agente puede
invocar durante una llamada. Le proporcionas a ThunderPhone una descripción en esquema JSON
de la herramienta junto con una URL de endpoint; el agente decide cuándo llamarla
según la conversación, y ThunderPhone realiza la solicitud HTTP saliente desde
sus servidores y devuelve la respuesta al agente.

<Note>
  El dashboard cubre la mayoría de las necesidades de herramientas sin esta API: **Conexiones
  → Aplicaciones** conecta Slack, HubSpot, Salesforce, Google Calendar,
  Google Sheets y Cal.com con unos pocos clics de OAuth; **Conexiones →
  APIs** convierte cualquier API HTTP en una acción del agente (pega un comando cURL
  y un asistente de IA crea el borrador de la herramienta, con una función integrada de Probar solicitud); y
  **Conexiones → MCP** agrega servidores MCP. Consulta
  [Conexiones](/es/guides/concepts). Esta guía trata sobre la
  API sin procesar que sustenta la sección de APIs.
</Note>

Esta guía explica cómo crear una herramienta de consulta del clima de principio a fin.

## Anatomía de una herramienta

Dos elementos:

1. **El esquema** — una definición de función al estilo de OpenAI
   (`{type: "function", function: {name, description, parameters}}`)
   que le indica al LLM qué hace la herramienta y qué argumentos acepta.
2. **El endpoint** — la URL que llaman los servidores de ThunderPhone cuando el
   LLM decide usar la herramienta. La solicitud es un POST JSON con los
   argumentos elegidos por el LLM como cuerpo.

## 1. Elige un editor

<CardGroup cols={2}>
  <Card title="Dashboard" icon="window-maximize">
    Abre **Conexiones → APIs**, crea o edita la conexión de API,
    cambia el editor de parámetros a **JSON** y agrega allí el formato.
  </Card>
  <Card title="API de integraciones" icon="plug">
    Crea la especificación con `POST /v1/integrations` o actualízala con
    `PATCH /v1/integrations/{id}`.
  </Card>
</CardGroup>

Ambas opciones crean una integración guardada. Adjunta esa integración al
agente después de guardarla. La API de agentes no tiene un campo `tools`
en línea que se pueda modificar. Esta guía usa la ruta de la API de integraciones.

## 2. Crea la integración

```bash
curl -X POST https://api.thunderphone.com/v1/integrations \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Weather API",
    "spec": {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Return the current weather for a zip code.",
        "parameters": {
          "type": "object",
          "properties": {
            "zip": { "type": "string", "description": "5-digit US ZIP code" }
          },
          "required": ["zip"]
        }
      }
    },
    "endpoint_url":    "https://api.example.com/weather",
    "endpoint_method": "GET",
    "headers": [
      { "key": "X-Api-Key", "value": "your-provider-key" }
    ]
  }'
```

Guarda el `id` devuelto (un UUID). Los valores de los encabezados son de solo escritura: las respuestas solo enumeran los nombres de los encabezados, y un `PATCH` posterior puede enviar un encabezado sin un `value` para conservar el valor almacenado.

<Tip>
  Dedica un esfuerzo real a la `description` de la herramienta y de cada
  parámetro. El LLM usa estas cadenas en tiempo de ejecución para decidir si debe
  llamar a la herramienta y cómo hacerlo. Descripciones vagas → llamadas a herramientas vagas.
</Tip>

### Declara `format: "email"` en los parámetros de dirección

Un parámetro que recibe una dirección de correo electrónico debe indicarlo en su
esquema:

```json
"email": { "type": "string", "format": "email", "description": "The caller's email address" }
```

`format` es más que una sugerencia. Para un esquema de correo electrónico resuelto, antes de llamar a tu
endpoint, ThunderPhone recorta el valor, convierte el dominio a minúsculas,
convierte las palabras inglesas independientes `at`, `dot`, `underscore`,
`dash` y `hyphen` en sus caracteres, y elimina los espacios en blanco
directamente alrededor de `@`, `.`, `_` y `-`. Las palabras tienen el mismo significado
tanto si la transcripción ya contiene un `@` literal como si no:
`"john dot smith at gmail dot com"` se convierte en
`john.smith@gmail.com`.

Cualquier otro espacio en blanco interno se rechaza en lugar de unirse silenciosamente.
Las palabras de separadores hablados solo son compatibles con inglés; las formas con espacios
que no estén en inglés o que no se reconozcan fallan de forma segura. Se aceptan dominios internacionalizados válidos y partes locales SMTPUTF8.
La entrada en Punycode permanece en Punycode y la entrada de dominio Unicode permanece en Unicode
después de la normalización del analizador, por lo que tu API recibe la representación
convencional proporcionada por quien llama. Si el valor final no es válido, la herramienta **no se llama**. El agente recibe
`invalid_email_argument`, que le indica
que confirme la ortografía con quien llama y reenvíe la dirección literal.

Un correo electrónico opcional omitido no se modifica. `null`, una cadena vacía o una
cadena que solo contiene espacios en blanco tampoco se modifican cuando la propiedad es opcional
o admite valores nulos; los mismos valores se rechazan para un correo electrónico obligatorio que no admite valores nulos.

Las referencias de esquema locales como `#/$defs/email` y
`#/definitions/email`, además de `anyOf`, `oneOf` y `allOf`, se inspeccionan
con límites de ciclos y profundidad. Un `$ref` no local o que no se puede resolver es un
límite de aplicación conocido y se transmite sin cambios, al igual que una llamada
cuya instantánea de herramienta no tiene un esquema utilizable. Mantén los esquemas de correo electrónico locales cuando
necesites que se aplique la validación.

Los parámetros sin un formato de correo electrónico aplicado se transmiten exactamente
como los produjo el modelo.

Los formatos compatibles son `date-time`, `time`, `date`, `duration`,
`email`, `hostname`, `ipv4`, `ipv6` y `uuid`; actualmente solo `email` se
normaliza y aplica.

## 3. Prueba el endpoint en el entorno aislado

Antes de vincular la integración a un agente, envía una solicitud firmada
desde los servidores de ThunderPhone para confirmar la conectividad:

```bash
curl -X POST https://api.thunderphone.com/v1/integrations/test-request \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://api.example.com/weather?zip=94110",
    "method": "GET",
    "headers": { "X-Api-Key": "your-provider-key" }
  }'
```

```json Response
{
  "ok": true,
  "status": 200,
  "elapsed_ms": 187,
  "response_headers": { "content-type": "application/json" },
  "response_preview": "{\"temperature_f\": 64, ...}"
}
```

Esta prueba también refuerza las protecciones SSRF de ThunderPhone: las solicitudes a
localhost o a rangos de IP privados devuelven `400 code=url_not_allowed`.

## 4. Vincula la integración a un agente

Adjúntala mediante `integration_ids` cuando crees o actualices un agente:

```bash
curl -X PATCH https://api.thunderphone.com/v1/agents/12 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_ids": ["f9b5a1a4-..."]
  }'
```

Puedes vincular varias integraciones a un agente. El prompt del agente puede
hacer referencia a ellas por nombre — "usa `get_weather` cuando quien llama
pregunte sobre las condiciones" — o puede descubrirlas implícitamente a partir de las
descripciones del esquema.

## 5. Implementa el endpoint

Cuando el agente invoca la herramienta, ThunderPhone envía un POST firmado a
tu `endpoint_url`:

```
POST /weather HTTP/1.1
Host: api.example.com
X-Api-Key: your-provider-key
X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<HMAC-SHA256 hex>
X-ThunderPhone-Signature: <HMAC-SHA256 hex>
X-ThunderPhone-Call-ID: 987654321
Content-Type: application/json

{"zip": "94110"}
```

Tu servidor responde con JSON que se devuelve al LLM:

```json
{"temperature_f": 64, "condition": "Partly cloudy", "wind_mph": 8}
```

El LLM procesa esa respuesta y le comunica un resumen comprensible a quien llama.

<Warning>
  La firma se calcula sobre el cuerpo de la solicitud sin procesar usando el mismo
  `secret` que tu endpoint de webhook. **Verifícala** — los endpoints de herramientas
  están expuestos a internet y sujetos a los mismos riesgos de suplantación que los
  webhooks. Consulta
  [Verificar firmas de webhook](/es/guides/verify-webhook-signatures).
</Warning>

## 6. Prueba el ciclo

Inicia una [sesión de micrófono](/api-reference/mic-sessions) con el agente
y haz la pregunta que gestiona tu herramienta ("¿Cómo está el clima en
94110?"). La transcripción de la llamada muestra el ciclo completo:

```json
{
  "call_id": 987654321,
  "transcripts": [
    { "role": "user",
      "content": "What's the weather in 94110?" },
    { "role": "tool_call",
      "content": "{\"tool_call\": \"get_weather\", \"arguments\": {\"zip\": \"94110\"}}" },
    { "role": "tool_response",
      "content": "{\"tool_name\": \"get_weather\", \"response\": {\"temperature_f\": 64, \"condition\": \"Partly cloudy\"}}" },
    { "role": "agent",
      "content": "It's 64 degrees and partly cloudy." }
  ]
}
```

Puedes obtenerla mediante
[`GET /v1/calls/{call_id}/transcript`](/api-reference/calls#get-transcript);
el flujo de eventos sin procesar (con tiempos por entrada y desplazamientos de audio) está en
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

En las vistas de lista de llamadas y analíticas, ThunderPhone ahora informa las llamadas de prueba por separado del tráfico real. Las pruebas y simulaciones de Browser Talk pueden reutilizar un número de teléfono real y parecer entrantes, pero no cuentan para los totales de llamadas de producción, las calificaciones ni las métricas de incidencias.

## Problemas comunes

<AccordionGroup>
  <Accordion title="El agente nunca llama a la herramienta">
    El LLM decide según la descripción de la herramienta. Si la pregunta de quien llama
    no coincide con la descripción, el modelo no invocará la herramienta. Haz la
    descripción más precisa (agrega sinónimos y frases comunes) o menciónala
    explícitamente en el prompt del agente ("Cuando quien llama pregunte sobre el
    clima, usa `get_weather`.").
  </Accordion>

  <Accordion title="La herramienta devuelve demasiados datos">
    Las respuestas de más de 6 kB se truncan en la vista previa de la transcripción. Devuelve
    solo los campos que el LLM necesita, no toda la fila.
  </Accordion>

  <Accordion title="Tiempos de espera">
    Los endpoints de herramientas tienen un tiempo de espera predeterminado de 10 segundos. Si necesitas más tiempo,
    manéjalo de forma asíncrona: devuelve `{"status": "pending", "request_id": "..."}`
    y muestra el resultado mediante una llamada de herramienta independiente.
  </Accordion>

  <Accordion title="Control de versiones">
    Cada `PATCH` de integración crea una nueva revisión. Consulta
    [`GET /v1/integrations/{id}/versions`](/api-reference/integrations#version-history)
    para ver quién cambió qué. Si dañas el esquema de una herramienta, puedes
    revertirlo manualmente aplicando PATCH a una instantánea anterior.
  </Accordion>
</AccordionGroup>

---

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Referencia de integraciones" icon="plug" href="/api-reference/integrations">
    CRUD, transferencia, historial de versiones.
  </Card>
  <Card title="Especificación de herramientas de función" icon="screwdriver-wrench" href="/es/tools/overview">
    Gramática completa del esquema JSON y el contrato de endpoint firmado.
  </Card>
  <Card title="Verificar firmas" icon="shield-check" href="/es/guides/verify-webhook-signatures">
    Aplica el patrón de firma de webhook a los endpoints de herramientas.
  </Card>
  <Card title="API de transcripción e historial" icon="phone" href="/api-reference/calls">
    Inspecciona el ciclo completo de una llamada de herramienta.
  </Card>
</CardGroup>
