
# API de campañas

Una campaña agrupa todo lo que el bot de IA necesita para hablar con tus contactos: sus instrucciones, los canales en los que se ejecuta, sus horas de actividad y su comportamiento de seguimiento. La API de campañas te permite listar, crear, actualizar, duplicar, habilitar, archivar y ajustar campañas desde tu propio código en lugar de hacerlo desde el panel de control.

Todos los endpoints a continuación son relativos a la URL base `https://api.youraiconnector.com/v1`. Cada solicitud debe estar autenticada; consulta [Acceso a la API](../integrations/api-access.md) y [Autenticación](authentication.md) para saber cómo obtener y enviar tu clave de API. El acceso a la API es una función de pago; sin ella, las solicitudes serán rechazadas con un `403`.

> **Atención:** Algunos ejemplos muestran la forma de consulta simple `?apiKey=YOUR_API_KEY`, otros utilizan el encabezado `X-API-Key`. Ambos funcionan en todas partes; utiliza el que mejor se adapte a tu configuración.

---

## Tipos de campaña

Al crear una campaña, debe elegir uno de estos tipos:

| Tipo | Para qué sirve |
|---|---|
| `Incoming from Unknown Contacts` | El bot responde a las personas que le escriben por primera vez. |
| `Outgoing` | El bot inicia conversaciones con los contactos que añada a la campaña. |
| `Keywords` | **Inerte: no utilizar.** Una campaña `Keywords` es inerte: se sigue aceptando por compatibilidad con versiones anteriores, pero es invisible para el enrutamiento entrante en todos los canales y nada lee sus palabras clave de activación. Utilice un punto de entrada de tipo **Palabra clave** en un agente de IA. |
| `Combined` | Una combinación de comportamiento entrante y saliente. |

**Las mayúsculas y minúsculas no importan.** `type`, `status`, `booking_provider`, `first_response_mode`, `bot.anthropic_model` y `bot.ai_speed` aceptan cualquier combinación de mayúsculas y minúsculas — `"live"`, `"Live"` y `"LIVE"` son lo mismo — y el valor se almacena en su forma canónica, que es lo que se devuelve al leer la campaña. La única excepción es el par de pausa: `"Paused"` y `"paused"` son dos estados genuinamente diferentes, por lo que una grafía ambigua como `"PAUSED"` se rechaza con un `400` que le indica que elija una.

### Los dos estados de pausa

| Estado | Quién lo escribe | Qué significa |
|---|---|---|
| `Paused` | Las comprobaciones de seguridad propias de la plataforma (baja interacción, errores de envío repetidos, límite alcanzado) y las nuevas interfaces de Agentes y Difusiones | La campaña está retenida. Un barrido programado puede levantar una pausa de seguridad automáticamente una vez que se soluciona el motivo. |
| `paused` | El botón de Pausa del panel de control, junto con `resumed` en Reanudar | Una persona lo pausó manualmente. Los envíos programados se eliminan y se reconstruyen al reanudar. |

Ambos detienen la campaña: el enrutamiento entrante solo funciona mientras el estado sea exactamente `Live`. **Desde la API, usa `Paused` para pausar y `Live` para reanudar** — el par en minúsculas existe para el botón del panel de control y se mantiene funcional para este.

Ninguno de estos es lo que sucede cuando la IA deja de responder dentro de una conversación. Ese es un interruptor por contacto, `is_bot_active` en el contacto — configurado cuando un humano toma el control, cuando el contacto se da de baja o cuando la IA concluye el chat. El estado de la campaña permanece intacto y todas las demás conversaciones en ella siguen funcionando. Consulta [pausar o reanudar la IA para un contacto](messages.md#pause-or-resume-the-ai-for-one-contact).

> **Crear una campaña no decide quién responde a un canal.** El enrutamiento se gestiona mediante **Puntos de entrada** en un agente de IA, no mediante campañas. Cada canal tiene un punto de entrada predeterminado que designa al agente que responde a los contactos nuevos y desconocidos: configúrelo con `PUT /entry-points/channel-defaults`, compruebe si la escalera está activa para la cuenta con `GET /entry-points/routing-status`, límpielo con `DELETE /entry-points/channel-defaults`. `POST /channels/campaign` sigue escribiendo el mapa de enrutamiento de campañas heredado por canal, pero ese mapa ya no se consulta para el enrutamiento entrante en ninguna cuenta; se conserva solo para reversiones. No desarrolle basándose en él. Consulte [Enrutar un canal a una campaña](channels.md#route-a-channel-to-a-campaign) para ver ambas superficies lado a lado.

---

## Listar campañas

`GET /campaigns`

Devuelve tus campañas, de la más reciente a la más antigua. Las campañas archivadas se excluyen a menos que pases `archived=true`.

**Parámetros de consulta**

| Parámetro | Requerido | Descripción |
|---|---|---|
| `limit` | No | Número máximo de campañas a devolver. Por defecto `50`, máximo `100`. |
| `cursor` | No | Cursor de paginación. Pasa el valor `next_cursor` de la respuesta anterior para obtener la página siguiente. |
| `archived` | No | Establécelo en `true` para incluir campañas archivadas. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/campaigns?limit=20&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/campaigns?limit=20", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.campaigns, data.next_cursor);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/campaigns",
    params={"limit": 20},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
print(data["campaigns"], data["next_cursor"])
```

**Respuesta**

```json
{
  "success": true,
  "campaigns": [
    {
      "id": "NBCXrhqGPSFsd6MV7pRo",
      "name": "Inbound WhatsApp Leads",
      "type": "Incoming from Unknown Contacts",
      "status": "Live",
      "enabled": true,
      "archived": false,
      "created_at": 1700000000000,
      "ai_mode": true,
      "language": "en",
      "enabled_channels": ["whatsapp", "instagram"]
    }
  ],
  "next_cursor": "NBCXrhqGPSFsd6MV7pRo"
}
```

Cuando `next_cursor` es `null`, has llegado a la última página.

---

## Obtener una campaña

`GET /campaigns/{campaignId}`

Devuelve el documento completo de la campaña, incluyendo la configuración del bot en vivo (`bot`), los ajustes de seguimiento, los canales habilitados y cualquier palabra clave. Las marcas de tiempo se devuelven en milisegundos de época.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const { campaign } = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
campaign = res.json()["campaign"]
```

**Respuesta**

```json
{
  "success": true,
  "campaign": {
    "id": "NBCXrhqGPSFsd6MV7pRo",
    "name": "Inbound WhatsApp Leads",
    "type": "Incoming from Unknown Contacts",
    "status": "Live",
    "language": "en",
    "ai_mode": true,
    "enabled": true,
    "archived": false,
    "created_at": 1700000000000,
    "enabled_channels": ["whatsapp", "instagram"],
    "bot": {
      "instructions": "Greet warmly and ask about their goals.",
      "goal": "Book a discovery call.",
      "ai_speed": "balanced",
      "anthropic_model": "standard",
      "max_messages": 20
    }
  }
}
```

::: note
**Nota:** Una campaña propiedad de una cuenta diferente devuelve `404 Campaign not found` (no `403`), por lo que no puede saber si un ID existe en otra cuenta.
:::


---

## Crear una campaña

`POST /campaigns`

Crea una nueva campaña. `name` y `type` son obligatorios; todo lo demás es opcional. Puedes incluir cualquier otro campo de campaña en la misma solicitud — por ejemplo `language`, `ai_mode`, o un objeto de configuración completo `bot` — y se almacenará con la nueva campaña. El propietario y la hora de creación se establecen automáticamente.

**Campos de la solicitud**

| Campo | Obligatorio | Descripción |
|---|---|---|
| `name` | Sí | El nombre de la campaña. |
| `type` | Sí | Uno de los cuatro tipos de campaña anteriores. |
| `language` | No | Idioma en el que responde el bot (p. ej., `"en"`). |
| `ai_mode` | No | Si el modo IA está activado (`true`/`false`). En una campaña respondida por un agente de IA, las lecturas devuelven el interruptor **Activo** del agente en lugar de un valor almacenado; consulte la nota sobre la actualización a continuación. |
| `bot` | No | El objeto de configuración del bot (consulte [Campos de configuración del bot](#bot-configuration-fields)). |
| `list_id` | No | ID de la lista de contactos que se va a adjuntar. |
| `event_id` | No | ID del tipo de evento que la IA puede reservar. |
| `event_ids` | No | Varios tipos de evento a la vez, como una matriz de ID de tipo de evento; el primero es el predeterminado. Envíe `event_id` o `event_ids`, no ambos. |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring Promo",
    "type": "Outgoing",
    "language": "en",
    "ai_mode": true,
    "bot": {
      "instructions": "Greet warmly and ask about their goals.",
      "goal": "Book a discovery call."
    }
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/campaigns", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Spring Promo",
    type: "Outgoing",
    language: "en",
    ai_mode: true,
    bot: {
      instructions: "Greet warmly and ask about their goals.",
      goal: "Book a discovery call.",
    },
  }),
});
const { campaign_id } = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/campaigns",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "name": "Spring Promo",
        "type": "Outgoing",
        "language": "en",
        "ai_mode": True,
        "bot": {
            "instructions": "Greet warmly and ask about their goals.",
            "goal": "Book a discovery call.",
        },
    },
)
campaign_id = res.json()["campaign_id"]
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

