
# API de Campanhas

Uma campanha agrupa tudo o que o bot de IA precisa para falar com os seus contactos: as suas instruções, os canais em que é executado, o seu horário de funcionamento e o seu comportamento de seguimento. A API de Campanhas permite-lhe listar, criar, atualizar, duplicar, ativar, arquivar e ajustar campanhas a partir do seu próprio código em vez de utilizar o painel de controlo.

Todos os endpoints abaixo são relativos ao URL base `https://api.youraiconnector.com/v1`. Todos os pedidos devem ser autenticados — consulte [Acesso à API](../integrations/api-access.md) e [Autenticação](authentication.md) para saber como obter e transmitir a sua chave de API. O acesso à API é uma funcionalidade paga; sem ela, os pedidos são rejeitados com um `403`.

> **Atenção:** Alguns exemplos mostram a forma de consulta simples `?apiKey=YOUR_API_KEY`, outros utilizam o cabeçalho `X-API-Key`. Ambos funcionam em todo o lado — utilize o que melhor se adaptar à sua configuração.

---

## Tipos de campanha

Ao criar uma campanha, tem de escolher um destes tipos:

| Tipo | Para que serve |
|---|---|
| `Incoming from Unknown Contacts` | O bot responde a pessoas que lhe enviam mensagens pela primeira vez. |
| `Outgoing` | O bot inicia conversas com contactos que adiciona à campanha. |
| `Keywords` | **Inerte - não utilizar.** Uma campanha `Keywords` é inerte: ainda é aceite por retrocompatibilidade, mas é invisível para o encaminhamento de entrada em todos os canais e nada lê as suas palavras-chave de ativação. Utilize um Ponto de Entrada do tipo **Palavra-chave** num Agente de IA. |
| `Combined` | Uma mistura de comportamento de entrada e saída. |

**As maiúsculas e minúsculas não importam.** `type`, `status`, `booking_provider`, `first_response_mode`, `bot.anthropic_model` e `bot.ai_speed` aceitam qualquer capitalização — `"live"`, `"Live"` e `"LIVE"` são a mesma coisa — e o valor é guardado na sua forma canónica, que é o que é devolvido quando lê a campanha. A única exceção é o par de pausa: `"Paused"` e `"paused"` são dois estados genuinamente diferentes, pelo que uma grafia ambígua como `"PAUSED"` é rejeitada com um `400` a pedir-lhe que escolha um.

### Os dois estados de pausa

| Estado | Quem o define | O que significa |
|---|---|---|
| `Paused` | As verificações de segurança da própria plataforma (baixo envolvimento, erros de envio repetidos, limite atingido) e as superfícies mais recentes de Agentes e Transmissões | A campanha está retida. Uma verificação agendada pode levantar uma pausa de segurança automaticamente assim que o motivo for resolvido. |
| `paused` | O botão Pausar do painel, emparelhado com `resumed` em Retomar | Uma pessoa pausou manualmente. Os envios agendados são removidos e reconstruídos ao retomar. |

Ambos interrompem a campanha: o encaminhamento de entrada só funciona enquanto o estado for exatamente `Live`. **A partir da API, utilize `Paused` para pausar e `Live` para retomar** — o par em minúsculas existe para o botão do painel e mantém-se funcional para o mesmo.

