# API REST

> Chatea con tus agentes de intoCHAT, lee sus conversaciones, leads y fuentes de conocimiento, mantén sus contactos sincronizados y suscribe webhooks desde tu propio servidor o una herramienta de automatización: claves de API, ámbitos, límites de frecuencia, errores, paginación y todos los endpoints con ejemplos.

La API REST permite que tu propio servidor hable con tus agentes. Envía un mensaje y recibe la respuesta del agente, lee conversaciones y leads, crea y actualiza contactos, añade o elimina fuentes de conocimiento y suscribe webhooks a los eventos de un agente, con JSON sobre HTTPS. Las [apps de Zapier y Make](/es/docs/zapier-and-make) están construidas sobre ella. Una respuesta a través de la API la escribe el mismo código que una respuesta en el widget del chat, con el mismo conocimiento y los mismos ajustes.

La API es para servidores. No envía cabeceras CORS, así que una página web de otro sitio no puede llamarla, y tu clave de API nunca debe aparecer en un navegador ni en tu código de inserción. Para mostrar un chat en tu web, usa la [etiqueta script](/es/docs/script-tag). Para recibir los eventos en cuanto ocurren, usa los [webhooks](/es/docs/webhooks).

## Qué planes incluyen la API

La API está incluida en **Starter**, **Pro** y **Enterprise**. En **Free** no puedes crear claves.

Si una cuenta vuelve a Free, sus claves siguen en la lista, pero cada petición recibe un error `403` con el código `plan_required`. Vuelven a funcionar en cuanto la cuenta está en Starter o superior.