---

## Actualizar una campaña

`PUT /campaigns/{campaignId}`

Actualiza parcialmente una campaña: envía solo los campos que deseas cambiar. Este es el único verbo de actualización general; no existe un `PATCH /campaigns/{campaignId}` (las dos rutas `PATCH` son los conmutadores específicos de [activar](#enable-or-disable-a-campaign) y [archivar](#archive-or-restore-a-campaign)).

**Qué campos puedes cambiar.** Todo lo que escribe el editor de campañas, incluyendo `name`, `status`, `type`, `language`, `ai_mode`, `enabled_channels`, la configuración de disparadores y goteo, las banderas de reserva y seguimiento, los campos de monitoreo de Instagram/Facebook y toda la configuración de `bot`. La identidad y la propiedad están bloqueadas durante la vida de la campaña: `user`, `id` y `created_at` son rechazados, al igual que cualquier nombre de campo que el endpoint no reconozca. El rechazo es por solicitud, no por campo: una clave desconocida devuelve un `400` y **nada** en esa solicitud se escribe.

**`ai_mode` en una campaña respaldada por un Agente refleja al Agente.** Cuando una campaña es respondida por un Agente de IA, la lectura de la campaña devuelve `ai_mode` derivado del interruptor **Activo** de ese Agente: el único interruptor que realmente decide si la IA responde. Escribir `ai_mode` en dicha campaña se acepta, pero no cambiará lo que se lee posteriormente; en su lugar, active o desactive el interruptor Activo del Agente (en el panel de control o a través de la API de Agentes). En las campañas clásicas sin Agente, `ai_mode` lee y escribe el valor almacenado como antes.

**Los campos del bot se fusionan, no se sobrescriben.** Envía la configuración del bot ya sea como claves con puntos (`"bot.instructions": "..."`) o como un objeto anidado (`"bot": { "instructions": "..." }`) — ambos escriben hoja por hoja, por lo que los campos que omitas mantienen sus valores actuales. `bot.instructions`, `bot.goal`, `bot.rules` y `bot.personality` son todos editables de esta manera, al igual que cualquier otra configuración de bot listada en [Campos de configuración del bot](#bot-configuration-fields). Lo mismo aplica para `test_bot`, `frequency` y `follow_up_config`.

Para reemplazar una configuración de bot por completo — eliminando cualquier campo que no envíes — usa `bot_replace` (o `test_bot_replace`) con el objeto completo. No puedes combinar un reemplazo y una fusión para el mismo objeto en una sola solicitud; eso devuelve un `400`.

::: note
**Nota:** Escribir `bot.*` a través de la API tiene efecto **inmediatamente** en la campaña activa. El editor del panel de control funciona de manera diferente: las ediciones allí se guardan como borrador y solo se publican cuando el cliente hace clic en Publicar. Por lo tanto, si un cliente tiene cambios no publicados en el panel de control, estos permanecen en `test_bot` y una lectura de API de `bot` muestra correctamente lo que la IA está usando en este momento.
:::


Algunos campos se establecen a través de una clave dedicada en lugar de escribirse directamente: utilice `list_id` para la lista de contactos, `event_id` para el tipo de evento (o `event_ids`, una matriz ordenada de ID de tipo de evento, para permitir que la IA reserve varios; el primero es el predeterminado; una matriz vacía los desvincula todos) y `contact_ids` (una matriz de ID de contacto) para los contactos de la campaña. Las entradas de la base de conocimientos se gestionan a través de la [API de preguntas frecuentes](faqs.md), no de este endpoint.

**Las etiquetas reemplazan, no se combinan.** Envía `tags` como el array completo y se convertirá en el conjunto de etiquetas de la campaña; consulta [Etiquetas de campaña](#campaign-tags) para ver los campos y los endpoints que añaden o editan una sola etiqueta.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Spring Promo v2", "enabled_channels": ["whatsapp"] }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Spring Promo v2",
      enabled_channels: ["whatsapp"],
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"name": "Spring Promo v2", "enabled_channels": ["whatsapp"]},
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

---

## Eliminar una campaña

`DELETE /campaigns/{campaignId}`

Elimina permanentemente una campaña. Esto no se puede deshacer; si es posible que necesite la campaña de nuevo, [archívela](#archive-or-restore-a-campaign) en su lugar.

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true
}
```

---

## Duplicar una campaña

`POST /campaigns/{campaignId}/duplicate`

Crea una copia de la campaña conservando toda su configuración. La copia comienza **deshabilitada** y su nombre recibe un sufijo `(copy)`, por lo que nunca envía mensajes hasta que usted la habilite explícitamente.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/duplicate?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/duplicate",
  { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const { campaign_id } = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/duplicate",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
new_campaign_id = res.json()["campaign_id"]
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "aZ9plnewCopyId01234"
}
```

> Copias duplicadas **dentro de una misma cuenta**.

---


## Habilitar o deshabilitar una campaña

`PATCH /campaigns/{campaignId}/enabled`

Activa o desactiva una campaña. Una campaña deshabilitada deja de interactuar con los contactos, pero mantiene toda su configuración.

**Campos de la solicitud**

| Campo | Obligatorio | Descripción |
|---|---|---|
| `enabled` | Sí | `true` para habilitar, `false` para deshabilitar. Debe ser un valor booleano. |

**cURL**

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/enabled?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/enabled",
  {
    method: "PATCH",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ enabled: true }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.patch(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/enabled",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"enabled": True},
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "enabled": true
}
```

---

## Archivar o restaurar una campaña

`PATCH /campaigns/{campaignId}/archived`

Archiva o restaura una campaña. Las campañas archivadas se ocultan de la lista de campañas predeterminada, pero conservan todos sus datos y pueden restaurarse en cualquier momento.

**Campos de la solicitud**

| Campo | Obligatorio | Descripción |
|---|---|---|
| `archived` | Sí | `true` para archivar, `false` para restaurar. Debe ser un valor booleano. |

**cURL**

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/archived?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "archived": true }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/archived",
  {
    method: "PATCH",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ archived: true }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.patch(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/archived",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"archived": True},
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "archived": true
}
```

---

## Actualizar la configuración del bot

`PUT /campaigns/{campaignId}/bot-config`

Esta es la forma segura de cambiar la configuración individual del bot. Cada campo que envíes se **fusiona** con la configuración existente del bot, por lo que cualquier campo que omitas se conservará. Utiliza esto en lugar del endpoint de actualización de campañas siempre que solo desees ajustar una parte del bot.

Las claves de los campos deben utilizar únicamente letras, números, guiones bajos y guiones.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/bot-config?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instructions": "Always answer in a friendly, concise tone.",
    "ai_speed": "balanced"
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/bot-config",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      instructions: "Always answer in a friendly, concise tone.",
      ai_speed: "balanced",
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/bot-config",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "instructions": "Always answer in a friendly, concise tone.",
        "ai_speed": "balanced",
    },
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

### Campos de configuración del bot

Todos los campos del bot son opcionales. Envía solo los que desees establecer. Cualquier campo adicional del bot más allá de los enumerados aquí se aceptará y almacenará tal cual.

| Campo | Tipo | Descripción |
|---|---|---|
| `instructions` | string | Las instrucciones principales que guían cómo habla el bot con los contactos. |
| `rules` | string | Reglas estrictas que el bot debe seguir siempre. |
| `goal` | string | El resultado que el bot debe intentar conseguir en cada conversación. |
| `personality` | string | Descripción del tono de voz y la personalidad del bot. |
| `ai_speed` | string | Cuánto razonamiento aplica la IA antes de responder. Uno de `fast`, `fast_thinker`, `balanced`, `thorough`. |
| `anthropic_model` | string | El nivel de calidad de IA utilizado para las respuestas de esta campaña. Uno de `standard`, `economy` (obsoleto), `max`, `mini`. `max` y `mini` solo surten efecto en cuentas elegibles para esos niveles. |
| `max_messages` | integer | Número máximo de mensajes del bot por conversación. |
| `alert_human_when` | string | Condiciones bajo las cuales el bot debe alertar a un compañero humano. |
| `availability` | object | El horario de horas activas del bot. Puede configurarlo aquí o utilizar el [endpoint de horas activas](#set-the-bot-active-hours) dedicado. |
| `follow_up_config` | object | Configuración del comportamiento de seguimiento, almacenada tal cual se proporciona. |

---

## Establecer las horas activas del bot

`PUT /campaigns/{campaignId}/active-hours`

Establece el horario de disponibilidad del bot. Fuera de las ventanas configuradas, el bot no responde automáticamente. Esto escribe el campo `availability` de la configuración del bot.

**Campos de la solicitud**

| Campo | Obligatorio | Descripción |
|---|---|---|
| `availability` | Sí | Un objeto indexado por día de la semana. Las claves permitidas son `monday` hasta `sunday`; cualquier otra clave devuelve un `400`. Los días que omita no se modificarán. |

Cada día de la semana contiene una única ventana de tiempo o una matriz de ventanas. Una ventana tiene un `start_time` y un `end_time` en formato de `HH:MM` de 24 horas.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/active-hours?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "availability": {
      "monday": { "start_time": "09:00", "end_time": "17:00" },
      "tuesday": [
        { "start_time": "09:00", "end_time": "12:00" },
        { "start_time": "13:00", "end_time": "17:00" }
      ]
    }
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/active-hours",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      availability: {
        monday: { start_time: "09:00", end_time: "17:00" },
        tuesday: [
          { start_time: "09:00", end_time: "12:00" },
          { start_time: "13:00", end_time: "17:00" },
        ],
      },
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/active-hours",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "availability": {
            "monday": {"start_time": "09:00", "end_time": "17:00"},
            "tuesday": [
                {"start_time": "09:00", "end_time": "12:00"},
                {"start_time": "13:00", "end_time": "17:00"},
            ],
        }
    },
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

