> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zelto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Proveedores personalizados y otros

> Crea una clave de API y envía conversaciones a Zelto desde cualquier plataforma de voz o texto que todavía no admitamos de forma nativa.

## Crear una clave

1. Abre **Configuración → Integraciones → Carga de llamadas por API** y haz clic
   en **Crear clave de API**.
2. Asigna un nombre descriptivo, por ejemplo `prod-twilio-uploader`.
3. Copia la clave **una sola vez**. Zelto solo guarda su hash.

La tarjeta muestra las claves activas y la última vez que se utilizaron. Puedes
eliminar una clave en cualquier momento para revocar el acceso inmediatamente.

Para crear o revocar claves necesitas el [rol](/es/docs/settings#miembros-e-invitaciones)
`owner` o `admin`. Una clave permite leer todas las conversaciones de la
organización, así que no es una credencial para un miembro individual.

## Configurar con un agente de codificación

¿Prefieres no implementar la conexión a mano? Pega el siguiente mensaje en el
agente de programación de tu editor, como Claude Code, Cursor o Codex. El mensaje
remite a esta guía y a la [referencia de API](/es/api-reference/agents/list-agents),
describe el contrato completo y pide al agente que siga las convenciones de tu
base de código. Tú debes proporcionar la clave: [créala arriba](#crear-una-clave)
y expónla como `ZELTO_API_KEY`. El mensaje indica que debe leerla desde el entorno
y que nunca debe incluirla directamente en el código.

<div className="prompt-scroll">
  ````text title="Pega esto en tu agente de programación" theme={null}
  Estás integrando mi aplicación con Zelto, una plataforma de analítica en
  producción para agentes de IA de voz y chat. Zelto recibe cada conversación
  finalizada mediante un endpoint HTTP y después la transcribe y analiza. Tu
  tarea es añadir a esta base de código el envío por POST de cada conversación
  finalizada a Zelto.

  Lee estas fuentes antes de escribir código. Son la referencia definitiva; si
  algo de este mensaje entra en conflicto con ellas, sigue la documentación:
  - Guía: https://docs.zelto.ai/es/docs/integrations/api-call-upload
  - Referencia de API (formato completo y área para probar una carga):
    https://docs.zelto.ai/es/api-reference/agents/list-agents
  - Si este editor tiene conectado el servidor MCP de Zelto, llama a la
    herramienta `read_docs` con la ruta `integrations/api-call-upload` para
    consultar la versión actual.

  Contrato:
  - POST https://ingest.zelto.ai/webhooks/calls
  - Cabeceras: `Authorization: Bearer $ZELTO_API_KEY`,
    `X-Zelto-Provider: zelto`, `Content-Type: application/json`. Lee la clave de
    la variable de entorno ZELTO_API_KEY; nunca la incluyas directamente en el
    código ni la escribas en los registros.
  - Obligatorio: `call.externalId` (identificador estable y único de la
    conversación) y una referencia al agente: `agent.externalId` (Zelto busca o
    crea el agente; usa un identificador estable por agente, no por conversación)
    o `agentId` (UUID de un agente existente en Zelto). Todo lo demás es opcional.
  - Envía `version` en el nivel superior de cada llamada si la integración va a
    ejecutar experimentos. La API permite omitirlo, pero las etiquetas explícitas
    identifican el despliegue que atendió la sesión. Conserva el ID del agente y
    la etiqueta por versión; cámbiala al modificar el prompt o pipeline. No uses
    valores por llamada. Envía `systemPrompt` de esa versión y conserva ambos en
    los reintentos. Consulta https://docs.zelto.ai/es/docs/agent-versions.
  - `companyExternalId` opcional: identificador estable del cliente o marca al
    que pertenece esta llamada (máximo 255 caracteres). Cuando
    [Empresas](/es/docs/companies) está activado, Zelto lo asocia con una empresa
    y, si es necesario, crea una empresa temporal al utilizarlo por primera vez.
  - `agent.groups` opcional: lista de grupos —área, equipo, idioma o campaña— que
    se asignarán al agente, por ejemplo `["Support", "Acme"]`. Zelto busca o crea
    cada grupo por nombre y añade el agente. Es aditivo: una carga posterior no
    elimina grupos existentes. Permite filtrar informes y conversaciones por
    grupo. Se admiten hasta 20 nombres.
  - Cada elemento de `transcript.turns[]` tiene `{ role, content }`; `role` puede
    ser user, assistant, system o tool. Un turno `tool` también puede incluir un
    `toolCall` estructurado; consulta [Invocaciones de herramientas](#invocaciones-de-herramientas).
  - Llamadas de voz: envía también call.startedAt / call.endedAt (ISO 8601),
    call.recordingUrl (Zelto vuelve a alojar el audio) y los campos disponibles:
    call.durationSeconds, call.endedReason, call.cost,
    call.customer.{number,name}, además de startSeconds / endSeconds por turno.
    Cuando sea posible, añade `words[]` con `startSeconds` / `endSeconds` de cada
    palabra para reproducir la transcripción sincronizada con el audio.
  - Conversaciones de texto o chat: omite los campos de audio y telefonía
    (recordingUrl, segundos por turno y normalmente customer.number) y guarda el
    canal en metadata.
  - Respuesta: 200 `{ "received": true }` significa que la carga se aceptó y se
    puso en cola. El procesamiento es asíncrono; 200 no significa "procesamiento
    terminado". Una respuesta 4xx devuelve `{ "error", "details"? }`: 401 indica
    una clave ausente o no válida y 400 indica JSON incorrecto o un error de
    validación; details enumera los campos problemáticos.
  - Idempotencia: call.externalId evita duplicados. Volver a enviar el mismo
    identificador actualiza la conversación existente y nunca duplica el cobro,
    así que son seguros tanto los reintentos como los reenvíos de enriquecimiento.

  Cuerpo mínimo:

  ```json
  {
    "agent": {
      "externalId": "support-bot",
      "name": "Support Bot",
      "groups": ["Support"]
    },
    "version": "support-2026-09-a",
  "call": { "externalId": "conv_01HXYZ" },
    "transcript": {
      "turns": [
        { "role": "assistant", "content": "Hi, how can I help?" },
        { "role": "user", "content": "I need to reschedule." }
      ]
    }
  }
  ```

  Implementación:
  - Envía cada conversación una vez, justo después de que finalice, por ejemplo
    desde un hook de fin de llamada o sesión, utilizando los datos disponibles.
  - No bloquees el flujo del usuario: envía desde un trabajo o cola en segundo
    plano y tolera que Zelto no esté disponible temporalmente. Si la respuesta no
    es 200, registra el estado y el cuerpo y reintenta con espera progresiva; es
    seguro porque externalId evita duplicados.
  - Sigue las convenciones existentes de esta base de código para HTTP,
    configuración, registros y tareas asíncronas. Añade ZELTO_API_KEY al entorno
    o la configuración.

  Verificación: configura ZELTO_API_KEY, ejecuta una conversación real y confirma
  que aparece en Conversaciones dentro del panel de Zelto en pocos segundos. Si
  no aparece, consulta el cuerpo de la respuesta POST para conocer el motivo.
  ````
</div>

<Tip>
  Si tu editor tiene conectado el [servidor MCP de Zelto](/es/docs/mcp), el agente
  puede utilizar `read_docs` para consultar siempre la versión actual del
  contrato. La [misma clave](/es/docs/mcp#obtener-una-clave-de-api) sirve para MCP, la
  [API REST](/es/api-reference/agents/list-agents) y este flujo de carga.
</Tip>

## Subir una conversación

Envía toda la conversación —agente, transcripción, grabación opcional y
metadatos— en una sola solicitud POST a `/webhooks/calls`. Zelto confirma la
recepción inmediatamente y crea la conversación y la transcripción en segundo
plano. La pestaña **Referencia de API** explica las convenciones REST e incluye un
área de pruebas para validar una carga útil antes de utilizarla en producción.

El ejemplo siguiente representa una llamada de voz. Para una conversación de
texto se utiliza [el mismo formato sin los campos de audio](/es/docs/integrations/chatbot-text-conversations).

```bash theme={null}
curl -X POST https://ingest.zelto.ai/webhooks/calls \
  -H "Authorization: Bearer $ZELTO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Zelto-Provider: zelto" \
  -d @call.json
```

```json title="call.json" theme={null}
{
  "agent": {
    "externalId": "support-bot",
    "name": "Support Bot",
    "groups": ["Support", "Acme"]
  },
  "version": "v3",
  "call": {
    "externalId": "call_01HXYZ",
    "startedAt": "2026-05-29T15:00:00Z",
    "endedAt": "2026-05-29T15:02:03Z",
    "endedReason": "customer_hangup",
    "cost": 0.042,
    "recordingUrl": "https://your-cdn.example.com/call_01HXYZ.mp3",
    "customer": { "number": "+15555550123", "name": "Jane Doe" }
  },
  "transcript": {
    "turns": [
      { "role": "assistant", "content": "Hi, how can I help?", "startSeconds": 0 },
      { "role": "user", "content": "I need to reschedule.", "startSeconds": 3.2 }
    ]
  },
  "systemPrompt": "You are a friendly scheduling assistant.",
  "metadata": { "campaign": "spring-2026" }
}
```

Una carga exitosa devuelve HTTP `200` con `{ "received": true }`.

Solo son obligatorios `call.externalId` y una referencia al agente. Utiliza
`agent.externalId` para que Zelto busque o cree el agente, o `agentId` para
asociar la llamada con un agente que ya exista en el panel. Si incluyes
`call.recordingUrl`, Zelto vuelve a alojar el audio para que el reproductor siga
funcionando después de que caduque la grabación original.

Utiliza `version` para identificar la compilación del agente o la revisión del
prompt que atendió la llamada, por ejemplo `"v3"`. Zelto la guarda como la versión
del agente para que puedas comparar el rendimiento entre versiones. El campo es
opcional y funciona con cualquiera de las referencias al agente. Envía siempre
la misma cadena para las llamadas de una misma compilación.

### Enviar la configuración del despliegue

Envía `versionConfig` opcional en el nivel superior junto con `version` para registrar todo lo que usa el despliegue. Es un objeto JSON de hasta 64 KiB; `llm`, `stt` y `tts` son nombres convencionales, y se conservan herramientas, flujos, ejecución y campos propios. Este contexto es independiente de `metadata` por llamada. Se captura al crear y no reemplaza una captura existente. Consulta [Versiones de agentes](/es/docs/agent-versions) para el formulario, la API y un ejemplo completo.

### Contrato de versiones para experimentos

Para atribuir correctamente las llamadas, incluye `version` en **cada** carga
de cada despliegue. Es una cadena de 1 a 255 caracteres, sin espacios en los
extremos y sensible a mayúsculas. El mismo agente y etiqueta reutilizan una
versión; una etiqueta nueva la crea al procesar su primera llamada. Cámbiala
al modificar el pipeline o prompt. `metadata.version` no asigna la versión.

Conserva el ID del agente entre despliegues e indica la versión que atendió la
sesión, junto con su `systemPrompt` y configuración capturada. La detección
automática basada en prompts no distingue de inmediato despliegues simultáneos
ni cambios exclusivos del pipeline.

Consulta [Versiones de agentes](/es/docs/agent-versions) para ver ejemplos,
límites de capturas, comportamiento por proveedor, reintentos y verificación.

### Atribuir cada llamada a una empresa

Si tu organización gestiona agentes para varios clientes o marcas, envía
`companyExternalId` en el nivel superior. La atribución se realiza por llamada,
por lo que un agente compartido puede atender llamadas consecutivas para empresas
distintas:

```json theme={null}
{
  "agent": {
    "externalId": "shared-collections-agent",
    "name": "Shared Collections"
  },
  "companyExternalId": "acme-bank",
  "call": {
    "externalId": "call-001"
  }
}
```

Utiliza el mismo valor estable para todas las llamadas de una empresa. Cuando la
función Empresas está activada, el primer valor desconocido crea una empresa
temporal con ese nombre. Puedes cambiarlo en el panel sin alterar la asignación de
las llamadas posteriores. Si omites el campo, Zelto utiliza la empresa
predeterminada del agente, si existe; de lo contrario, la llamada queda sin
atribuir.

Consulta [Empresas](/es/docs/companies) para entender el modelo y
[Configurar empresas](/es/docs/guides/set-up-companies) para seguir los pasos en
el panel.

### Grupos de agentes

Envía `agent.groups` para organizar agentes relacionados, por ejemplo por área,
idioma, equipo o campaña. Cada nombre representa un grupo; Zelto lo busca o lo
crea en la organización y añade el agente. Un agente puede pertenecer a varios
grupos y el campo es aditivo: una carga posterior nunca elimina un grupo
existente. Los grupos permiten filtrar todos los informes y la
[lista de conversaciones](/es/docs/conversations). También puedes administrarlos
manualmente desde la configuración del agente.

<Note>
  Un grupo no atribuye llamadas a una empresa. Utiliza `companyExternalId` para
  un cliente o una marca, y `agent.groups` para organizar agentes con flexibilidad.
</Note>

```json theme={null}
{
  "agent": {
    "externalId": "collections-bot-es",
    "name": "Collections Bot (ES)",
    "groups": ["Acme", "Collections Q2"]
  },
  "call": { "externalId": "call_01HXYZ" }
}
```

### Invocaciones de herramientas

Si el agente invoca herramientas durante la llamada —por ejemplo, para consultar
disponibilidad, reservar una cita o buscar un pedido—, envía cada invocación como
un turno `tool` con un objeto `toolCall`. Zelto la muestra en la transcripción, la
registra como una [traza de invocación de herramienta](/es/docs/conversations#invocaciones-de-herramientas)
consultable y utiliza su nombre, argumentos y resultado en los análisis de IA.
Envía la llamada una sola vez como datos estructurados; no necesitas volver a
representarla manualmente en `content`.

Un `toolCall` tiene la forma `{ name, arguments?, result?, status?, toolCallId?,
errorMessage? }`; solo `name` es obligatorio. `arguments` y `result` aceptan
cualquier valor JSON; `status` puede ser `success`, `error` o `pending`; y
`toolCallId` relaciona una invocación con su resultado si los envías en turnos
separados. `content` puede quedar vacío cuando existe `toolCall`, porque Zelto
utiliza el nombre de la herramienta como etiqueta. `startSeconds` lo coloca en la
línea de tiempo.

```json theme={null}
{
  "agent": { "externalId": "scheduler-bot" },
  "call": { "externalId": "call-1015", "startedAt": "2026-07-08T18:04:00Z" },
  "transcript": {
    "turns": [
      { "role": "user", "content": "Any openings Friday?", "startSeconds": 4 },
      {
        "role": "tool",
        "startSeconds": 6,
        "toolCall": {
          "name": "check_availability",
          "toolCallId": "tc_1",
          "arguments": { "date": "2026-07-10" },
          "result": { "slots": ["09:00", "13:30"] },
          "status": "success"
        }
      },
      { "role": "assistant", "content": "I have 9am or 1:30pm.", "startSeconds": 9 }
    ]
  }
}
```

### Conversaciones de texto y mensajería

El mismo endpoint admite conversaciones de texto de chatbots o agentes de
mensajería, como chat web, SMS, WhatsApp o chat dentro de la aplicación. Envía
`transcript.turns` y omite los campos de audio y telefonía, ya que no existe una
grabación que volver a alojar. Consulta
[Chatbot y conversaciones de texto](/es/docs/integrations/chatbot-text-conversations)
para ver el contrato completo y un ejemplo de `chat.json`.

### Manejar errores

Los errores se devuelven como JSON con una cadena `error`. Los errores de
validación y del proveedor también incluyen un objeto `details` con lo que debes
corregir.

| Situación | `error` | Cuando |
| - | - | - |
| `401` | `Unauthorized` | La clave de API falta o no es válida. También se rechaza una clave cuyo propietario ya no pertenece a la organización o tiene la cuenta bloqueada. |
| `400` | `Invalid JSON` | El cuerpo no es válido JSON. |
| `400` | `Unsupported provider` | `X-Zelto-Provider` se establece en un valor no reconocido. |
| `400` | `Validation failed` | Falta un campo o tiene un tipo incorrecto; `details` enumera cada campo problemático. |

<Note>
  Una respuesta `200` significa que Zelto **aceptó** la carga, no que haya terminado
  de procesarla. Los problemas de autenticación y los cuerpos mal formados fallan
  inmediatamente con `4xx`, pero un problema semántico —como un `agentId` que no
  existe en la organización— puede devolver `200` y fallar después. Para evitarlo,
  utiliza `agent.externalId`, que busca o crea el agente, y confirma que la llamada
  aparece en [Conversaciones](/es/docs/conversations).

  Una entrega **sin transcripción ni grabación** se considera una llamada no
  conectada. Zelto responde con `200`, pero no crea una conversación porque no hay
  nada que transcribir o analizar. Envía la llamada cuando dispongas de una
  transcripción o de `recordingUrl`; también puedes hacer un
  [reenvío de enriquecimiento](#cargas-repetidas-idempotentes) más adelante.
</Note>

### Cargas repetidas idempotentes

`call.externalId` es la clave de idempotencia. Si vuelves a enviar el mismo
identificador, Zelto **actualiza** la conversación existente —grabación,
transcripción, duración o costo— y nunca la duplica. Envía la llamada al finalizar
y vuelve a enviarla después si añades información, por ejemplo cuando termine de
subirse la grabación.

### ¿Ya estás en Vapi o Retell?

El mismo endpoint también acepta las cargas útiles nativas de los webhooks de
Vapi y Retell. Configura la cabecera `X-Zelto-Provider` como `vapi` o `retell` y
envía el cuerpo del proveedor sin modificarlo. Omite la cabecera o utiliza
`zelto` para enviar el formato canónico anterior.

## Verificar la primera llamada

Después de la primera solicitud, abre [Conversaciones](/es/docs/conversations).
La llamada debería aparecer con su transcripción en pocos segundos. El área de
pruebas de **Referencia de API** es la forma más rápida de enviar una carga de
ejemplo. Si la llamada no aparece, revisa la respuesta: `{ "received": true }`
indica que Zelto la aceptó y una respuesta `4xx` explica el motivo, como un valor
incorrecto de `X-Zelto-Provider`, un campo no válido o una clave incorrecta.
Consulta
[Conectar un proveedor de voz](/es/docs/guides/connect-a-voice-provider#diagnosticar-una-llamada-ausente).

## Cuándo usar esto vs una integración nativa

Si ya utilizas [Vapi](/es/docs/integrations/vapi) o
[Retell](/es/docs/integrations/retell), elige la integración nativa: recibe los
webhooks y obtiene las grabaciones automáticamente. Esta guía sirve para todo lo
demás, como Bland, Pipecat, agentes de texto o mensajería, plataformas internas o
cualquier proveedor sin una integración dedicada. Para **LiveKit**, consulta su
[guía específica](/es/docs/integrations/livekit).

## Relacionado

* [Chatbot y conversaciones de texto](/es/docs/integrations/chatbot-text-conversations) — el mismo endpoint para texto, sin audio.
* [Conectar un proveedor de voz](/es/docs/guides/connect-a-voice-provider) — elige el método adecuado.
* [LiveKit](/es/docs/integrations/livekit) — el mismo endpoint con un ejemplo para un worker.
* [Referencia de API](/es/api-reference/agents/list-agents) — convenciones REST y formato completo de la solicitud.
* [Conversaciones](/es/docs/conversations) — dónde aparecen las llamadas cargadas.