Nenhum destes estados é o que acontece quando a IA para de responder dentro de uma conversa. Esse é um interruptor por contacto, `is_bot_active` no contacto — definido quando um humano assume o controlo, quando o contacto opta por sair ou quando a IA conclui o chat. O estado da própria campanha permanece inalterado e todas as outras conversas nela continuam a decorrer. Consulte [pausar ou retomar a IA para um contacto](messages.md#pause-or-resume-the-ai-for-one-contact).

> **Criar uma campanha não decide quem responde a um canal.** O encaminhamento é gerido por **Pontos de Entrada** num Agente de IA, não por campanhas. Cada canal tem um Ponto de Entrada predefinido que nomeia o Agente que responde a contactos novos e desconhecidos: defina-o com `PUT /entry-points/channel-defaults`, verifique se a hierarquia está ativa para a conta com `GET /entry-points/routing-status`, limpe-o com `DELETE /entry-points/channel-defaults`. O `POST /channels/campaign` ainda escreve o mapa de encaminhamento de campanhas legado por canal, mas esse mapa já não é consultado para encaminhamento de entrada em nenhuma conta; é mantido apenas para reversão. Não crie nada com base nele. Consulte [Encaminhar um canal para uma campanha](channels.md#route-a-channel-to-a-campaign) para ver ambas as superfícies lado a lado.

---

## Listar campanhas

`GET /campaigns`

Devolve as suas campanhas, da mais recente para a mais antiga. As campanhas arquivadas são excluídas, a menos que passe `archived=true`.

**Parâmetros de consulta**

| Parâmetro | Obrigatório | Descrição |
|---|---|---|
| `limit` | Não | Número máximo de campanhas a devolver. Predefinição `50`, máximo `100`. |
| `cursor` | Não | Cursor de paginação. Passe o valor `next_cursor` da resposta anterior para obter a página seguinte. |
| `archived` | Não | Defina como `true` para incluir campanhas arquivadas. |

**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"])
```

**Resposta**

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

Quando `next_cursor` é `null`, chegou à última página.

---

## Obter uma campanha

`GET /campaigns/{campaignId}`

Devolve o documento completo da campanha, incluindo a configuração do bot em direto (`bot`), definições de seguimento, canais ativados e quaisquer palavras-chave. Os carimbos de data/hora são devolvidos em milissegundos da é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"]
```

**Resposta**

```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:** Uma campanha pertencente a uma conta diferente devolve `404 Campaign not found` (não `403`), pelo que não é possível saber se um ID existe noutra conta.
:::


---

## Criar uma campanha

`POST /campaigns`

Cria uma nova campanha. `name` e `type` são obrigatórios; tudo o resto é opcional. Pode incluir qualquer outro campo de campanha no mesmo pedido — por exemplo, `language`, `ai_mode` ou um objeto de configuração `bot` completo — e este será guardado com a nova campanha. O proprietário e a hora de criação são definidos automaticamente.

**Campos do pedido**

| Campo | Obrigatório | Descrição |
|---|---|---|
| `name` | Sim | O nome da campanha. |
| `type` | Sim | Um dos quatro tipos de campanha acima. |
| `language` | Não | Idioma em que o bot responde (por exemplo, `"en"`). |
| `ai_mode` | Não | Se o modo IA está ligado (`true`/`false`). Numa campanha respondida por um Agente de IA, as leituras devolvem o botão **Ativo** do Agente em vez de um valor guardado — veja a nota abaixo sobre a atualização. |
| `bot` | Não | O objeto de configuração do bot (veja [Campos de configuração do bot](#bot-configuration-fields)). |
| `list_id` | Não | ID da lista de contactos a anexar. |
| `event_id` | Não | ID do tipo de evento que a IA pode agendar. |
| `event_ids` | Não | Vários tipos de evento de uma vez, como uma matriz de IDs de tipos de evento — o primeiro é o predefinido. Envie `event_id` ou `event_ids`, não 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"]
```

**Resposta**

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

---

## Atualizar uma campanha

`PUT /campaigns/{campaignId}`

Atualiza parcialmente uma campanha — envie apenas os campos que pretende alterar. Este é o único verbo de atualização geral; não existe `PATCH /campaigns/{campaignId}` (as duas rotas `PATCH` são os seletores restritos de [ativar](#enable-or-disable-a-campaign) e [arquivar](#archive-or-restore-a-campaign)).

**Campos que pode alterar.** Tudo o que o editor de campanhas escreve, incluindo `name`, `status`, `type`, `language`, `ai_mode`, `enabled_channels`, as definições de gatilho e gotejamento, os sinalizadores de reserva e seguimento, os campos de monitorização do Instagram/Facebook e toda a configuração `bot`. A identidade e a propriedade estão bloqueadas durante a vida útil da campanha: `user`, `id` e `created_at` são rejeitados, assim como qualquer nome de campo que o endpoint não reconheça. A rejeição é por pedido, não por campo — uma chave desconhecida devolve um `400` e **nada** nesse pedido é escrito.

**`ai_mode` numa campanha suportada por um Agente reflete o Agente.** Quando uma campanha é respondida por um Agente de IA, a leitura da campanha devolve `ai_mode` derivado do interruptor **Ativo** desse Agente — o único interruptor que decide efetivamente se a IA responde. Escrever `ai_mode` numa campanha deste tipo é aceite, mas não alterará o que lê posteriormente; em vez disso, ligue ou desligue o interruptor Ativo do Agente (no painel de controlo ou através da API de Agentes). Em campanhas clássicas sem Agente, `ai_mode` lê e escreve o valor guardado como anteriormente.

**Os campos do bot são fundidos, não substituídos.** Envie as definições do bot como chaves com pontos (`"bot.instructions": "..."`) ou como um objeto aninhado (`"bot": { "instructions": "..." }`) — ambos escrevem folha a folha, pelo que os campos que omitir mantêm os seus valores atuais. `bot.instructions`, `bot.goal`, `bot.rules` e `bot.personality` são todos editáveis desta forma, tal como qualquer outra definição de bot listada em [Campos de configuração do bot](#bot-configuration-fields). O mesmo se aplica a `test_bot`, `frequency` e `follow_up_config`.

Para substituir uma configuração de bot na totalidade — eliminando qualquer campo que não envie — utilize `bot_replace` (ou `test_bot_replace`) com o objeto completo. Não pode combinar uma substituição e uma fusão para o mesmo objeto num único pedido; isso devolve um `400`.

::: note
**Nota:** Escrever `bot.*` através da API entra em vigor **imediatamente** na campanha ativa. O editor do painel funciona de forma diferente: as edições feitas aí são guardadas como rascunho e só entram em vigor quando o cliente clica em Publicar. Portanto, se um cliente tiver alterações no painel não publicadas, estas permanecem em `test_bot` e uma leitura da API de `bot` mostra corretamente o que a IA está a utilizar neste momento.
:::


Alguns campos são definidos através de uma chave dedicada em vez de serem escritos diretamente: utilize `list_id` para a lista de contactos, `event_id` para o tipo de evento (ou `event_ids`, uma matriz ordenada de IDs de tipos de evento, para permitir que a IA agende vários — o primeiro é o predefinido; uma matriz vazia desassocia-os a todos), e `contact_ids` (uma matriz de IDs de contactos) para os contactos da campanha. As entradas da base de conhecimento são geridas através da [API de FAQs](faqs.md), e não deste endpoint.

**As etiquetas substituem, não se fundem.** Envie `tags` como o array completo e este torna-se o conjunto de etiquetas da campanha — consulte [Etiquetas de campanha](#campaign-tags) para os campos e para os endpoints que adicionam ou editam uma única 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()
```

**Resposta**

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

---

## Eliminar uma campanha

`DELETE /campaigns/{campaignId}`

Elimina permanentemente uma campanha. Esta ação não pode ser anulada — se puder vir a precisar da campanha novamente, [arquive-a](#archive-or-restore-a-campaign) em vez disso.

**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()
```

**Resposta**

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

---

## Duplicar uma campanha

`POST /campaigns/{campaignId}/duplicate`

Cria uma cópia da campanha com todas as suas definições preservadas. A cópia começa **desativada** e o seu nome recebe um sufixo `(copy)`, para que nunca envie mensagens até que a ative explicitamente.

**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"]
```

**Resposta**

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

> Cópias duplicadas **dentro de uma conta**.

---


## Ativar ou desativar uma campanha

`PATCH /campaigns/{campaignId}/enabled`

Liga ou desliga uma campanha. Uma campanha desativada deixa de interagir com os contactos, mas mantém toda a sua configuração.

**Campos do pedido**

| Campo | Obrigatório | Descrição |
|---|---|---|
| `enabled` | Sim | `true` para ativar, `false` para desativar. Tem de ser um 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()
```

**Resposta**

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

---

## Arquivar ou restaurar uma campanha

`PATCH /campaigns/{campaignId}/archived`

Arquiva ou restaura uma campanha. As campanhas arquivadas são ocultadas da lista de campanhas predefinida, mas mantêm todos os seus dados e podem ser restauradas a qualquer momento.

**Campos do pedido**

| Campo | Obrigatório | Descrição |
|---|---|---|
| `archived` | Sim | `true` para arquivar, `false` para restaurar. Tem de ser um 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()
```

**Resposta**

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

---

## Atualizar a configuração do bot

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

Esta é a forma segura de alterar definições individuais do bot. Cada campo que envia é **fundido** com a configuração existente do bot, pelo que quaisquer campos que omita são preservados. Utilize este método em vez do endpoint de atualização de campanha sempre que pretender apenas ajustar uma parte do bot.

As chaves dos campos devem utilizar apenas letras, números, sublinhados e hífenes.

**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()
```

**Resposta**

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

### Campos de configuração do bot

Todos os campos do bot são opcionais. Envie apenas aqueles que pretende definir. Quaisquer campos adicionais do bot para além dos listados aqui são aceites e armazenados tal como estão.

| Campo | Tipo | Descrição |
|---|---|---|
| `instructions` | string | As instruções principais que orientam a forma como o bot fala com os contactos. |
| `rules` | string | Regras rígidas que o bot deve seguir sempre. |
| `goal` | string | O resultado para o qual o bot deve trabalhar em cada conversa. |
| `personality` | string | Descrição do tom de voz e da personalidade do bot. |
| `ai_speed` | string | Quanta capacidade de raciocínio a IA aplica antes de responder. Um de `fast`, `fast_thinker`, `balanced`, `thorough`. |
| `anthropic_model` | string | O nível de qualidade da IA utilizado para as respostas desta campanha. Um de `standard`, `economy` (obsoleto), `max`, `mini`. `max` e `mini` só produzem efeito em contas elegíveis para esses níveis. |
| `max_messages` | integer | Número máximo de mensagens do bot por conversa. |
| `alert_human_when` | string | Condições sob as quais o bot deve alertar um colega humano. |
| `availability` | object | O horário de funcionamento do bot. Pode defini-lo aqui ou utilizar o [endpoint de horário de funcionamento](#set-the-bot-active-hours) dedicado. |
| `follow_up_config` | object | Configuração do comportamento de seguimento, armazenada conforme fornecida. |

---

## Definir o horário de funcionamento do bot

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

Define o horário de disponibilidade do bot. Fora das janelas configuradas, o bot não responde automaticamente. Isto escreve no campo `availability` da configuração do bot.

**Campos do pedido**

| Campo | Obrigatório | Descrição |
|---|---|---|
| `availability` | Sim | Um objeto organizado por dia da semana. As chaves permitidas são `monday` a `sunday`; qualquer outra chave devolve um `400`. Os dias que omitir permanecem inalterados. |

Cada dia da semana contém uma única janela de tempo ou uma matriz de janelas. Uma janela tem um `start_time` e um `end_time` no formato `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()
```

**Resposta**

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

---

## Listar as funções personalizadas de uma campanha

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

Devolve as funções personalizadas associadas a esta campanha, resolvidas em definições completas. As funções personalizadas são ações HTTP externas que o bot pode chamar durante uma conversa — por exemplo, verificar o stock na sua loja ou criar um registo no seu 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"]
```

**Resposta**

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

---

## Associar uma função personalizada a uma campanha

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

Associa uma [função personalizada](../ai-automation/custom-functions.md) existente a esta campanha para que o bot a possa chamar durante uma conversa. Associar uma função que já se encontra associada não produz qualquer efeito.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `custom_function_id` | Sim | ID da função personalizada a associar. |

```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" }'
```

**Resposta**

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

---

## Desassociar uma função personalizada de uma campanha

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

Desassociar uma função que não se encontra associada não produz qualquer efeito.

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

**Resposta**

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

---

## Associar uma fonte de base de conhecimento a uma campanha

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

Associa uma fonte de base de conhecimento (criada através da [API de FAQs](faqs.md)) a esta campanha para que o bot a possa utilizar ao responder. Associar uma fonte que já se encontra associada não produz qualquer efeito.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `kb_source_id` | Sim | ID da fonte de base de conhecimento a associar. |

```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" }'
```

**Resposta**

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

---

## Desassociar uma fonte de base de conhecimento de uma campanha

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

Desassociar uma fonte que não se encontra associada não produz qualquer efeito.

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

**Resposta**

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

---

## Associar um servidor MCP a uma campanha

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

Associa um servidor MCP a esta campanha, dando ao bot acesso às ferramentas desse servidor durante uma conversa. Associar um servidor que já se encontra associado não produz qualquer efeito.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `mcp_server_id` | Sim | ID do servidor MCP a associar. |

```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" }'
```

**Resposta**

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

---

## Desassociar um servidor MCP de uma campanha

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

Desassociar um servidor que não está associado não produz qualquer efeito.

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

**Resposta**

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

---

## Biblioteca de multimédia da campanha

A biblioteca de multimédia contém imagens, vídeos, documentos e notas de voz que o bot pode enviar durante uma conversa.

### Listar a biblioteca de multimédia de uma campanha

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

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

**Resposta**

```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` é um URL assinado capturado no momento do carregamento — pode já ter expirado quando o ler; o painel de controlo volta a assiná-lo a pedido.

### Carregar um item multimédia

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

| Campo | Obrigatório | Descrição |
|---|---|---|
| `base64Data` | Sim | O ficheiro, codificado em base64 (sem prefixo data-URL). |
| `mimeType` | Sim | Tipo MIME do ficheiro (por exemplo, `image/png`). |
| `title` | Sim | Etiqueta curta apresentada na biblioteca e no prompt da IA. |
| `description` | Sim | Instrução que indica ao bot **quando** deve enviar este item. |
| `fileName` | Não | Nome original do ficheiro, utilizado para criar o nome do objeto de armazenamento. |
| `sendMessage` | Não | Texto preferencial que o bot deve utilizar ao enviar este item. |
| `maxSendsPerConversation` | Não | Número máximo de vezes que o bot pode enviar este item a um contacto numa conversa. O valor predefinido é `1`. |
| `sendAsVoiceNote` | Não | Para um carregamento de áudio, transcodifica-o para uma nota de voz do WhatsApp. O valor predefinido é `false` (guardado como um ficheiro de áudio simples). |

```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."
  }'
```

**Resposta**

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

### Atualizar um item de multimédia

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

Edita apenas os metadados do item — para substituir o próprio ficheiro, elimine o item e carregue um novo.

| Campo | Descrição |
|---|---|
| `title` | Etiqueta curta. |
| `description` | Instrução de quando enviar. |
| `send_message` | Formulação preferencial a ser utilizada pelo bot. |
| `max_sends_per_conversation` | Inteiro não negativo, ou `null` para limpar o limite. |

```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" }'
```

**Resposta**

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

### Eliminar um item de multimédia

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

Eliminar um item que já não existe é uma operação sem efeito (no-op).

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

**Resposta**

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

---

## Etiquetas de campanha

Uma etiqueta de campanha é um rótulo que ensina o bot a aplicar a um contacto durante uma conversação — `hot-lead`, `not-interested`, `booked-a-call`. Cada etiqueta tem três partes:

| Campo | Tipo | Descrição |
|---|---|---|
| `name` | string, obrigatório | O rótulo em si. É isto que o bot aplica ao contacto e o que utilizará para correspondência mais tarde, por isso mantenha-o curto e estável. |
| `description` | string | A instrução que diz ao bot **quando** aplicar esta etiqueta. Esta é a parte que realiza o trabalho — "a pessoa confirma que se juntou à comunidade" é utilizada, "potencial cliente interessado" não. |
| `webhook` | string | Um URL que recebe um `POST` no momento em que a etiqueta é atribuída a um contacto. Deixe em branco se não precisar de um. |
| `tag_id` | string | Opcional. Liga esta entrada a uma etiqueta existente na sua conta em vez de uma nova. Forneça-o se quiser tratar desta etiqueta específica mais tarde com os endpoints de etiqueta única abaixo. |

Os nomes das etiquetas devem ser únicos dentro de uma campanha. O bot aplica etiquetas **por nome**, pelo que duas entradas que partilhem o mesmo nome não têm um vencedor definido.

### Definir todas as etiquetas de uma campanha

`PUT /campaigns/{campaignId}` com um array `tags`.

Isto substitui as etiquetas da campanha exatamente pelo que enviar, o que é o mesmo que o separador Etiquetas do painel de controlo faz quando guarda. **Envie o array completo sempre** — uma etiqueta que deixe de fora é uma etiqueta que eliminou. Enviar `[]` limpa-as 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()
```

**Resposta**

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

Leia as etiquetas de volta com [`GET /campaigns/{campaignId}`](#get-a-campaign).

### Adicionar uma etiqueta

`POST /campaigns/{campaignId}/tags`

Anexa uma única etiqueta sem reenviar o resto. Utilize isto quando estiver a adicionar a um conjunto que não criou neste pedido.

```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 exatamente a mesma etiqueta duas vezes não faz nada na segunda vez. Publicar o mesmo `tag_id` com um nome ou descrição diferente anexa uma **segunda** entrada em vez de editar a primeira — utilize o endpoint abaixo para editar no local.

### Atualizar ou remover uma etiqueta

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

Estes endereçam uma entrada pelo seu `tag_id`, por isso só funcionam em etiquetas que foram criadas com um. Se uma etiqueta não tiver um `tag_id`, altere-a com o `PUT /campaigns/{campaignId}` de matriz completa acima.

```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." } }'
```

Uma `tagId` que não esteja na campanha devolve `404` com `"Tag not found in campaign tags"`.

---

## Alternar os canais de uma campanha

`POST /campaigns/{campaignId}/channels`

Adiciona ou remove canais da matriz `enabled_channels` da campanha sem reenviar a matriz completa — mais seguro do que [`PUT /campaigns/{campaignId}`](#update-a-campaign) quando algo mais pode estar a editar a campanha ao mesmo tempo.

Envie uma alternância única ou um lote — não ambos no mesmo pedido:

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

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

| Campo | Descrição |
|---|---|
| `channel` | Um canal para alternar. Emparelhe com `action`. |
| `action` | `"add"` ou `"remove"`. Emparelhe com `channel`. |
| `add` | Matriz de canais a adicionar. Formulário de lote — utilize em vez de `channel`/`action`. |
| `remove` | Matriz de canais a remover. Formulário de lote. |

Canais 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" }'
```

**Resposta**

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

> Isto altera apenas os canais que a campanha anuncia — não decide quem responde a um canal. Consulte [Tipos de campanha](#campaign-types) acima e [Encaminhar uma campanha para canais de entrada](#route-a-campaign-to-incoming-channels) abaixo para esse efeito.

---

## Comment-to-DM (Instagram e Facebook)

A funcionalidade Comment-to-DM transforma um comentário numa das suas publicações numa conversa privada: alguém comenta, o bot envia-lhes uma mensagem direta (DM) e a campanha assume o controlo da conversa a partir daí. É configurada inteiramente através do objeto da campanha, pelo que não existe qualquer componente exclusiva da interface de utilizador.

Ligue primeiro a Página de Facebook — consulte [Ligação de Canal](channels.md#instagram--messenger-meta). Em seguida, defina os campos abaixo com [`PUT /campaigns/{campaignId}`](#update-a-campaign).

> **A campanha tem de estar `Live`.** A monitorização de comentários apenas recolhe campanhas cujo `status` seja `Live` (qualquer capitalização — veja [Tipos de campanha](#campaign-types)). Qualquer outro estado desativa-a silenciosamente, e um inventado como `"Active"` é agora rejeitado com um `400` em vez de ser guardado. Os estados válidos incluem `Draft`, `Pending Approval`, `Scheduled`, `Live`, `Paused`, `Completed`, `Sent` e `Failed`.

**Campos**

| Campo | Tipo | Descrição |
|---|---|---|
| `monitor_instagram_posts` | boolean | Monitorizar todas as publicações do Instagram na página ligada. |
| `instagram_post_ids` | string[] | Monitorizar apenas estas publicações do Instagram. Deixe por definir quando `monitor_instagram_posts` estiver ativado. |
| `instagram_comment_delay_minutes` | number | Aguardar este número de minutos após um comentário antes de enviar a DM. |
| `monitor_facebook_posts` | boolean | Monitorizar todas as publicações do Facebook na página ligada. |
| `facebook_post_ids` | string[] | Monitorizar apenas estas publicações do Facebook. |
| `facebook_comment_delay_minutes` | number | Atraso antes da DM, em minutos. |
| `public_comment_reply_instructions` | string | Orientação para a resposta visível deixada no próprio comentário. Substitui a redação predefinida "verifique as suas DMs". |
| `first_response_mode` | string | `"ai"` (predefinição) gera a primeira DM e a resposta pública. `"exact_text"` envia a sua redação palavra por palavra, sem geração de IA e sem custo de crédito. |
| `first_response_exact_text` | string | A primeira DM palavra por palavra, utilizada quando `first_response_mode` é `"exact_text"`. Necessário para que esse modo entre em vigor. |
| `first_response_exact_text_variants` | string[] | Redações adicionais para a primeira DM. Uma é escolhida aleatoriamente por envio, para que as DMs repetidas não sejam idênticas. |
| `public_comment_reply_exact_text` | string | A resposta pública palavra por palavra no modo `"exact_text"`. Deixe em branco para ignorar a resposta pública e enviar apenas a DM. |
| `public_comment_reply_exact_text_variants` | string[] | Redações adicionais para a resposta pública. |
| `monitor_instagram_followers` | boolean | Tratar um novo seguidor como um gatilho e enviar uma DM de abertura (contas pessoais do Instagram). |
| `follower_outreach_instructions` | string | Orientação para essa DM de abertura para novos seguidores. |
| `respond_to_instagram_story_replies` | boolean | Se a IA responde a respostas às suas Stories do Instagram. Predefinição `true`. Defina `false` para que as respostas às Stories cheguem ao chat (com a Story anexada) sem uma resposta da IA. Definição em tempo real — não faz parte do rascunho, pelo que não necessita de publicação. |

**Limpar um campo**

Estes campos são removidos em vez de definidos como `null` quando envia `null`, pelo que o bot reverte para as suas predefinições: `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`.

> **Uma chave desconhecida rejeita todo o pedido.** O `PUT /campaigns/{campaignId}` valida todo o corpo contra uma lista de permissões. Uma chave que não seja reconhecida devolve `400` para o pedido como um todo — não é ignorada silenciosamente e nenhum dos outros campos nesse corpo é escrito.

**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()
```

**Resposta**

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

> A resposta visível deixada no comentário requer a funcionalidade de resposta a comentários no seu plano. Sem ela, a DM é enviada na mesma e a resposta pública é ignorada.

---

## Otimizar uma campanha com IA

`POST /campaigns/{campaignId}/optimize`

Executa a mesma reescrita de IA que os fluxos de Otimização e feedback de polegar para baixo do painel: recebe o seu feedback, reescreve as instruções do bot e prepara o resultado como uma nova revisão de rascunho para você analisar.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `user_feedback` | Um destes dois é obrigatório | Feedback de formato livre que descreve o que melhorar. |
| `thumbs_down_feedback` | Um destes dois é obrigatório | Feedback capturado a partir de um polegar para baixo numa resposta específica do bot. |
| `thumbs_down_message` | Não | A mensagem do bot à qual o feedback de polegar para baixo se refere. |

```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." }'
```

**Resposta** (`202` — a reescrita é executada em segundo plano)

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

Faça o polling de [`GET /campaigns/{campaignId}`](#get-a-campaign) e observe `test_bot.status`: muda imediatamente para `"Optimizing"` e, depois, volta para `"Draft"` assim que a reescrita for concluída em `test_bot`. A partir daí, comporta-se como qualquer rascunho do painel — analise-o e, em seguida, publique-o no painel para o tornar ativo. Um `409` significa que já existe uma otimização em execução para esta campanha.

> A otimização consome créditos, tal como qualquer outra operação de IA na sua conta.

---

## Atribuir um contacto a uma campanha

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

Coloca um contacto existente numa campanha e, se assim o solicitar, envia a mensagem de abertura da campanha imediatamente. Esta é a forma de enviar o modelo de WhatsApp aprovado de uma campanha para um contacto: o modelo com o qual uma campanha foi aprovada pertence a essa campanha, pelo que não aparece na biblioteca da [API de Modelos](templates.md) e não pode ser enviado através de `/whatsapp-templates/send`.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `sendOpeningMessage` | Não | `true` envia a mensagem de abertura da campanha (o modelo de WhatsApp aprovado numa campanha de WhatsApp) assim que o contacto é atribuído. O valor predefinido é `false`. |
| `triggerAIResponse` | Não | `true` permite que a IA escreva a sua própria primeira mensagem. O valor predefinido é `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 }'
```

**Resposta**

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

> **Créditos:** O envio da mensagem de abertura numa campanha de WhatsApp é cobrado como qualquer envio de modelo, com o preço definido pelo país do destinatário e pela categoria do modelo. Noutros canais, a mensagem de abertura é uma mensagem de saída normal.

---

## Direcionar uma campanha para canais de entrada

Estes endpoints gerem qual campanha responde a contactos novos e desconhecidos num canal. **Prefira Pontos de Entrada** para novas integrações (consulte a nota em [Tipos de campanha](#campaign-types)) — estes continuam a ser úteis para trabalhar com campanhas que utilizam o método de encaminhamento mais antigo e para resolver conflitos de propriedade de canal entre duas campanhas de entrada.

### Atribuir uma campanha a canais de entrada

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

| Campo | Obrigatório | Descrição |
|---|---|---|
| `channels` | Sim | Matriz de canais para os quais esta campanha deve responder a contactos novos e desconhecidos. |

```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"] }'
```

**Resposta**

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

`channels` lista apenas os canais que foram efetivamente encaminhados para esta campanha; `failed` lista todos os que não foram. Se todos os canais solicitados falharem, o próprio pedido falha.

### Limpar o encaminhamento de entrada de uma campanha

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

| Campo | Obrigatório | Descrição |
|---|---|---|
| `channelToUnassign` | Não | Limpar o encaminhamento apenas para este canal. Omitir para limpar todos os canais aos quais esta campanha responde atualmente. |

```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" }'
```

**Resposta**

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

### Reativar uma campanha inativa

`POST /campaigns/{campaignId}/reactivate`

Recupera uma campanha de `Ended`, `Completed`, `Paused` ou `Draft` e reclama os seus canais. Só funciona em campanhas `Incoming from Unknown Contacts` ou `Combined` — uma campanha que já esteja `Live` é tratada como um sucesso sem nada a fazer.

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

**Resposta**

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

Um canal já reclamado pelo agente de uma campanha diferente aparece em `channelsBlockedByConflict` em vez de fazer falhar toda a chamada — utilize [parar uma campanha recebida em conflito](#stop-a-conflicting-incoming-campaign) abaixo para o libertar primeiro, se quiser que esta campanha o assuma. É devolvido um `400` para um tipo de campanha que não suporta reativação, ou um estado que não seja um dos estados inativos acima.

### Parar uma campanha recebida em conflito

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

Liberta os canais desta campanha de qualquer OUTRA campanha que os detenha atualmente, para que esta campanha os possa reclamar a seguir. Esta é a versão REST do que o painel de controlo faz automaticamente quando inicia uma campanha recebida num canal que outra pessoa já está a atender.

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

**Resposta**

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

`released_channels` devolve vazio quando esta campanha já possui todos os canais que anuncia — não há nada para assumir.

---

## Estimativas de custos

Estime quanto custará lançar uma campanha antes de a enviar.

### Estimativa de custo de modelo de WhatsApp

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

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

**Resposta**

```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` é `"credits"` na via de WhatsApp gerida. Numa via onde a Meta fatura diretamente a sua própria Conta WhatsApp Business, `costPerContact`, `subtotal` e `totalTemplateCost` devolvem `null` — nunca `0`, o que seria lido como gratuito — uma vez que não existe um valor de crédito a reportar.

### Estimativa de custo de SMS

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

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

**Resposta**

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

O SMS é sempre enviado através da sua própria conta Twilio (ver [fornecedor de SMS](../settings/sms-provider.md)), pelo que é sempre faturado diretamente pela Twilio — `estimatedCostUsd` é uma estimativa dessa fatura da Twilio, não uma cobrança de crédito.

---

## Verificações de limites

Verifique um limite antes de iniciar, em vez de descobrir através de um envio falhado.

### Verificações ao nível da campanha

`GET /campaigns/{campaignId}/limits/ai-credit-messaging` — se iniciar ou agendar esta campanha excederia o limite de mensagens de créditos de IA da sua conta.

`GET /campaigns/{campaignId}/limits/messaging` — se excederia o limite diário de mensagens da sua conta.

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

**Resposta** (limite não excedido)

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

É devolvido um `400` em vez disso quando o limite é excedido, com o motivo em `error`.

### Verificações ao nível da conta

`GET /campaigns/limits/campaigns` — se atingiu o limite mensal de criação de campanhas da sua subscrição.

`GET /campaigns/limits/contacts` — se atingiu o limite de contactos da sua subscrição.

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

**Resposta**

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

---

## Totais de estatísticas de campanha

`GET /campaigns/stats/totals`

Totais de envios e respostas para cada campanha E cada agente de IA na sua conta, durante um período recente — os mesmos números que a página da lista de campanhas mostra junto a cada linha, numa única chamada em vez de um pedido por campanha.

| Parâmetro de consulta | Descrição |
|---|---|
| `days` | Tamanho do período recente, 1-365. O padrão é 90. |

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

**Resposta**

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

`byAgent` é o seu próprio resumo, não uma soma de `byCampaign` — o tráfego de uma conta nativa de Agente de IA pode não ter qualquer campanha associada, pelo que, de outra forma, seria invisível aqui.

---

## Testar uma campanha no playground

O playground permite-lhe manter uma conversa com o bot de uma campanha sem tocar num canal real ou num contacto real. É o mesmo ambiente de testes (sandbox) que o painel de experimentação do dashboard e está totalmente disponível através da API.

O fluxo é: criar um contacto de teste oculto, enviar uma mensagem e, em seguida, consultar a campanha para obter a resposta do bot. As respostas são geradas de forma assíncrona, pelo que chegam em `test_messages` na campanha e não no corpo da resposta.

> **O Playground é executado com base nos créditos de custo da API.** Uma conversa de teste iniciada com uma chave de API é cobrada à taxa normal de mensagens de IA, tal como uma resposta real, e aparece no seu histórico de utilização como uma entrada regular. Os testes a partir do painel de controlo permanecem gratuitos. A diferença é intencional: um teste executa o mesmo trabalho de IA que um em direto, pelo que um playground de API sem contagem seria uma forma de executar IA ilimitada à custa de terceiros.

### Passo 1 - Criar o contacto de teste

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

Cria o contacto de teste oculto e associa-o à campanha. Todos os campos do corpo são opcionais; qualquer campo que não preencha utilizará uma identidade de exemplo predefinida (John Doe).

| Campo | Obrigatório | Descrição |
|---|---|---|
| `first_name` | Não | Nome próprio do contacto de teste. |
| `last_name` | Não | Apelido do contacto de teste. |
| `email` | Não | E-mail do contacto de teste. |
| `phone` | Não | Número de telefone do contacto de teste. |

```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" }'
```

**Resposta**

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

### Passo 2 - Registar a mensagem recebida

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

Anexa mensagens ao tópico de teste. Envie primeiro a mensagem do visitante aqui, para que apareça no histórico da conversa que o bot lê.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `messages` | Sim | Matriz de objetos de mensagem, máximo de 200 por pedido. |
| `messages[].body` | Sim | O texto da mensagem. |
| `messages[].direction` | Sim | `"inbound"` para o visitante, `"outbound"` para o bot. |
| `messages[].timestamp` | Não | Cadeia ISO-8601 ou milissegundos da época. |
| `messages[].role` | Não | Etiqueta de função opcional. |
| `messages[].name` | Não | Nome de visualização opcional. |
| `ignoreCounter` | Não | Inteiro. Repõe o contador de ignorar da campanha na mesma escrita. |

```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"
      }
    ]
  }'
```

**Resposta**

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

### Passo 3 - Pedir ao bot para responder

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

Envia a mensagem para o pipeline de IA. Esta é a chamada que efetivamente produz uma resposta do bot.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `message` | Sim | O texto da última mensagem do 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?" }'
```

**Resposta**

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

`"Published"` significa que a mensagem foi enviada para o pipeline de IA. `"Ignored"` significa que uma mensagem de teste mais recente substituiu esta — o playground agrupa uma sequência rápida numa única resposta, cerca de quatro segundos após a última mensagem, da mesma forma que uma conversa real espera que alguém termine de escrever. Devido a essa janela de agrupamento, esta chamada demora alguns segundos a responder.

### Passo 4 - Ler a resposta

`GET /campaigns/{campaignId}`

A resposta do bot é anexada à matriz `test_messages` da campanha. Consulte a campanha até que apareça uma nova 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" }
    ]
  }
}
```

### Repor o playground

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

Limpa todo o ambiente de teste (sandbox): elimina o contacto de teste, apaga `test_messages` e liberta os bloqueios de resposta do bot. Utilize isto entre execuções de teste.

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

**Resposta**

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

### Outros endpoints do playground

| Endpoint | O que faz |
|---|---|
| `DELETE /campaigns/{campaignId}/try-out/contact` | Elimina apenas o contacto de teste atual e desassocia-o, mantendo `test_messages` intacto. É bem-sucedido mesmo quando não existe nenhum contacto associado. |
| `POST /campaigns/{campaignId}/try-out/transfer` | Inicia um playground novo com uma conversação existente, num único pedido: substitui o contacto de teste e substitui `test_messages`. O corpo aceita `first_name`, `last_name`, `messages` (pode estar vazio) e `ignoreCounter`. Prefira este método em vez de eliminar-depois-criar-depois-anexar, que triplica o consumo do seu limite de taxa. |
| `POST /campaigns/{campaignId}/try-out/messages/replace` | Substitui `test_messages` na totalidade em vez de anexar. Utilize para truncar ou retroceder uma conversa. |
| `POST /campaigns/{campaignId}/try-out/contact/reset-ignore-counter` | Repõe apenas o contador de ignorar do contacto de teste, para fluxos de refazer e repetir após um envio. |

---

## Erros da API de Campanhas

Os endpoints de campanhas devolvem o envelope de erro padrão:

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

| Estado | Quando ocorre num endpoint de campanha |
|---|---|
| `400` | Um campo obrigatório está em falta ou é inválido (por exemplo, um `type` incorreto, um `enabled` não booleano ou uma chave de dia da semana desconhecida). Também devolvido por um endpoint de [verificação de limite](#limit-checks) quando o limite seria excedido, e por [reativar](#reactivate-a-dormant-campaign) para um tipo ou estado de campanha que não o suporta. |
| `404` | A campanha não foi encontrada — ou não existe ou pertence a outra conta. |
| `409` | Uma [otimização](#optimize-a-campaign-with-ai) já está a ser executada para esta campanha. |

Os códigos partilhados que qualquer endpoint pode devolver — `401`, `403` (o seu plano não inclui acesso à API), `429` (limite de taxa) e `500` — estão listados com orientações de repetição em [Erros e Paginação](errors-and-pagination.md).

---

## Relacionado

- [Encaminhar um canal para uma campanha](channels.md#route-a-channel-to-a-campaign) — direcione o Instagram, WhatsApp ou qualquer outro canal para o Agente de IA que deve responder, utilizando Pontos de Entrada.
- [Gerar modelos de seguimento com IA](templates.md#generate-follow-up-templates-with-ai) — inicie uma tarefa em segundo plano que escreve os modelos de seguimento de WhatsApp de uma campanha.
- [API de FAQs](faqs.md) — gira as entradas de perguntas e respostas que as suas campanhas utilizam.
- [Acesso à API](../integrations/api-access.md) — gere a sua chave de API.
- [Autenticação](authentication.md) — todas as formas de transmitir a sua chave.