---

## Listar las funciones personalizadas de una campaña

`GET /campaigns/{campaignId}/custom-functions`

Devuelve las funciones personalizadas vinculadas a esta campaña, resueltas en definiciones completas. Las funciones personalizadas son acciones HTTP externas que el bot puede llamar durante una conversación; por ejemplo, consultar el inventario en su tienda o crear un registro en su CRM.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const { custom_functions } = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
custom_functions = res.json()["custom_functions"]
```

**Respuesta**

```json
{
  "success": true,
  "custom_functions": [
    {
      "id": "fn_abc123",
      "name": "check_stock",
      "description": "Looks up whether a product is in stock.",
      "url": "https://example.com/api/stock",
      "method": "POST",
      "input": [
        { "name": "sku", "type": "string" }
      ],
      "ai_action": "Tell the customer whether the item is available.",
      "created_at": 1700000000000,
      "updated_at": 1700000500000
    }
  ]
}
```

---

## Vincular una función personalizada a una campaña

`POST /campaigns/{campaignId}/custom-functions`

Vincula una [función personalizada](../ai-automation/custom-functions.md) existente a esta campaña para que el bot pueda llamarla durante una conversación. Vincular una función que ya está vinculada no tiene ningún efecto.

| Campo | Obligatorio | Descripción |
|---|---|---|
| `custom_function_id` | Sí | ID de la función personalizada a vincular. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "custom_function_id": "fn_abc123" }'
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "custom_function_id": "fn_abc123"
}
```

---

## Desvincular una función personalizada de una campaña