Cada respuesta que un agente escribe a través de la API cuenta como un mensaje de tu plan, exactamente igual que una respuesta en el widget, y cuenta para el [tope mensual de mensajes](/es/docs/limits-and-access#tope-mensual-de-mensajes) del agente si has fijado uno. Leer datos, y añadir o eliminar fuentes, no gasta mensajes. Consulta [Qué cuenta como mensaje](/es/docs/plans-and-limits#que-cuenta-como-mensaje).

## Crear una clave de API

1. En la barra lateral, en **Settings**, haz clic en **API keys**.
2. En **Create a key**, ponle a la clave un **Name** que reconozcas más adelante, por ejemplo `CRM sync`.
3. En **Access**, deja **Every scope, including ones added later**, o elige **Only the scopes I choose** y marca los que necesita esta clave. Consulta [Ámbitos](#ambitos).
4. Haz clic en **Create key**.
5. Haz clic en **Copy**, guarda la clave donde tu servidor pueda leerla, por ejemplo en una variable de entorno, y haz clic en **I've saved it**.

Una clave tiene el aspecto `ic_live_` seguido de 32 letras y dígitos. Solo se muestra una vez: intoCHAT guarda únicamente una huella de ella, así que nadie puede volver a mostrártela. Si la pierdes, revócala y crea una nueva.

Una clave pertenece a la cuenta, no a la persona que la creó. Funciona para todos los agentes de la cuenta, dentro de sus ámbitos, y sigue funcionando si esa persona deja el equipo. Una cuenta puede tener hasta 20 claves activas.

Solo el propietario de la cuenta y los admins pueden crear y revocar claves. Los editors y los viewers ven la lista de claves, solo con sus primeros caracteres. Consulta [Miembros del equipo y roles](/es/docs/team).

### La lista de claves

Cada clave muestra su nombre, sus primeros caracteres (por ejemplo `ic_live_3f9A…`), sus ámbitos, cuándo se creó y cuándo se usó por última vez. **Last used** se actualiza como mucho una vez por minuto.

### Revocar una clave

Haz clic en **Revoke** junto a la clave y confirma. La clave deja de funcionar desde su siguiente petición, y no se puede deshacer. Revoca una clave en cuanto pienses que otra persona puede tenerla.

Revocar una clave también elimina los webhooks que [suscribió](#suscribir-un-webhook), por ejemplo para Zapier o Make, con su historial de envíos: de otro modo, nada podría eliminarlos. Los webhooks que añadiste en la pestaña **Leads** no se ven afectados.

## Autenticación

Envía la clave en la cabecera `Authorization` de cada petición:

```bash
curl https://www.intochat.ai/api/v1/agents \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

Todos los endpoints están bajo `https://www.intochat.ai/api/v1`. Las peticiones con cuerpo envían JSON con `Content-Type: application/json`, y todas las respuestas son JSON, salvo una respuesta de chat en streaming. La API no usa cookies ni sesión iniciada: solo la clave decide qué puede hacer una petición.

Una clave que falta, mal formada, desconocida o revocada recibe un error `401` con el código `unauthorized`.

## Ámbitos

Un ámbito (scope) permite a una clave usar un grupo de endpoints. Una clave creada con **Every scope, including ones added later** puede usar todos los endpoints, también los que se añadan en el futuro.

| Ámbito | Permite |
| --- | --- |
| `agents:read` | [Listar agentes](#listar-agentes) y [obtener un agente](#obtener-un-agente) |
| `chat` | [Enviar un mensaje](#enviar-un-mensaje) a un agente. Cada respuesta cuenta como un mensaje de tu plan. |
| `conversations:read` | [Listar conversaciones](#listar-conversaciones) y [obtener una conversación](#obtener-una-conversacion) con su transcripción |
| `leads:read` | [Listar leads](#listar-leads), [listar contactos](#listar-contactos) y [obtener un contacto](#obtener-un-contacto), y los eventos de leads, formularios, reservas y devoluciones de [Listar eventos](#listar-eventos) y [Obtener eventos de ejemplo](#obtener-eventos-de-ejemplo) |
| `contacts:write` | [Crear o actualizar un contacto](#crear-o-actualizar-un-contacto), [cambiar un contacto](#cambiar-un-contacto) y [eliminar un contacto](#eliminar-un-contacto) |
| `sources:read` | [Listar fuentes](#listar-fuentes) |
| `sources:write` | [Añadir una fuente](#anadir-una-fuente) y [eliminar una fuente](#eliminar-una-fuente), lo que también inicia el entrenamiento |
| `webhooks:write` | [Suscribir un webhook](#suscribir-un-webhook) y [cancelar la suscripción de un webhook](#cancelar-la-suscripcion-de-un-webhook) |

`conversations:read` también cubre los eventos de traspaso, chat en vivo y conversación nueva de [Listar eventos](#listar-eventos).

Una petición a un endpoint para el que la clave no tiene ámbito recibe un error `403` con el código `insufficient_scope`. Para cambiar los ámbitos de una clave, crea una clave nueva y revoca la anterior.

## Límites de frecuencia

Cada clave puede hacer 60 peticiones por minuto, entre todos los endpoints. Cada respuesta que superó la comprobación de la clave lleva estas cabeceras:

| Cabecera | Valor |
| --- | --- |
| `X-RateLimit-Limit` | `60` |
| `X-RateLimit-Remaining` | Peticiones que quedan en el minuto actual |
| `X-RateLimit-Reset` | Cuándo termina el minuto, en segundos Unix |

Por encima del límite, la petición recibe un error `429` con el código `rate_limited` y una cabecera `Retry-After` con los segundos que hay que esperar.

El chat tiene dos límites más que no dependen de la clave: los mensajes del plan, y que un agente responde como mucho 1.000 mensajes por hora, entre el widget y la API. Por encima de eso, una petición de chat recibe un error `429` con el código `agent_busy`.

## Errores

Todos los errores tienen la misma forma:

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key doesn't have the \"chat\" scope."
  }
}
```

Usa `code` en tu código; `message` lo explica para una persona y puede cambiar.

| Estado | Código | Significado |
| --- | --- | --- |
| `400` | `invalid_request` | A la petición le falta algo o tiene un valor incorrecto. El mensaje dice cuál. |
| `401` | `unauthorized` | No hay clave, o la clave está mal formada, es desconocida o está revocada. |
| `403` | `insufficient_scope` | La clave no tiene el ámbito que necesita este endpoint. |
| `403` | `plan_required` | La cuenta está en Free. La API está incluida a partir de Starter. |
| `403` | `message_limit_reached` | La cuenta ha usado todos los mensajes que incluye su plan en este periodo. |
| `403` | `agent_cap_reached` | El agente ha llegado al tope mensual de mensajes fijado en **Share**, **Limits and access**. |
| `403` | `agent_private` | El agente es privado, así que solo responde en el panel. Consulta [Agentes privados](/es/docs/allowed-domains#agentes-privados). |
| `403` | `character_limit_exceeded` | Una fuente nueva haría que el agente superara los caracteres de entrenamiento de su plan. |
| `403` | `contact_limit_reached` | Un contacto nuevo haría que el agente superara los contactos por agente de su plan. |
| `404` | `not_found` | No existe ese agente, esa conversación, esa fuente, ese contacto o ese webhook en esta cuenta. |
| `409` | `conflict` | Otro contacto del agente ya tiene este ID externo o este correo. |
| `429` | `rate_limited` | La clave ha hecho más de 60 peticiones en este minuto. |
| `429` | `agent_busy` | El agente está respondiendo demasiados mensajes en este momento. |
| `500` | `internal_error` | Algo ha fallado por nuestra parte. Inténtalo de nuevo. |
| `504` | `timeout` | El agente ha tardado demasiado en responder. |

Un agente, una conversación o una fuente que pertenece a otra cuenta recibe `404`, igual que uno que no existe.

## Paginación

[Listar conversaciones](#listar-conversaciones) y [listar leads](#listar-leads) devuelven una página cada vez, de la más reciente a la más antigua. Admiten estos parámetros de consulta:

| Parámetro | Contenido |
| --- | --- |
| `limit` | Elementos por página, de 1 a 100. Por defecto, 20. |
| `cursor` | El `nextCursor` de la página anterior, para obtener la siguiente. |
| `since` | Una hora ISO 8601, por ejemplo `2026-10-01T00:00:00Z`. Solo se devuelven los elementos con actividad en ese momento o después: una conversación con un mensaje desde entonces, o un lead que se guardó o se volvió a enviar desde entonces. |

Una página tiene este aspecto:

```json
{
  "data": [ ],
  "hasMore": true,
  "nextCursor": "MjAyNi0xMC0wNFQwOTowMDowMC4wMDBafGNsdjEyMw"
}
```

Pide la página siguiente con `?cursor=` y ese valor, con los mismos `limit` y `since`, hasta que `hasMore` sea `false` y `nextCursor` sea `null`. El orden lo fija la fecha de creación, así que la actividad nueva mientras recorres las páginas no hace que un elemento aparezca dos veces ni que falte. Para sincronizar con regularidad, guarda la hora en que empezaste la última sincronización y pásala como `since` la próxima vez.

## Listar agentes

`GET /api/v1/agents` · ámbito `agents:read`

Todos los agentes de la cuenta, del más reciente al más antiguo.

```bash
curl https://www.intochat.ai/api/v1/agents \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "AGENT_ID",
      "name": "Acme Assistant",
      "private": false,
      "status": "trained",
      "trainedAt": "2026-10-02T09:00:00.000Z",
      "characters": 48210,
      "counts": { "conversations": 312, "messages": 2240, "leads": 41, "sources": 18 },
      "createdAt": "2026-09-01T09:00:00.000Z",
      "updatedAt": "2026-10-02T09:00:00.000Z"
    }
  ]
}
```

`status` es `untrained`, `training`, `trained` o `error` (el último entrenamiento falló). `characters` es cuántos caracteres de entrenamiento usan las fuentes entrenadas del agente.

## Obtener un agente

`GET /api/v1/agents/{agentId}` · ámbito `agents:read`

Un agente, en `data`, con los mismos campos que en [Listar agentes](#listar-agentes).

## Enviar un mensaje

`POST /api/v1/agents/{agentId}/chat` · ámbito `chat`

Envía un mensaje al agente y devuelve su respuesta. El agente responde como en el widget: a partir de su conocimiento y sus instrucciones, con tus [acciones API](/es/docs/api-actions), [botones personalizados](/es/docs/custom-buttons), [formularios de chat](/es/docs/chat-forms), [reservas](/es/docs/booking), [traspaso por correo](/es/docs/handoff) y [chat en vivo](/es/docs/live-chat) donde los hayas configurado.

| Campo | Contenido |
| --- | --- |
| `message` | Obligatorio. El texto, hasta 10.000 caracteres. |
| `conversationId` | Opcional. Continúa una conversación que empezaste a través de la API, con el `conversationId` de una respuesta anterior. |
| `sessionId` | Opcional. Tu propio ID para la persona por la que chateas, por ejemplo tu ID de usuario, hasta 100 caracteres. El mismo valor continúa siempre la misma conversación. También puedes enviar el `sessionId` de una respuesta anterior. |
| `stream` | Opcional. `true` envía el texto de la respuesta en streaming; consulta [Streaming](#streaming). Por defecto, `false`. |
| `timeZone` | Opcional. La zona horaria IANA de la persona, por ejemplo `Europe/Berlin`, para las horas de las reservas. Sin ella, las horas están en UTC. |

Sin `conversationId` ni `sessionId`, cada mensaje empieza una conversación nueva.

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/chat \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Do you ship to Switzerland?", "sessionId": "customer-4821" }'
```

```json
{
  "reply": "Yes, we ship to Switzerland. Delivery takes 3 to 5 working days.",
  "conversationId": "CONVERSATION_ID",
  "sessionId": "icapi5f0c2b9e4d7a41f8a3c6e1b2d9f07a64",
  "sources": [
    { "title": "Shipping", "url": "https://www.example.com/shipping" }
  ],
  "followUps": ["How much does shipping cost?", "Can I track my order?"],
  "buttons": [],
  "products": [],
  "form": null,
  "leadForm": null,
  "booking": null,
  "liveChat": null
}
```

| Campo | Contenido |
| --- | --- |
| `reply` | La respuesta del agente como texto plano con Markdown, sin los bloques de control del widget. Vacía mientras alguien de tu equipo lleva el chat (consulta `liveChat`). |
| `conversationId` | La conversación, como en [Obtener una conversación](#obtener-una-conversacion). |
| `sessionId` | La sesión de la conversación en intoCHAT. Siempre empieza por `icapi`. |
| `sources` | Las páginas en las que se basó la respuesta, como `title` y `url`, cuando **Show sources under replies** está activado. Las páginas de la [búsqueda web](/es/docs/web-search) siempre aparecen, con `"web": true`. |
| `followUps` | Preguntas siguientes sugeridas, cuando **Suggest follow-up questions** está activado. |
| `buttons` | Los [botones personalizados](/es/docs/custom-buttons) que eligió el agente, como `label` y `url`. |
| `products` | Tarjetas de producto de tu [tienda](/es/docs/connect-your-store) o de acciones API, con `name`, `url` y, cuando se conocen, `image`, `price`, `compareAtPrice`, `currency` y `available`. |
| `form` | Un [formulario de chat](/es/docs/chat-forms) que ofreció el agente, con su `formId`, `name`, `fields` y `submitLabel`, o `null`. La API no puede enviarlo; muéstralo en tu propia interfaz o ignóralo. |
| `leadForm` | El [formulario de leads](/es/docs/lead-collection), con su `message`, los `fields` de contacto y `form`, cuando el agente pidió datos de contacto, o `null`. Los datos de contacto escritos en un mensaje se guardan como lead cuando **Save contact details written in the chat** está activado, igual que en el widget. |
| `booking` | Horas libres para citas, con `provider`, `eventTitle`, `eventLength` en minutos, `timeZone` y `days`, cada uno con su `date` y sus `slots` como horas UTC, o `null`. La API no puede reservar una hora. |
| `liveChat` | `null`, o el estado del [chat en vivo](/es/docs/live-chat): `offered` (el agente puede pedir a tu equipo que se una), `requested` (se lo pidió) o `paused` (un miembro del equipo lleva el chat, así que el agente no respondió). |

### En qué se diferencian los chats por API del widget

- La conversación se guarda como un chat del widget y aparece en la pestaña **Conversations** del agente, con sus leads, valoraciones y estadísticas. No tiene país ni página.
- Los [dominios permitidos](/es/docs/allowed-domains), los [países bloqueados](/es/docs/limits-and-access#paises-bloqueados) y los [mensajes por visitante](/es/docs/limits-and-access#mensajes-por-visitante) no se aplican: no hay página ni dirección del visitante que comprobar. Sí se aplican los mensajes del plan, el tope mensual del agente y el límite de frecuencia de la clave.
- Un [agente privado](/es/docs/allowed-domains#agentes-privados) rechaza los chats por API con `agent_private`.
- Las [acciones del lado del cliente](/es/docs/client-actions) se ejecutan en el navegador de un visitante, así que el agente no las tiene disponibles en los chats por API. Las acciones API, los botones, los formularios y las reservas funcionan.
- No se pueden adjuntar archivos y no hay chats temporales.
- Solo las conversaciones empezadas a través de la API se pueden continuar a través de la API. El agente ve como historial los últimos 40 mensajes de la conversación.
- Mientras alguien de tu equipo ha tomado el control del chat en la [bandeja de Live chat](/es/docs/live-chat#la-bandeja-de-live-chat), tu mensaje se guarda, `reply` está vacío y `liveChat` es `paused`. Las respuestas del miembro del equipo aparecen en la conversación: léelas con [Obtener una conversación](#obtener-una-conversacion), donde tienen un `authorName`. Un mensaje durante una toma de control no gasta ningún mensaje de tu plan.
- Una respuesta que tarda más de 55 segundos termina con un error `504` y el código `timeout`. Puede contar igualmente como mensaje.

### Streaming

Con `"stream": true`, la respuesta es el texto de la respuesta a medida que se escribe, como `text/plain`, sin los campos JSON. Las cabeceras `X-Conversation-Id` y `X-Session-Id` llevan la conversación y la sesión, y `X-Live-Chat` el estado del chat en vivo cuando lo hay. Las fuentes, los botones y los demás bloques no se envían en streaming; léelos después con [Obtener una conversación](#obtener-una-conversacion). Los errores son JSON, igual que sin streaming.

```bash
curl -N https://www.intochat.ai/api/v1/agents/AGENT_ID/chat \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "What are your opening hours?", "stream": true }'
```

## Listar conversaciones

`GET /api/v1/agents/{agentId}/conversations` · ámbito `conversations:read`

Las conversaciones del agente, del widget y de la API, de la más reciente a la más antigua, sin sus mensajes. Admite `limit`, `cursor` y `since`; consulta [Paginación](#paginacion).

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/conversations?limit=50&since=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "CONVERSATION_ID",
      "sessionId": "8f14e45f-ceea-467a-9575-1a2b3c4d5e6f",
      "channel": "widget",
      "createdAt": "2026-10-04T09:00:00.000Z",
      "lastActivityAt": "2026-10-04T09:12:00.000Z",
      "country": "CH",
      "page": "https://www.example.com/pricing",
      "messageCount": 6,
      "topics": ["Pricing"],
      "sentiment": "positive",
      "liveChat": null,
      "csatScore": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

`channel` es `api` para una conversación empezada a través de la API y, si no, `widget` (la etiqueta script, el iframe en la página, el enlace directo y el Playground). `topics` y `sentiment` los asigna el [análisis de temas](/es/docs/conversations-and-dashboard#temas-y-sentimiento) cuando está activado. `liveChat` es `null`, `requested`, `active` o `ended`. `csatScore` es la valoración del visitante de 1 a 5 después de un chat en vivo, o `null`.

## Obtener una conversación

`GET /api/v1/conversations/{conversationId}` · ámbito `conversations:read`

Una conversación de cualquier agente de la cuenta, con todos sus mensajes en orden.

```json
{
  "data": {
    "id": "CONVERSATION_ID",
    "agentId": "AGENT_ID",
    "sessionId": "icapi5f0c2b9e4d7a41f8a3c6e1b2d9f07a64",
    "channel": "api",
    "createdAt": "2026-10-04T09:00:00.000Z",
    "lastActivityAt": "2026-10-04T09:14:00.000Z",
    "country": null,
    "page": null,
    "topics": [],
    "sentiment": null,
    "handoffRequestedAt": null,
    "liveChat": { "status": "ended", "startedAt": "2026-10-04T09:05:00.000Z", "endedAt": "2026-10-04T09:13:00.000Z" },
    "csat": { "score": 5, "comment": "Quick and friendly.", "ratedAt": "2026-10-04T09:14:00.000Z" },
    "messages": [
      { "id": "MESSAGE_ID", "role": "assistant", "content": "Hello! How can I help you today?", "createdAt": "2026-10-04T09:00:00.000Z", "authorName": null, "sources": [] },
      { "id": "MESSAGE_ID", "role": "user", "content": "Can I change my delivery address?", "createdAt": "2026-10-04T09:01:00.000Z", "authorName": null, "sources": [] },
      { "id": "MESSAGE_ID", "role": "assistant", "content": "Yes, I'm on it. What's your order number?", "createdAt": "2026-10-04T09:06:00.000Z", "authorName": "Anna", "sources": [] }
    ]
  }
}
```

`role` es `user` para el visitante o tus mensajes por API, y `assistant` para el agente y tu equipo. `authorName` es el nombre de pila del miembro del equipo que escribió una respuesta de [chat en vivo](/es/docs/live-chat), o `null` cuando la escribió el agente. `content` es el texto sin los bloques de control del widget; las `sources` de una respuesta del agente listan las páginas que mostró. `handoffRequestedAt` es cuándo el agente envió la conversación a tu equipo por correo, o `null`. `liveChat` y `csat` son `null` cuando no hubo chat en vivo o valoración.

## Listar leads

`GET /api/v1/agents/{agentId}/leads` · ámbito `leads:read`

Los leads del agente, del más reciente al más antiguo. Admite `limit`, `cursor` y `since`; consulta [Paginación](#paginacion).

```json
{
  "data": [
    {
      "id": "LEAD_ID",
      "email": "jane@example.com",
      "phone": null,
      "name": "Jane",
      "customFields": [{ "id": "FIELD_ID", "label": "Company", "value": "Acme" }],
      "source": "form",
      "conversationId": "CONVERSATION_ID",
      "collectedAt": "2026-10-04T09:03:00.000Z",
      "lastSeenAt": "2026-10-04T09:03:00.000Z",
      "submissionCount": 1
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

`source` es `form` para el formulario de leads o `chat` para datos de contacto escritos en un mensaje. `customFields` usa las etiquetas que tiene tu formulario ahora. `lastSeenAt` y `submissionCount` cambian cuando la misma persona vuelve a enviarlo. `conversationId` es `null` cuando se borró la conversación.

## Contactos

Los [contactos](/es/docs/contacts) de un agente: visitantes verificados, leads, contactos importados y los que añades aquí, cada uno con hasta 50 atributos propios. Leerlos requiere `leads:read`; crearlos, cambiarlos y eliminarlos requiere `contacts:write`. Las claves creadas con **Every scope** tienen los dos.

Un contacto tiene este aspecto:

```json
{
  "id": "CONTACT_ID",
  "agentId": "AGENT_ID",
  "externalId": "user-4711",
  "email": "jane@example.com",
  "phone": "+41441234567",
  "name": "Jane",
  "attributes": { "plan": "pro", "seats": 5, "vip": true },
  "source": "api",
  "firstSeenAt": "2026-10-04T09:03:00.000Z",
  "lastSeenAt": "2026-10-04T09:03:00.000Z",
  "createdAt": "2026-10-04T09:03:00.000Z",
  "updatedAt": "2026-10-04T09:03:00.000Z"
}
```

`source` es `identity`, `lead`, `import` o `api`: de dónde vino el contacto la primera vez. `externalId` es el ID de usuario de la persona en tu web. Los correos se guardan en minúsculas.

### Listar contactos

`GET /api/v1/agents/{agentId}/contacts` · ámbito `leads:read`

Los contactos del agente, del más reciente al más antiguo. Admite `limit`, `cursor` y `since` (contactos cambiados en ese momento o después); consulta [Paginación](#paginacion). Añade `email=` o `external_id=` para buscar un contacto.

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/contacts?external_id=user-4711" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{ "data": [ { "id": "CONTACT_ID", "externalId": "user-4711", "email": "jane@example.com", "attributes": { "plan": "pro" } } ], "hasMore": false, "nextCursor": null }
```

`data` tiene todos los campos mostrados arriba; aquí se omiten algunos.

### Crear o actualizar un contacto

`POST /api/v1/agents/{agentId}/contacts` · ámbito `contacts:write`

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/contacts \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "user-4711", "email": "jane@example.com", "name": "Jane", "attributes": { "plan": "pro", "seats": 5 } }'
```

| Campo | Notas |
| --- | --- |
| `external_id` | Hasta 200 caracteres. Envía este o `email`. |
| `email` | Una dirección de correo válida. |
| `phone` | De 7 a 15 dígitos, opcionalmente empezando por `+`. |
| `name` | Hasta 100 caracteres. |
| `attributes` | Un objeto con nombres y valores de atributos. Los nombres empiezan por una letra minúscula y usan letras minúsculas, dígitos y guiones bajos, hasta 40 caracteres; consulta [Atributos](/es/docs/contacts#atributos). Un valor es un texto de hasta 1.000 caracteres, un número o `true`/`false`, y `null` quita el atributo. |

Se actualiza el contacto con este `external_id`, si no el que tiene este `email`, y si no se crea un contacto nuevo. Un contacto que solo tenía correo y se encuentra así recibe el `external_id`. Los campos que envías sustituyen a lo guardado, un campo con `null` se vacía y los campos que no envías se quedan como están. Los atributos se combinan: los que envías se fijan y los demás se conservan.

```json
{ "data": { "id": "CONTACT_ID", "externalId": "user-4711", "email": "jane@example.com", "attributes": { "plan": "pro", "seats": 5 } }, "result": "created" }
```

`result` es `created` (estado `201`) o `updated` (estado `200`). Un correo que pertenece a un contacto con otro ID externo, o un ID externo o un correo que ya tiene otro contacto, recibe un error `409` con el código `conflict`. Un contacto nuevo que supera los [contactos por agente](/es/docs/plans-and-limits#contactos) de tu plan recibe un error `403` con el código `contact_limit_reached`.

### Obtener un contacto

`GET /api/v1/contacts/{contactId}` · ámbito `leads:read`

Un contacto de cualquier agente de la cuenta, como `{ "data": { … } }`.

### Cambiar un contacto

`PATCH /api/v1/contacts/{contactId}` · ámbito `contacts:write`

Admite los mismos campos que [Crear o actualizar un contacto](#crear-o-actualizar-un-contacto), todos opcionales, y responde con el contacto como `{ "data": { … } }`. Un contacto tiene que conservar un ID externo, un correo o un teléfono.

### Eliminar un contacto

`DELETE /api/v1/contacts/{contactId}` · ámbito `contacts:write`

```json
{ "data": { "id": "CONTACT_ID", "deleted": true } }
```

Los leads y las conversaciones del contacto se conservan.

## Listar fuentes

`GET /api/v1/agents/{agentId}/sources` · ámbito `sources:read`

Las fuentes de conocimiento del agente en el orden de su pestaña **Knowledge**, con el progreso del entrenamiento. El texto de una fuente no se incluye, salvo la pregunta y la respuesta de un par de pregunta y respuesta.

```json
{
  "data": [
    {
      "id": "SOURCE_ID",
      "type": "qa",
      "title": "Do you ship to Switzerland?",
      "url": null,
      "filename": null,
      "question": "Do you ship to Switzerland?",
      "answer": "Yes, in 3 to 5 working days.",
      "characters": 71,
      "status": "trained",
      "error": null,
      "trainedAt": "2026-10-02T09:00:00.000Z",
      "createdAt": "2026-10-02T08:59:00.000Z",
      "updatedAt": "2026-10-02T09:00:00.000Z"
    }
  ],
  "training": { "total": 18, "trained": 18, "pending": 0, "processing": 0, "failed": 0, "done": true },
  "characters": { "used": 48210, "limit": 500000 }
}
```

`type` es `website`, `file`, `text`, `qa` o `notion`. `status` es `pending`, `processing`, `trained` o `failed`, con el motivo en `error`. `characters.limit` son los caracteres de entrenamiento por agente de tu plan.

## Añadir una fuente

`POST /api/v1/agents/{agentId}/sources` · ámbito `sources:write`

Añade un [texto o un par de pregunta y respuesta](/es/docs/text-and-qa) e inicia el entrenamiento, como hace la pestaña **Knowledge**. El agente usa la fuente nueva en cuanto está entrenada; sigue su `status` con [Listar fuentes](#listar-fuentes).

Un texto:

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/sources \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "text", "title": "Opening hours", "content": "We are open Monday to Friday, 9:00 to 17:00." }'
```

Un par de pregunta y respuesta:

```json
{ "type": "qa", "question": "Do you ship to Switzerland?", "answer": "Yes, in 3 to 5 working days." }
```

```json
{
  "data": { "id": "SOURCE_ID", "type": "text", "title": "Opening hours", "status": "pending", "characters": 44 },
  "result": "created",
  "training": { "total": 19, "trained": 18, "pending": 1, "processing": 0, "failed": 0, "done": false }
}
```

`data` tiene todos los campos de [Listar fuentes](#listar-fuentes); arriba se omiten algunos. `result` es `created` (estado `201`), `updated` cuando ya existía un par con la misma pregunta, sin distinguir mayúsculas y minúsculas, y ahora tiene la respuesta nueva, o `unchanged` cuando ya tenía exactamente esta respuesta (ambos con estado `200`). Un texto siempre se añade como fuente nueva. Un título y una pregunta pueden tener cada uno hasta 200 caracteres.

La fuente cuenta para los caracteres de entrenamiento del agente. Una fuente que superaría el límite de tu plan recibe un error `403` con el código `character_limit_exceeded`, y no se añade nada. Consulta [Caracteres de entrenamiento por agente](/es/docs/plans-and-limits#caracteres-de-entrenamiento-por-agente).

## Eliminar una fuente

`DELETE /api/v1/agents/{agentId}/sources/{sourceId}` · ámbito `sources:write`

Elimina la fuente y lo que el agente aprendió de ella, al momento, como al eliminarla en la pestaña **Knowledge**. No hay que volver a entrenar nada más.

```json
{ "id": "SOURCE_ID", "deleted": true, "training": { "total": 17, "trained": 17, "pending": 0, "processing": 0, "failed": 0, "done": true } }
```

## Suscribir un webhook

`POST /api/v1/agents/{agentId}/webhooks` · ámbito `webhooks:write`

Suscribe un webhook a los eventos del agente: un REST hook, como los que usan los disparadores instantáneos de Zapier y Make. Después, intoCHAT envía cada evento a la dirección como se explica en [Webhooks](/es/docs/webhooks#que-se-envia), firmado y con reintentos, igual que un webhook añadido en la pestaña **Leads**.

| Campo | Contenido |
| --- | --- |
| `url` | Obligatorio. La dirección a la que se envían los eventos. Como en la pestaña **Leads**, tiene que empezar por `https://` y apuntar a un servidor público; las direcciones de redes privadas e internas se rechazan. |
| `events` | Obligatorio. Uno o varios de `lead.created`, `handoff.requested`, `form.submitted`, `booking.created`, `live_chat.requested` y `return.requested`. Consulta [Eventos](/es/docs/webhooks#eventos). |
| `source` | Opcional. `zapier`, `make` o `api` (por defecto): lo que muestra la pestaña **Leads** junto al webhook, por ejemplo **via Zapier**. |

```bash
curl https://www.intochat.ai/api/v1/agents/AGENT_ID/webhooks \
  -H "Authorization: Bearer ic_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/intochat", "events": ["lead.created"], "source": "api" }'
```

```json
{
  "id": "WEBHOOK_ID",
  "agentId": "AGENT_ID",
  "url": "https://hooks.example.com/intochat",
  "events": ["lead.created"],
  "source": "api",
  "secret": "whsec_…",
  "createdAt": "2026-10-08T09:00:00.000Z"
}
```

La respuesta tiene el estado `201`. Guarda `id` para cancelar la suscripción más adelante. `secret` es el secreto de firma del webhook, y solo se devuelve aquí; úsalo para [comprobar la firma](/es/docs/webhooks#comprobar-la-firma) si tu receptor puede hacerlo. Las herramientas que no pueden comprobar firmas, como Zapier y Make, pueden ignorarlo.

Un webhook suscrito aparece en la pestaña **Leads** del agente con una etiqueta **via Zapier**, **via Make** o **via API**, donde el propietario puede pausarlo o eliminarlo. Recuerda la clave que lo suscribió: [revocar esa clave](#revocar-una-clave) lo elimina. Un agente puede tener hasta 25 webhooks suscritos, aparte de los 5 añadidos en la pestaña **Leads**; por encima de eso, la petición recibe un error `400`.

## Cancelar la suscripción de un webhook

`DELETE /api/v1/webhooks/{webhookId}` · ámbito `webhooks:write`

Elimina un webhook que se suscribió a través de la API, con su historial de envíos. Puede hacerlo cualquier clave de la misma cuenta. Un webhook añadido en la pestaña **Leads**, un webhook de otra cuenta o uno ya eliminado recibe `404`.

```json
{ "id": "WEBHOOK_ID", "deleted": true }
```

## Listar eventos

`GET /api/v1/agents/{agentId}/events/{event}` · ámbito `agents:read`, más `leads:read` o `conversations:read`

Los últimos eventos de un tipo del agente, del más reciente al más antiguo, cada uno con exactamente la misma forma que el cuerpo de un [envío de webhook](/es/docs/webhooks#que-se-envia). Las herramientas de automatización lo consultan periódicamente en lugar de esperar a un webhook, por ejemplo para mostrar datos de ejemplo reales. Admite `limit`, de 1 a 100 (por defecto, 20).

| `event` | Necesita también | Se construye a partir de |
| --- | --- | --- |
| `lead.created` | `leads:read` | Los leads, primero los guardados más recientemente |
| `form.submitted` | `leads:read` | Los formularios enviados |
| `booking.created` | `leads:read` | Las reservas |
| `return.requested` | `leads:read` | Las peticiones de devolución |
| `handoff.requested` | `conversations:read` | Las conversaciones traspasadas por correo |
| `live_chat.requested` | `conversations:read` | Las conversaciones en las que el agente pidió a tu equipo que se uniera |
| `conversation.created` | `conversations:read` | Las conversaciones nuevas. Este evento no tiene webhook; solo se puede listar. |

```bash
curl "https://www.intochat.ai/api/v1/agents/AGENT_ID/events/lead.created?limit=5" \
  -H "Authorization: Bearer ic_live_YOUR_KEY"
```

```json
{
  "data": [
    {
      "id": "LEAD_ID",
      "event": "lead.created",
      "created_at": "2026-10-07T09:30:00.000Z",
      "agent": { "id": "AGENT_ID", "name": "Acme Assistant" },
      "data": {
        "leadId": "LEAD_ID",
        "agentId": "AGENT_ID",
        "agentName": "Acme Assistant",
        "email": "jane@example.com",
        "phone": null,
        "name": "Jane",
        "customFields": [],
        "source": "form",
        "conversationId": "CONVERSATION_ID",
        "collectedAt": "2026-10-07T09:30:00.000Z"
      }
    }
  ]
}
```

Las diferencias con un envío:

- `id` es el ID del registro, no un ID de envío, así que el mismo evento siempre tiene el mismo `id`: el ID del lead, del envío del formulario, de la reserva, de la petición de devolución o de la conversación. En los traspasos y las peticiones de chat en vivo es el ID de la conversación, dos puntos y la hora en milisegundos, porque una conversación puede volver a pedirlo más tarde.
- `created_at` es cuándo se guardó el registro.
- Los datos se reconstruyen a partir de lo que guarda intoCHAT. El `summary` de un traspaso no se guarda, así que es `null`, y `test` es `false`. Un traspaso que abrió un [ticket de helpdesk](/es/docs/helpdesk-tickets) lleva su `ticket`, igual que el webhook. El `providerStatus` de una reserva es `pending` mientras espera tu confirmación y, si no, `accepted`. Una petición de devolución tiene su `status` actual, `requested` o `handled`.
- Los datos de `conversation.created` tienen `conversationId`, `channel` (`widget` o `api`), `page`, `country` y `createdAt`.

## Obtener eventos de ejemplo

`GET /api/v1/agents/{agentId}/events/{event}/samples` · ámbito `agents:read`

Hasta 3 de los últimos eventos del agente, como en [Listar eventos](#listar-eventos), para que una herramienta siempre tenga datos a partir de los que asignar campos. Cuando el agente todavía no tiene ninguno, o la clave no tiene el ámbito que necesita ese evento (`leads:read` o `conversations:read`), devuelve en su lugar un ejemplo documentado con los mismos campos, y `sample` es `true`.

```json
{
  "data": [
    {
      "id": "sample_booking.created",
      "event": "booking.created",
      "created_at": "2026-10-07T09:30:00.000Z",
      "agent": { "id": "AGENT_ID", "name": "Acme Assistant" },
      "data": { "bookingId": "sample_booking", "eventTitle": "30 min intro call", "attendeeEmail": "jane@example.com" }
    }
  ],
  "sample": true
}
```

Los `data` del ejemplo tienen todos los campos del evento; arriba se omiten algunos.

## Límites

- La API está incluida a partir de Starter. En Free no se pueden crear claves y las existentes reciben `plan_required`.
- 60 peticiones por minuto por clave, y como mucho 20 claves activas por cuenta.
- Cada respuesta del chat cuenta como un mensaje de tu plan. Un agente responde como mucho 1.000 mensajes por hora, entre el widget y la API.
- Una respuesta del chat tiene que terminar en 55 segundos.
- Fuentes: solo textos y pares de pregunta y respuesta. Las páginas web, los archivos y las páginas de Notion se añaden en el panel.
- Hasta 25 webhooks suscritos por agente, aparte de los 5 añadidos en la pestaña **Leads**.
- Los contactos se crean y actualizan de uno en uno. Para añadir muchos a la vez, usa [Importar un archivo CSV](/es/docs/contacts#importar-un-archivo-csv) en el panel.
- Todavía no hay endpoints para crear o cambiar agentes, enviar formularios, reservar citas ni tomar el control de chats en vivo.

## Siguientes pasos

- Recibe en tu servidor los leads y los traspasos en cuanto ocurren: [Webhooks](/es/docs/webhooks).
- Usa intoCHAT en Zapier o Make: [Zapier y Make](/es/docs/zapier-and-make).
- Deja que tu agente llame a tu propia API mientras responde: [Acciones API](/es/docs/api-actions).
- Escribe buenos textos y pares de pregunta y respuesta: [Texto y preguntas](/es/docs/text-and-qa).
- Comprueba qué incluye tu plan: [Planes y límites](/es/docs/plans-and-limits).