`DELETE /campaigns/{campaignId}/custom-functions/{customFunctionId}`

Desvincular una función que no está vinculada no tiene ningún efecto.

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/custom-functions/fn_abc123?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "custom_function_id": "fn_abc123"
}
```

---

## Vincular una fuente de base de conocimientos a una campaña

`POST /campaigns/{campaignId}/kb-sources`

Vincula una fuente de base de conocimientos (creada a través de la [API de preguntas frecuentes](faqs.md)) a esta campaña para que el bot pueda utilizarla al responder. Vincular una fuente que ya está vinculada no tiene ningún efecto.

| Campo | Obligatorio | Descripción |
|---|---|---|
| `kb_source_id` | Sí | ID de la fuente de base de conocimientos a vincular. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/kb-sources?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kb_source_id": "kb_abc123" }'
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "kb_source_id": "kb_abc123"
}
```

---

## Desvincular una fuente de base de conocimientos de una campaña

`DELETE /campaigns/{campaignId}/kb-sources/{kbSourceId}`

Desvincular una fuente que no está vinculada no tiene ningún efecto.

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/kb-sources/kb_abc123?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "kb_source_id": "kb_abc123"
}
```

---

## Vincular un servidor MCP a una campaña

`POST /campaigns/{campaignId}/mcp-servers`

Vincula un servidor MCP a esta campaña, lo que permite al bot acceder a las herramientas de dicho servidor durante una conversación. Vincular un servidor que ya está vinculado no realiza ninguna acción.

| Campo | Requerido | Descripción |
|---|---|---|
| `mcp_server_id` | Sí | ID del servidor MCP a vincular. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/mcp-servers?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "mcp_server_id": "mcp_abc123" }'
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "mcp_server_id": "mcp_abc123"
}
```

---

## Desvincular un servidor MCP de una campaña

`DELETE /campaigns/{campaignId}/mcp-servers/{mcpServerId}`

Desvincular un servidor que no está vinculado no realiza ninguna acción.

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/mcp-servers/mcp_abc123?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "mcp_server_id": "mcp_abc123"
}
```

---

## Biblioteca de medios de la campaña

La biblioteca de medios contiene imágenes, vídeos, documentos y notas de voz que el bot puede enviar durante una conversación.

### Listar la biblioteca de medios de una campaña

`GET /campaigns/{campaignId}/media-library`

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "media_items": [
    {
      "id": "media_abc123",
      "item_id": "media_abc123",
      "title": "Pricing sheet",
      "description": "Send when the contact asks about pricing.",
      "media_url": "https://example.com/pricing.pdf",
      "media_content_type": "application/pdf",
      "type": "document",
      "agent_id": "",
      "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
      "media_home": "campaign"
    }
  ]
}
```

`media_url` es una URL firmada capturada en el momento de la carga; es posible que ya haya caducado cuando la lea; el panel de control la vuelve a firmar bajo demanda.

### Cargar un elemento multimedia

`POST /campaigns/{campaignId}/media-library`

| Campo | Requerido | Descripción |
|---|---|---|
| `base64Data` | Sí | El archivo, codificado en base64 (sin prefijo data-URL). |
| `mimeType` | Sí | Tipo MIME del archivo (p. ej., `image/png`). |
| `title` | Sí | Etiqueta corta que se muestra en la biblioteca y en el prompt de la IA. |
| `description` | Sí | Instrucción que le indica al bot **cuándo** enviar este elemento. |
| `fileName` | No | Nombre de archivo original, utilizado para crear el nombre del objeto de almacenamiento. |
| `sendMessage` | No | Redacción preferida que el bot debe usar al enviar este elemento. |
| `maxSendsPerConversation` | No | Número máximo de veces que el bot puede enviar este elemento a un contacto en una conversación. El valor predeterminado es `1`. |
| `sendAsVoiceNote` | No | Para una carga de audio, transcodifíquelo en una nota de voz de WhatsApp. El valor predeterminado es `false` (almacenado como un archivo de audio simple). |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "base64Data": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mimeType": "image/png",
    "title": "Product photo",
    "description": "Send when the contact asks what the product looks like."
  }'
```

**Respuesta**

```json
{
  "success": true,
  "itemId": "media_abc123",
  "mediaUrl": "https://example.com/product.png",
  "storagePath": "ai_media/campaigns/NBCXrhqGPSFsd6MV7pRo/media_abc123.png",
  "mediaContentType": "image/png",
  "type": "image",
  "isVoiceNote": false
}
```

### Actualizar un elemento multimedia

`PATCH /campaigns/{campaignId}/media-library/{itemId}`

Edita solo los metadatos del elemento; para reemplazar el archivo en sí, elimine el elemento y cargue uno nuevo.

| Campo | Descripción |
|---|---|
| `title` | Etiqueta corta. |
| `description` | Instrucción de cuándo enviar. |
| `send_message` | Redacción preferida para que el bot la utilice. |
| `max_sends_per_conversation` | Entero no negativo, o `null` para borrar el límite. |

```bash
curl -X PATCH "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library/media_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Updated pricing sheet" }'
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "item_id": "media_abc123"
}
```

### Eliminar un elemento multimedia

`DELETE /campaigns/{campaignId}/media-library/{itemId}`

Eliminar un elemento que ya no existe es una operación nula (no-op).

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/media-library/media_abc123?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{ "success": true, "deleted": true }
```

---

## Etiquetas de campaña

Una etiqueta de campaña es una etiqueta que le enseñas al bot a aplicar a un contacto durante una conversación: `hot-lead`, `not-interested`, `booked-a-call`. Cada etiqueta tiene tres partes:

| Campo | Tipo | Descripción |
|---|---|---|
| `name` | string, obligatorio | La etiqueta en sí. Es lo que el bot aplica al contacto y lo que usarás para hacer coincidencias más adelante, así que mantenla corta y estable. |
| `description` | string | La instrucción que le indica al bot **cuándo** aplicar esta etiqueta. Esta es la parte que realiza el trabajo: "la persona confirma que se unió a la comunidad" se utiliza, "cliente potencial" no. |
| `webhook` | string | Una URL que recibe un `POST` en el momento en que la etiqueta se asigna a un contacto. Déjala vacía si no necesitas una. |
| `tag_id` | string | Opcional. Vincula esta entrada a una etiqueta existente en tu cuenta en lugar de una nueva. Proporciónala si deseas gestionar esta etiqueta específica más adelante con los endpoints de etiqueta única que aparecen a continuación. |

Los nombres de las etiquetas deben ser únicos dentro de una campaña. El bot aplica las etiquetas **por nombre**, por lo que dos entradas que comparten el mismo nombre no tienen un ganador definido.

### Establecer todas las etiquetas de una campaña

`PUT /campaigns/{campaignId}` con un array `tags`.

Esto reemplaza las etiquetas de la campaña exactamente por lo que envíes, que es lo mismo que hace la pestaña Etiquetas del panel de control al guardar. **Envía el array completo cada vez**: una etiqueta que omitas es una etiqueta que has eliminado. Enviar `[]` las borra todas.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": [
      {
        "name": "hot-lead",
        "description": "The person confirms they want to buy, or asks how to get started right away.",
        "webhook": "https://example.com/hooks/campaign-events"
      },
      {
        "name": "not-interested",
        "description": "The person declines the offer or says they are not a fit."
      }
    ]
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      tags: [
        {
          name: "hot-lead",
          description:
            "The person confirms they want to buy, or asks how to get started right away.",
          webhook: "https://example.com/hooks/campaign-events",
        },
        {
          name: "not-interested",
          description: "The person declines the offer or says they are not a fit.",
        },
      ],
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "tags": [
            {
                "name": "hot-lead",
                "description": "The person confirms they want to buy, or asks how to get started right away.",
                "webhook": "https://example.com/hooks/campaign-events",
            },
            {
                "name": "not-interested",
                "description": "The person declines the offer or says they are not a fit.",
            },
        ]
    },
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

Lee las etiquetas de vuelta con [`GET /campaigns/{campaignId}`](#get-a-campaign).

### Añadir una etiqueta

`POST /campaigns/{campaignId}/tags`

Añade una sola etiqueta sin tener que reenviar el resto. Úsalo cuando estés añadiendo a un conjunto que no creaste en esta solicitud.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/tags?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tag": { "name": "booked-a-call", "description": "The person confirms a booked time." } }'
```

Publicar exactamente la misma etiqueta dos veces no hace nada la segunda vez. Publicar el mismo `tag_id` con un nombre o descripción diferente añade una **segunda** entrada en lugar de editar la primera; utiliza el endpoint a continuación para editarla directamente.

### Actualizar o eliminar una etiqueta

`PUT /campaigns/{campaignId}/tags/{tagId}`
`DELETE /campaigns/{campaignId}/tags/{tagId}`

Estas abordan una entrada por su `tag_id`, por lo que solo funcionan en etiquetas que se crearon con una. Si una etiqueta no tiene `tag_id`, cámbiela con la matriz completa `PUT /campaigns/{campaignId}` anterior.

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/tags/tag_abc123?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tag": { "name": "hot-lead", "description": "Updated instruction." } }'
```

Una `tagId` que no está en la campaña devuelve `404` con `"Tag not found in campaign tags"`.

---

## Alternar los canales de una campaña

`POST /campaigns/{campaignId}/channels`

Añade o elimina canales de la matriz `enabled_channels` de la campaña sin tener que reenviar toda la matriz; es más seguro que [`PUT /campaigns/{campaignId}`](#update-a-campaign) cuando otra entidad podría estar editando la campaña al mismo tiempo.

Envíe una sola alternancia o un lote, pero no ambos en la misma solicitud:

```json
{ "channel": "whatsapp", "action": "add" }
```

```json
{ "add": ["whatsapp", "instagram"], "remove": ["sms"] }
```

| Campo | Descripción |
|---|---|
| `channel` | Un canal para alternar. Emparejar con `action`. |
| `action` | `"add"` o `"remove"`. Emparejar con `channel`. |
| `add` | Matriz de canales a añadir. Formato de lote; usar en lugar de `channel`/`action`. |
| `remove` | Matriz de canales a eliminar. Formato de lote. |

Canales válidos: `whatsapp`, `whatsapp_web`, `sms`, `instagram`, `messenger`, `facebook`, `chat_widget`, `custom_channel`, `imessage`, `telegram`, `instagram_private`, `line`, `viber`, `tiktok`, `email`, `linkedin`, `skool`.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/channels?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "whatsapp", "action": "add" }'
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "added": ["whatsapp"],
  "removed": []
}
```

> Esto solo cambia los canales en los que se anuncia la campaña; no decide quién responde a un canal. Consulte [Tipos de campaña](#campaign-types) arriba y [Dirigir una campaña a canales entrantes](#route-a-campaign-to-incoming-channels) abajo para eso.

---

## Comentario a mensaje directo (Instagram y Facebook)

La función Comentario a mensaje directo convierte un comentario en una de tus publicaciones en una conversación privada: alguien comenta, el bot le envía un mensaje directo (DM) y la campaña continúa la conversación a partir de ahí. Se configura completamente a través del objeto de campaña, por lo que no depende exclusivamente de la interfaz de usuario.

Conecta primero la página de Facebook; consulta [Conexión de canal](channels.md#instagram--messenger-meta). Luego, configura los campos a continuación con [`PUT /campaigns/{campaignId}`](#update-a-campaign).

> **La campaña debe estar `Live`.** El monitoreo de comentarios solo recoge campañas cuyo `status` sea `Live` (cualquier combinación de mayúsculas y minúsculas; consulte [Tipos de campaña](#campaign-types)). Cualquier otro estado la deshabilita silenciosamente, y uno inventado como `"Active"` ahora se rechaza con un `400` en lugar de almacenarse. Los estados válidos incluyen `Draft`, `Pending Approval`, `Scheduled`, `Live`, `Paused`, `Completed`, `Sent` y `Failed`.

**Campos**

| Campo | Tipo | Descripción |
|---|---|---|
| `monitor_instagram_posts` | boolean | Supervisar todas las publicaciones de Instagram en la página conectada. |
| `instagram_post_ids` | string[] | Supervisar solo estas publicaciones de Instagram. Dejar sin configurar cuando `monitor_instagram_posts` esté activado. |
| `instagram_comment_delay_minutes` | number | Esperar esta cantidad de minutos después de un comentario antes de enviar el mensaje directo (DM). |
| `monitor_facebook_posts` | boolean | Supervisar todas las publicaciones de Facebook en la página conectada. |
| `facebook_post_ids` | string[] | Supervisar solo estas publicaciones de Facebook. |
| `facebook_comment_delay_minutes` | number | Retraso antes del DM, en minutos. |
| `public_comment_reply_instructions` | string | Guía para la respuesta visible que se deja en el propio comentario. Sustituye la redacción predeterminada "revisa tus mensajes directos". |
| `first_response_mode` | string | `"ai"` (predeterminado) genera el primer DM y la respuesta pública. `"exact_text"` envía tu redacción palabra por palabra, sin generación de IA y sin cargo de crédito. |
| `first_response_exact_text` | string | El primer DM literal, utilizado cuando `first_response_mode` es `"exact_text"`. Requerido para que ese modo surta efecto. |
| `first_response_exact_text_variants` | string[] | Redacciones adicionales para el primer DM. Se elige una al azar por envío, por lo que los DM repetidos no son idénticos byte a byte. |
| `public_comment_reply_exact_text` | string | La respuesta pública literal en modo `"exact_text"`. Déjalo en blanco para omitir la respuesta pública y enviar solo el DM. |
| `public_comment_reply_exact_text_variants` | string[] | Redacciones adicionales para la respuesta pública. |
| `monitor_instagram_followers` | boolean | Tratar a un nuevo seguidor como un activador y enviar un DM de apertura (cuentas personales de Instagram). |
| `follower_outreach_instructions` | string | Guía para ese DM de apertura para nuevos seguidores. |
| `respond_to_instagram_story_replies` | boolean | Si la IA responde a las respuestas de tus Historias de Instagram. Predeterminado `true`. Configura `false` para que las respuestas a las Historias lleguen al chat (con la Historia adjunta) sin una respuesta de la IA. Configuración en vivo: no es parte del borrador, por lo que no necesita publicación. |

**Borrar un campo**

Estos campos se eliminan en lugar de establecerse en `null` cuando envías `null`, por lo que el bot vuelve a sus valores predeterminados: `instagram_post_ids`, `facebook_post_ids`, `instagram_comment_delay_minutes`, `facebook_comment_delay_minutes`, `public_comment_reply_instructions`, `follower_outreach_instructions`, `first_response_exact_text`, `first_response_exact_text_variants`, `public_comment_reply_exact_text`, `public_comment_reply_exact_text_variants`.

> **Una clave desconocida rechaza toda la solicitud.** `PUT /campaigns/{campaignId}` valida todo el cuerpo contra una lista de permitidos. Una clave que no se reconoce devuelve `400` para la solicitud en su conjunto; no se ignora silenciosamente y ninguno de los otros campos en ese cuerpo se escribe.

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "Live",
    "monitor_instagram_posts": true,
    "instagram_comment_delay_minutes": 2,
    "first_response_mode": "exact_text",
    "first_response_exact_text": "Hey! Sending the details over now.",
    "first_response_exact_text_variants": [
      "Hi there, here are the details you asked for.",
      "Thanks for commenting, here is what you need."
    ],
    "public_comment_reply_exact_text": "Just sent you a DM."
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      status: "Live",
      monitor_instagram_posts: true,
      instagram_comment_delay_minutes: 2,
      first_response_mode: "ai",
      public_comment_reply_instructions:
        "Tell them to check their message requests folder too.",
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "status": "Live",
        "monitor_facebook_posts": True,
        "facebook_post_ids": None,
        "facebook_comment_delay_minutes": 5,
    },
)
data = res.json()
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

> La respuesta visible dejada en el comentario requiere la función de respuesta a comentarios en tu plan. Sin ella, el mensaje directo se envía de todos modos y la respuesta pública se omite.

---

## Optimizar una campaña con IA

`POST /campaigns/{campaignId}/optimize`

Ejecuta la misma reescritura de IA que los flujos de "Optimizar" y de comentarios de "pulgar hacia abajo" del panel de control: toma tus comentarios, reescribe las instrucciones del bot y prepara el resultado como una nueva revisión de borrador para que la revises.

| Campo | Obligatorio | Descripción |
|---|---|---|
| `user_feedback` | Uno de estos dos es obligatorio | Comentarios de formato libre que describen qué mejorar. |
| `thumbs_down_feedback` | Uno de estos dos es obligatorio | Comentarios capturados a partir de un "pulgar hacia abajo" en una respuesta específica del bot. |
| `thumbs_down_message` | No | El mensaje del bot al que se refieren los comentarios de "pulgar hacia abajo". |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/optimize?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_feedback": "Make the tone more casual and mention the free trial earlier." }'
```

**Respuesta** (`202` — la reescritura se ejecuta en segundo plano)

```json
{ "success": true, "campaign_id": "NBCXrhqGPSFsd6MV7pRo" }
```

Realiza un sondeo [`GET /campaigns/{campaignId}`](#get-a-campaign) y observa `test_bot.status`: cambia a `"Optimizing"` de inmediato, y luego vuelve a `"Draft"` una vez que la reescritura llega a `test_bot`. A partir de ahí, se comporta como cualquier borrador del panel de control: revísalo y luego publícalo en el panel de control para que esté activo. Un `409` significa que ya se está ejecutando una optimización para esta campaña.

> La optimización consume créditos, igual que cualquier otra operación de IA en tu cuenta.

---

## Asignar un contacto a una campaña

`POST /campaigns/{campaignId}/contacts/{contactId}/assign`

Añade un contacto existente a una campaña y, si así lo solicita, envía el mensaje de apertura de la campaña de inmediato. Esta es la forma de enviar la plantilla de WhatsApp aprobada de una campaña a un contacto: la plantilla con la que se aprobó una campaña pertenece a dicha campaña, por lo que no aparece en la biblioteca de la [API de plantillas](templates.md) y no se puede enviar a través de `/whatsapp-templates/send`.

| Campo | Obligatorio | Descripción |
|---|---|---|
| `sendOpeningMessage` | No | `true` envía el mensaje de apertura de la campaña (la plantilla de WhatsApp aprobada en una campaña de WhatsApp) tan pronto como se asigna el contacto. El valor predeterminado es `false`. |
| `triggerAIResponse` | No | `true` permite que la IA escriba su propio primer mensaje. El valor predeterminado es `false`. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/contacts/contact_abc123/assign?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sendOpeningMessage": true }'
```

**Respuesta**

```json
{
  "success": true,
  "data": { "contactId": "contact_abc123", "campaignId": "NBCXrhqGPSFsd6MV7pRo" }
}
```

> **Créditos:** El envío del mensaje de apertura en una campaña de WhatsApp se cobra como cualquier envío de plantilla, con un precio basado en el país del destinatario y la categoría de la plantilla. En otros canales, el mensaje de apertura es un mensaje saliente normal.

---

## Dirigir una campaña a canales entrantes

Estos endpoints gestionan qué campaña responde a contactos nuevos y desconocidos en un canal. **Prefiere los Puntos de entrada** para nuevas integraciones (consulta la nota en [Tipos de campaña](#campaign-types)); estos siguen siendo útiles para trabajar con campañas que se dirigen de la forma antigua y para resolver un conflicto de propiedad de canal entre dos campañas entrantes.

### Asignar una campaña a canales entrantes

`POST /campaigns/{campaignId}/incoming-routing`

| Campo | Obligatorio | Descripción |
|---|---|---|
| `channels` | Sí | Matriz de canales para los que esta campaña debe responder a contactos nuevos y desconocidos. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/incoming-routing?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channels": ["whatsapp", "instagram"] }'
```

**Respuesta**

```json
{
  "success": true,
  "uid": "abc123",
  "campaignId": "NBCXrhqGPSFsd6MV7pRo",
  "channels": ["whatsapp", "instagram"],
  "failed": []
}
```

`channels` enumera solo los canales que realmente se dirigieron a esta campaña; `failed` enumera los que no lo hicieron. Si todos los canales solicitados fallan, la solicitud en sí falla.

### Borrar el enrutamiento entrante de una campaña

`DELETE /campaigns/{campaignId}/incoming-routing`

| Campo | Obligatorio | Descripción |
|---|---|---|
| `channelToUnassign` | No | Borra el enrutamiento solo para este canal. Omítelo para borrar todos los canales que esta campaña responde actualmente. |

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/incoming-routing?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channelToUnassign": "instagram" }'
```

**Respuesta**

```json
{
  "success": true,
  "uid": "abc123",
  "campaignId": "NBCXrhqGPSFsd6MV7pRo",
  "channelsRemoved": ["instagram"]
}
```

### Reactivar una campaña inactiva

`POST /campaigns/{campaignId}/reactivate`

Recupera una campaña de `Ended`, `Completed`, `Paused` o `Draft` y vuelve a reclamar sus canales. Solo funciona en campañas `Incoming from Unknown Contacts` o `Combined`; una campaña que ya esté `Live` se considera un éxito y no requiere ninguna acción.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/reactivate?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "data": {
    "success": true,
    "channelsReactivated": ["whatsapp"],
    "channelsBlockedByConflict": [],
    "campaignType": "Incoming from Unknown Contacts"
  }
}
```

Un canal ya reclamado por el agente de otra campaña aparecerá en `channelsBlockedByConflict` en lugar de hacer que toda la llamada falle; utiliza [detener una campaña entrante en conflicto](#stop-a-conflicting-incoming-campaign) a continuación para liberarlo primero si deseas que esta campaña lo tome. Se devuelve un `400` para un tipo de campaña que no admite la reactivación, o un estado que no sea uno de los estados inactivos mencionados anteriormente.

### Detener una campaña entrante en conflicto

`POST /campaigns/{campaignId}/stop-incoming`

Libera los canales de esta campaña de cualquier OTRA campaña que los retenga actualmente, para que esta campaña pueda reclamarlos después. Esta es la versión REST de lo que el panel hace automáticamente cuando lanzas una campaña entrante en un canal que alguien más ya está respondiendo.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/stop-incoming?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "ended_campaign_ids": [],
  "released_channels": ["whatsapp"],
  "cleared_entire_field": false
}
```

`released_channels` devuelve un valor vacío cuando esta campaña ya posee todos los canales que anuncia; no hay nada que tomar.

---

## Estimaciones de costos

Estima cuánto costará lanzar una campaña antes de enviarla.

### Estimación de costo de plantilla de WhatsApp

`GET /campaigns/{campaignId}/template-cost-estimate`

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/template-cost-estimate?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "billing_mode": "credits",
  "data": {
    "countries": [
      {
        "countryCode": "1",
        "name": "United States",
        "iso": "US",
        "flag": "🇺🇸",
        "contactCount": 120,
        "costPerContact": 2,
        "subtotal": 240
      }
    ],
    "totalContacts": 120,
    "totalTemplateCost": 240,
    "templateCategory": "marketing",
    "billing_mode": "credits",
    "service_messages_billable_soon": false
  }
}
```

`billing_mode` es `"credits"` en el carril gestionado de WhatsApp. En un carril donde Meta factura directamente a tu propia cuenta de WhatsApp Business, `costPerContact`, `subtotal` y `totalTemplateCost` devuelven `null` (nunca `0`, lo que se interpretaría como gratuito), ya que no hay una cifra de crédito que informar.

### Estimación de costo de SMS

`GET /campaigns/{campaignId}/sms-cost-estimate`

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/sms-cost-estimate?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "billing_mode": "twilio_direct",
  "data": {
    "totalContacts": 120,
    "messageLength": 87,
    "segmentsPerMessage": 1,
    "totalSegments": 120,
    "estimatedCostUsd": 0.96,
    "priceUnit": "USD per segment",
    "billedByTwilio": true
  }
}
```

Los SMS siempre se envían a través de tu propia cuenta de Twilio (consulta [proveedor de SMS](../settings/sms-provider.md)), por lo que esto siempre es facturado directamente por Twilio; `estimatedCostUsd` es una estimación de esa factura de Twilio, no un cargo de crédito.

---

## Comprobaciones de límites

Compruebe un límite antes de realizar el lanzamiento, en lugar de descubrirlo tras un envío fallido.

### Comprobaciones a nivel de campaña

`GET /campaigns/{campaignId}/limits/ai-credit-messaging`: si el lanzamiento o la programación de esta campaña excedería el límite de mensajería de créditos de IA de su cuenta.

`GET /campaigns/{campaignId}/limits/messaging`: si excedería el límite de mensajería diario de su cuenta.

```bash
curl "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/limits/messaging?apiKey=YOUR_API_KEY"
```

**Respuesta** (límite no excedido)

```json
{
  "success": true,
  "data": "Campaign is within the daily messaging limit."
}
```

Se devuelve un `400` en su lugar cuando se excede el límite, con el motivo en `error`.

### Comprobaciones a nivel de cuenta

`GET /campaigns/limits/campaigns`: si ha alcanzado el límite mensual de creación de campañas de su suscripción.

`GET /campaigns/limits/contacts`: si ha alcanzado el límite de contactos de su suscripción.

```bash
curl "https://api.youraiconnector.com/v1/campaigns/limits/campaigns?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "data": "You can create 3 more campaigns this month."
}
```

---

## Totales de estadísticas de campaña

`GET /campaigns/stats/totals`

Totales de envíos y respuestas para cada campaña Y cada agente de IA en su cuenta, durante un periodo de tiempo determinado: las mismas cifras que muestra la página de lista de campañas junto a cada fila, en una sola llamada en lugar de una solicitud por campaña.

| Parámetro de consulta | Descripción |
|---|---|
| `days` | Tamaño del periodo de tiempo, de 1 a 365. El valor predeterminado es 90. |

```bash
curl "https://api.youraiconnector.com/v1/campaigns/stats/totals?days=30&apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "byCampaign": {
    "NBCXrhqGPSFsd6MV7pRo": { "sent": 1204, "replied": 318 }
  },
  "byAgent": {
    "agent_abc123": { "sent": 1204, "replied": 318 }
  },
  "windowDays": 30
}
```

`byAgent` es su propio resumen, no una suma de `byCampaign`: el tráfico de una cuenta nativa de Agente de IA puede no tener ninguna campaña, por lo que de otro modo sería invisible aquí.

---

## Probar una campaña en el entorno de pruebas (playground)

El entorno de pruebas te permite mantener una conversación con el bot de una campaña sin tocar un canal real o un contacto real. Es el mismo entorno aislado que el panel de prueba del tablero, y está totalmente disponible a través de la API.

El flujo es: crear un contacto de prueba oculto, enviar un mensaje y luego consultar la campaña para obtener la respuesta del bot. Las respuestas se generan de forma asíncrona, por lo que llegan en `test_messages` en la campaña en lugar de en el cuerpo de la respuesta.

> **Playground utiliza créditos de coste de la API.** Una conversación de prueba iniciada con una clave de API se cobra a la tarifa normal de mensajes de IA, igual que una respuesta real, y aparece en su historial de uso como una entrada normal. Las pruebas desde el panel de control siguen siendo gratuitas. La diferencia es deliberada: una prueba realiza el mismo trabajo de IA que una real, por lo que un playground de API sin medición sería una forma de ejecutar IA ilimitada a costa de otros.

### Paso 1 - Crear el contacto de prueba

`POST /campaigns/{campaignId}/try-out/contact`

Crea el contacto de prueba oculto y lo vincula a la campaña. Todos los campos del cuerpo son opcionales; cualquier cosa que omita recurrirá a una identidad de muestra integrada (John Doe).

| Campo | Requerido | Descripción |
|---|---|---|
| `first_name` | No | Nombre del contacto de prueba. |
| `last_name` | No | Apellido del contacto de prueba. |
| `email` | No | Correo electrónico del contacto de prueba. |
| `phone` | No | Número de teléfono del contacto de prueba. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/contact?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "first_name": "Maria", "last_name": "Lopez" }'
```

**Respuesta**

```json
{
  "success": true,
  "contactId": "8kQx1vNbA2fLpR7d"
}
```

### Paso 2 - Registrar el mensaje entrante

`POST /campaigns/{campaignId}/try-out/messages`

Añade mensajes al hilo de prueba. Envíe primero el mensaje del visitante aquí, para que aparezca en el historial de la conversación que lee el bot.

| Campo | Requerido | Descripción |
|---|---|---|
| `messages` | Sí | Matriz de objetos de mensaje, máximo 200 por solicitud. |
| `messages[].body` | Sí | El texto del mensaje. |
| `messages[].direction` | Sí | `"inbound"` para el visitante, `"outbound"` para el bot. |
| `messages[].timestamp` | No | Cadena ISO-8601 o milisegundos de época. |
| `messages[].role` | No | Etiqueta de rol opcional. |
| `messages[].name` | No | Nombre para mostrar opcional. |
| `ignoreCounter` | No | Entero. Restablece el contador de ignorar de la campaña en la misma escritura. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/messages?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "body": "Do you ship to Belgium?",
        "direction": "inbound",
        "timestamp": "2026-07-22T09:30:00Z"
      }
    ]
  }'
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "appended": 1
}
```

### Paso 3 - Pedir al bot que responda

`POST /campaigns/{campaignId}/try-out/test-message`

Envía el mensaje a la canalización de IA. Esta es la llamada que realmente produce una respuesta del bot.

| Campo | Requerido | Descripción |
|---|---|---|
| `message` | Sí | El texto del mensaje más reciente del visitante. |

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/test-message?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Do you ship to Belgium?" }'
```

**Respuesta**

```json
{
  "success": true,
  "data": "Published"
}
```

`"Published"` significa que el mensaje fue a la canalización de IA. `"Ignored"` significa que un mensaje de prueba más reciente reemplazó a este: el entorno de pruebas combina una ráfaga rápida en una sola respuesta, aproximadamente cuatro segundos después del último mensaje, de la misma manera que una conversación real espera a que alguien termine de escribir. Debido a esa ventana de combinación, esta llamada tarda unos segundos en devolver una respuesta.

### Paso 4 - Leer la respuesta

`GET /campaigns/{campaignId}`

La respuesta del bot se añade a la matriz `test_messages` de la campaña. Sondee la campaña hasta que aparezca una nueva entrada `outbound`.

```json
{
  "success": true,
  "campaign": {
    "id": "NBCXrhqGPSFsd6MV7pRo",
    "test_messages": [
      { "body": "Do you ship to Belgium?", "direction": "inbound" },
      { "body": "Yes, we ship across the EU.", "direction": "outbound" }
    ]
  }
}
```

### Restablecer el entorno de pruebas

`POST /campaigns/{campaignId}/try-out/reset`

Limpia todo el entorno de pruebas (sandbox): elimina el contacto de prueba, borra `test_messages` y libera los bloqueos de respuesta del bot. Úselo entre ejecuciones de prueba.

```bash
curl -X POST "https://api.youraiconnector.com/v1/campaigns/NBCXrhqGPSFsd6MV7pRo/try-out/reset?apiKey=YOUR_API_KEY"
```

**Respuesta**

```json
{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}
```

### Otros endpoints del playground

| Endpoint | Qué hace |
|---|---|
| `DELETE /campaigns/{campaignId}/try-out/contact` | Elimina solo el contacto de prueba actual y lo desvincula, dejando `test_messages` intacto. Se ejecuta correctamente incluso cuando no hay ningún contacto vinculado. |
| `POST /campaigns/{campaignId}/try-out/transfer` | Inicia un playground nuevo con una conversación existente, en una sola solicitud: reemplaza el contacto de prueba y sobrescribe `test_messages`. El cuerpo acepta `first_name`, `last_name`, `messages` (puede estar vacío) y `ignoreCounter`. Prefiera esto en lugar de eliminar-luego-crear-luego-añadir, lo cual triplica su consumo de límite de tasa. |
| `POST /campaigns/{campaignId}/try-out/messages/replace` | Sobrescribe `test_messages` por completo en lugar de añadir. Úselo para truncar o rebobinar un hilo. |
| `POST /campaigns/{campaignId}/try-out/contact/reset-ignore-counter` | Restablece solo el contador de ignorados del contacto de prueba, para flujos de rehacer y repetir después de un envío. |

---

## Errores de la API de campañas

Los endpoints de campaña devuelven el sobre de error estándar:

```json
{
  "success": false,
  "error": "Campaign not found"
}
```

| Estado | Cuándo ocurre en un endpoint de campaña |
|---|---|
| `400` | Falta un campo obligatorio o no es válido (por ejemplo, un `type` incorrecto, un `enabled` que no es booleano o una clave de día de la semana desconocida). También lo devuelve un endpoint de [verificación de límite](#limit-checks) cuando se excedería el límite, y por [reactivar](#reactivate-a-dormant-campaign) para un tipo o estado de campaña que no lo admite. |
| `404` | No se encontró la campaña: o bien no existe o pertenece a otra cuenta. |
| `409` | Ya hay una [optimización](#optimize-a-campaign-with-ai) en ejecución para esta campaña. |

Los códigos compartidos que puede devolver cualquier endpoint — `401`, `403` (su plan no incluye acceso a la API), `429` (límite de tasa) y `500` — se enumeran con orientación sobre reintentos en [Errores y paginación](errors-and-pagination.md).

---

## Relacionado

- [Enrutar un canal a una campaña](channels.md#route-a-channel-to-a-campaign) — dirija Instagram, WhatsApp o cualquier otro canal al Agente de IA que deba responderlo, utilizando Puntos de entrada.
- [Generar plantillas de seguimiento con IA](templates.md#generate-follow-up-templates-with-ai) — inicie una tarea en segundo plano que redacte las plantillas de seguimiento de WhatsApp de una campaña.
- [API de preguntas frecuentes](faqs.md) — gestione las entradas de preguntas y respuestas que utilizan sus campañas.
- [Acceso a la API](../integrations/api-access.md) — genere su clave de API.
- [Autenticación](authentication.md) — todas las formas de proporcionar su clave.
