# Fiwano — Documentação da API

REST API unificada para WhatsApp, Instagram e Facebook Messenger.

| | |
|---|---|
| **Base URL** | `https://fiwano.com/api/v1` |
| **Formato** | JSON |
| **Auth** | Header `X-API-Key` |

---

## Conteúdo

A documentação completa da API Fiwano em um único arquivo. As seções abaixo aparecem nesta ordem.

1. **Autenticação** — Chaves de API e o header X-API-Key.
2. **Erros** — Formato de erro e códigos de status HTTP.
3. **Início Rápido** — Do cadastro à sua primeira mensagem enviada e recebida em cinco minutos.
4. **Canais** — Conecte, gerencie e reconecte canais de WhatsApp, Instagram e Messenger.
5. **Enviando Mensagens** — Envie mensagens de texto, mídia e modelos do WhatsApp.
6. **Recebendo Mensagens** — Receba mensagens e status, baixe mídia e consulte perfis de remetentes.
7. **Modelos do WhatsApp** — Crie, gerencie e revise modelos de mensagem do WhatsApp.
8. **Capacidades e Limites** — Capacidades por canal, planos de licença, limites de taxa, janelas de mensagem e limites de mídia.
9. **Assinaturas e Cobrança** — Leia o estado de assinatura de um canal e o ciclo de cobrança.
10. **Integração n8n** — Node comunitário verificado do n8n — nodes, operações e eventos.
11. **Referência da API (OpenAPI)** — O contrato completo e legível por máquina da API pública /api/v1, gerado a partir do serviço em produção.

---

## Autenticação

Todas as requisições à API exigem uma chave de API no header `X-API-Key`.

Crie uma chave na página **API Keys** do portal. A chave completa é exibida **apenas uma vez** — guarde-a com segurança. Chaves perdidas não podem ser recuperadas; revogue e crie uma nova.

```bash
curl https://fiwano.com/api/v1/channels \
  -H "X-API-Key: YOUR_API_KEY"
```

Todas as chaves começam com `mip_live_`. As chaves são armazenadas como hash do nosso lado.

---

## Erros

Toda resposta de erro usa o mesmo formato — um campo `detail` com uma descrição legível:

```json
{ "detail": "Human-readable error description" }
```

Alguns erros de validação trazem, em vez disso, um objeto `detail` estruturado (por exemplo `text_too_long` ao enviar um texto longo demais); os formatos por endpoint estão na [Referência da API](/documentation/api).

### Códigos de status HTTP

| Código | Significado | O que fazer |
|---|---|---|
| `200` | Sucesso | — |
| `201` | Criado | — |
| `400` | Requisição inválida | Verifique o campo `detail` |
| `401` | Não autorizado | Verifique seu header `X-API-Key` |
| `402` | Pagamento necessário | Período de teste encerrado ou assinatura inativa — veja [Assinaturas e Cobrança](/br/documentation/subscriptions) |
| `404` | Não encontrado | O recurso não existe ou pertence a outra conta |
| `429` | Limite de taxa excedido | Reduza o ritmo e tente novamente após `Retry-After` — veja [limites de taxa](/br/documentation/capabilities#rate-limits) |
| `502` | Erro da API da Meta | Falha no upstream. Verifique `detail`. Tentar novamente pode ajudar. |
| `503` | Sobrecarregado temporariamente | Descarte de carga transitório. Tente novamente após `Retry-After`. |

---

## Início Rápido

A Fiwano coloca WhatsApp, Instagram e Messenger atrás de uma única REST API. Este
é o ciclo central em **quatro passos** — autenticar, conectar um canal, receber uma
mensagem, responder — mais um quinto opcional para enviar mensagens fora da janela
de 24 horas. Toda requisição usa a base URL `https://fiwano.com` e leva sua chave no
header `X-API-Key`.

### 1. Obtenha e verifique sua chave de API

Toda conta nova ganha um **período de teste gratuito de 7 dias com funcionalidade
completa** — todos os tipos de canal, mídia e modelos, sem necessidade de cartão.
Abra **API Keys** no [portal](https://fiwano.com) e crie uma chave: a chave completa
é exibida **apenas uma vez**, começa com `mip_live_` e é armazenada somente como
hash — chaves perdidas não podem ser recuperadas, então revogue e recrie se preciso.
Guarde-a em uma variável de ambiente (ex.: `FIWANO_API_KEY`); nunca a deixe fixa no
código nem a comite.

**Verifique se a chave funciona** listando os canais:

```bash
curl https://fiwano.com/api/v1/channels -H "X-API-Key: $FIWANO_API_KEY"
```

Uma chave válida em uma conta nova (ainda sem canais) retorna **`200`** com uma
lista vazia — esse é o sinal de sucesso de que você está autenticado e pronto para
o passo 2:

```json
{ "channels": [], "total": 0 }
```

Uma chave inválida ou ausente retorna `401`. Formatos de erro e códigos de status:
[Erros](/br/documentation/errors).

### 2. Conecte um canal

Há **duas formas de conectar** — escolha a que combina com quem é o dono da conta,
ambas detalhadas em [Canais](/br/documentation/channels):

- **Seu próprio canal** — conecte-o no [portal](https://fiwano.com)
  (Channels → Connect), sem código. Melhor quando você mesmo opera as contas. Os
  pré-requisitos (o ativo deve pertencer a um Meta Business Portfolio, e você deve
  ser admin dele) estão detalhados lá.
- **Os canais dos seus usuários finais** — um fluxo OAuth incorporado que seu app
  conduz: cadastre um `redirect_uri` na whitelist, crie uma setup URL, o usuário
  conclui o login da Meta dentro dela, e você troca o `code` retornado (de uso
  único) por um `channel_id`.

Operações OpenAPI deste passo:

- Gerenciar canais: `GET /api/v1/channels`, `GET /api/v1/channels/{channel_id}`, `PATCH /api/v1/channels/{channel_id}`, `DELETE /api/v1/channels/{channel_id}`
- Fluxo de conexão incorporado: `POST /api/v1/channels/setup-url`, `POST /api/v1/channels/exchange-code`
- Whitelist de redirect URI: `GET /api/v1/redirects`, `POST /api/v1/redirects`, `DELETE /api/v1/redirects/{redirect_id}`

**Sucesso:** você tem um `channel_id` (o fluxo incorporado o retorna direto do
`exchange-code`), e `GET /api/v1/channels` agora lista o canal com
`"is_active": true` — ele pode enviar e receber. Esse `channel_id` é o que você
passa em toda chamada de envio e recebimento daqui em diante.

### 3. Receba uma mensagem

Responder mensagens recebidas é o caso de uso central da Fiwano, então configure o
recebimento **antes** do envio. Duas partes:

**1. Ative os eventos no canal.** A entrega é opt-in — **por padrão nenhum evento é
entregue**. Defina `webhook_events` (e uma `webhook_url`) no canal e ative apenas os
eventos que você realmente trata (comece por `message.received`). A lista de eventos
por canal e como configurá-la estão em [Canais](/br/documentation/channels).

**2. Trate o webhook.** A Fiwano envia um POST de cada evento ativado para a sua
`webhook_url`. Seu endpoint **deve verificar a `X-Webhook-Signature`** (HMAC-SHA256
com o `webhook_secret` do canal) e **responder HTTP 2xx em ~5 segundos** — caso
contrário a Fiwano tenta novamente com backoff e envia um e-mail. Os formatos de
payload e a verificação de assinatura estão em
[Recebendo Mensagens](/br/documentation/webhooks).

Um `message.received` recebido carrega os dois identificadores que você precisa para
responder (destacados abaixo):

```jsonc
{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",   // ← qual dos seus canais o recebeu
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",              // ← quem enviou — responda para este
    "from_name": "John Doe",
    "type": "text",
    "text": "Hello!"
  }
}
```

- **`channel_id`** (nível superior) — o canal em que a mensagem chegou.
- **`data.from`** — o id do remetente: número de telefone (WhatsApp), IGSID (Instagram)
  ou PSID (Facebook). É exatamente o que você passa de volta como `recipient`.

A partir de um handler, você também costuma chamar:

- `PATCH /api/v1/channels/{channel_id}` — define ou atualiza `webhook_events` / `webhook_url`
- `GET /api/v1/media/{media_id}` — baixa a mídia recebida
- `GET /api/v1/channels/{channel_id}/profile/{user_id}` — consulta o perfil do remetente

**Sucesso:** mande uma mensagem para o seu canal conectado de um aparelho real; seu
endpoint recebe um webhook `message.received` com assinatura válida e retorna 2xx.
Você já está recebendo.

### 4. Responda a ela

Com o recebimento no lugar, faça o envio. O caso do dia a dia é uma **resposta em
formato livre dentro da janela de 24 horas** depois que um usuário te escreve —
texto ou mídia, sem aprovação. Este é o movimento central: responda ao remetente
devolvendo os **mesmos** identificadores — o `channel_id` do webhook como
`channel_id`, e `data.from` como `recipient`:

```bash
curl -X POST https://fiwano.com/api/v1/messages/send \
  -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"channel_id": "a1b2c3d4e5f67890", "recipient": "1234567890", "text": "Thanks for your message!"}'
```

- Enviar: `POST /api/v1/messages/send` (texto), `POST /api/v1/messages/send-media` (mídia)

**Sucesso:** a chamada retorna uma mensagem aceita com um `message_id`; se você
ativou os eventos de entrega no passo 3, você então recebe os webhooks
`message.sent` / `message.delivered` acompanhando-a. Leia
[Enviando Mensagens](/br/documentation/sending-messages) para mídia e mais.

Isso cobre o ciclo central — conectar, receber, responder. O passo 5 é opcional.

### 5. Modelos do WhatsApp — mensagens fora da janela de 24 horas (opcional)

Mensagens em formato livre só alcançam um usuário **dentro** da janela de 24 horas.
Para iniciar uma conversa, ou para responder depois que a janela fechou, o WhatsApp
exige um **modelo** (template) pré-aprovado (somente WhatsApp). Pule este passo se
você sempre responde dentro da janela — veja a janela de 24 horas em
[Capacidades](/br/documentation/capabilities#messaging-windows-24h).

Leia [Modelos do WhatsApp](/br/documentation/templates) para o ciclo de criação/revisão
e então envie o modelo aprovado.

- Enviar um modelo: `POST /api/v1/messages/send-template`
- Gerenciar modelos: `GET /api/v1/channels/{channel_id}/templates`, `POST /api/v1/channels/{channel_id}/templates`, `GET|PUT|DELETE /api/v1/channels/{channel_id}/templates/{template_id}`

### Próximos passos

- **Sem código?** Use o [node n8n](/br/documentation/n8n) verificado — mesmos canais,
  mesmos eventos, arrastar e soltar.
- **Mídia e modelos** — [Enviando Mensagens](/br/documentation/sending-messages).
- **Limites, janelas e planos** — [Capacidades](/br/documentation/capabilities).
- **O contrato completo legível por máquina** — [Referência da API](/documentation/api).
</content>

---

## Canais

Um **canal** é um ativo Meta conectado — um número de WhatsApp, uma conta do Instagram
ou uma Página do Facebook — pelo qual você envia e recebe mensagens. Esta página
cobre como conectar, gerenciar e reconectar canais.

Para o schema exato de request/response de cada endpoint de canal (campos, tipos,
códigos de status), veja a **[Referência da API](/documentation/api)**. Esta página é o
guia em nível de tarefa; ela não repete as tabelas de campos.

### Pré-requisitos

Antes de conectar qualquer canal — WhatsApp, Instagram ou Facebook Messenger — garanta
que ambas as condições abaixo sejam atendidas. Elas valem igualmente para o fluxo do
Portal e o fluxo da API; se alguma faltar, a Meta interrompe o popup de OAuth antes que um
canal possa ser criado.

- **O ativo pertence a um Meta Business Portfolio** (Business Manager). O
  "ativo" é a WABA do número de WhatsApp, a Página do Facebook ou — no caso do Instagram —
  uma conta do Instagram Business ou Creator vinculada a uma Página do Facebook que seja
  de propriedade de um Business Portfolio.
- **O usuário do Facebook que faz login tem direitos completos de admin** nesse Business
  Portfolio e no próprio ativo. Um usuário sem papel de admin vê a opção relevante
  desabilitada no popup.

### Opção A: Via Portal (self-service)

Use isto para conectar **seus próprios** canais, sem necessidade de código.

1. Vá em **Channels → Connect Channel** no portal.
2. Selecione o tipo de canal (WhatsApp, Instagram ou Facebook Messenger).
3. Conclua o fluxo de OAuth da Meta na janela popup.
4. Configure a **Webhook URL** e selecione os **Webhook Events** nas configurações do canal.
5. Por padrão, nenhum evento está habilitado — selecione quais eventos encaminhar ao seu endpoint.

Definir uma webhook URL no Portal **não** cria um `webhook_secret`. Defina um
explicitamente para que as entregas recebidas sejam assinadas — veja
[Segredo do webhook](#webhook-secret).

### Opção B: Via API (programática)

Use isto quando sua aplicação conecta canais **em nome dos seus usuários finais**.

**Passo 1 — Cadastre seu redirect URI na whitelist.** Por segurança, o usuário só pode ser
redirecionado de volta a uma URL que você tenha pré-registrado para a sua chave de API.
Registre a(s) URL(s) onde os usuários chegam após o OAuth (coringas são permitidos, ex.:
`https://*.example.com/callback`):

```bash
curl -X POST https://fiwano.com/api/v1/redirects \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"uri_pattern": "https://yourapp.com/callback"}'
```

Você os gerencia com `GET /api/v1/redirects` e `DELETE /api/v1/redirects/{id}`.

**Passo 2 — Solicite uma setup URL.** Passe um dos seus redirect URIs da whitelist. A
URL é válida até o `expires_at` retornado na resposta — abra-a em um navegador ou popup
para o usuário:

```bash
curl -X POST https://fiwano.com/api/v1/channels/setup-url \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"channel_type": "whatsapp", "redirect_uri": "https://yourapp.com/callback"}'
```

**Passo 3 — O usuário conclui o OAuth da Meta.** Após a aprovação, o usuário é
redirecionado ao seu `redirect_uri` com um parâmetro `code` de uso único:

```
https://yourapp.com/callback?code=abc123...
```

Em caso de falha, o redirect leva dois query params — ramifique sua lógica apenas no
`error`:

| Query param | Como usar |
|---|---|
| `error` | Código legível por máquina. **Ramifique nisto.** `access_denied` — o usuário cancelou o diálogo da Meta. `setup_failed` — o setup não pôde ser concluído (ex.: nenhuma conta do Instagram Business estava acessível com as permissões concedidas). |
| `message` | Explicação legível por humanos em inglês, codificada em URL, segura para exibir ao usuário. **Formato livre e pode mudar — nunca faça parse ou ramifique no texto dela.** |

Exemplo de redirect de falha:

```
https://yourapp.com/callback?error=setup_failed&message=We%20couldn%27t%20access%20any%20Instagram%20Business%20account...
```

**Passo 4 — Troque o code.** Em até 5 minutos (uso único), troque o code
pelo canal. Você pode configurar o webhook na mesma chamada:

```bash
curl -X POST https://fiwano.com/api/v1/channels/exchange-code \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "abc123...",
    "webhook_url": "https://yourapp.com/webhooks/meta",
    "webhook_events": ["message.received", "message.delivered", "message.failed"]
  }'
```

A resposta retorna seu `channel_id` (guarde-o — toda outra chamada o utiliza). Quando
você define `webhook_url` e não fornece um `webhook_secret`, a Fiwano **gera um
automaticamente** e o retorna aqui. Ele é retornado **apenas nesta resposta** — o `GET`
nunca o mostra de novo — então guarde-o para verificar as assinaturas dos webhooks
([Segredo do webhook](#webhook-secret)). Todos os campos exceto `code` são opcionais e
podem ser definidos depois via `PATCH /api/v1/channels/{id}`.

### Eventos de webhook

A entrega de webhooks é **opt-in por canal**: por padrão **nenhum evento é entregue**.
Você escolhe o que recebe definindo `webhook_events` — na chamada de conexão, no Portal,
ou depois via `PATCH /api/v1/channels/{id}`. Até fazer isso, seu endpoint não recebe nada.

Os eventos disponíveis dependem do tipo de canal — o WhatsApp expõe mais
(`message.sent`, `message.failed`) do que Instagram e Facebook. A lista completa com os
payloads está na página de **[Webhooks](/br/documentation/webhooks#event-types)**. Um evento
que você listar e que não seja válido para o tipo de canal é simplesmente ignorado, não é
um erro.

**Seu endpoint cumpre a outra metade desse contrato.** Uma vez habilitados os eventos, a
Fiwano faz POST de cada um para seu `webhook_url`, e seu endpoint **precisa responder com
HTTP 2xx em até ~5 segundos**. Uma resposta não-2xx ou um timeout conta como entrega
falha: a Fiwano **tenta novamente com backoff e envia e-mail para você** — um aviso após a
3ª tentativa falha e um alerta quando as tentativas se esgotam. Por isso, habilite
**apenas os eventos que você realmente trata** e retorne 2xx assim que aceitar o payload
(faça o trabalho mais lento depois). Mensagens recebidas são salvas do nosso lado mesmo se
o relay falhar. Comportamento completo de entrega e retentativas:
**[Webhooks → Política de novas tentativas](/br/documentation/webhooks#retry-policy)**.

### Segredo do webhook

O `webhook_secret` é a chave HMAC que a Fiwano usa para **assinar as entregas de
webhook**, para que seu endpoint possa confirmar que a requisição realmente veio da
Fiwano e não foi alterada no caminho. Quando um canal tem um segredo, cada entrega traz
um cabeçalho `X-Webhook-Signature: sha256=<hmac>` — veja
**[Webhooks](/br/documentation/webhooks)** para o trecho de verificação. Um canal sem
segredo recebe entregas **sem assinatura**.

Como um segredo aparece pela primeira vez difere conforme você conecta — e este é o
único ponto em que o Portal e a API se comportam propositalmente de forma diferente:

- **Portal (Opção A):** um canal novo **não tem segredo**, e salvar uma webhook URL não
  cria um. Defina-o você mesmo nas configurações do canal: clique em **Generate random**
  para um segredo aleatório de 64 caracteres, ou digite o seu próprio e clique em
  **Save** (mínimo de 16 caracteres). O valor é revelado **uma única vez**, logo em
  seguida.
- **API (Opção B):** quando você define `webhook_url` e o canal ainda não tem segredo, a
  Fiwano **gera um automaticamente** (hex de 64 caracteres) e o retorna na resposta de
  `exchange-code` / `PATCH /api/v1/channels/{id}` — ou seja, canais conectados via API
  são **assinados por padrão**. Para usar um valor específico, forneça o seu próprio
  `webhook_secret` nessa mesma chamada.

**Lendo de volta.** O valor só é retornado no momento em que é definido ou alterado —
na revelação única do Portal, ou nas respostas de `exchange-code` e
`PATCH /api/v1/channels/{id}`. `GET /api/v1/channels` e `GET /api/v1/channels/{id}`
nunca o retornam; eles apenas informam `has_webhook_secret: true | false`. **Guarde o
valor quando ele for exibido** — se você o perder, a única opção é definir um novo.

**Rotacionando.** Defina um novo segredo a qualquer momento fornecendo um novo
`webhook_secret` ao `PATCH /api/v1/channels/{id}`, ou com as ações **Generate random** /
**Save** do Portal. Atualizar apenas `webhook_url`/`webhook_events` mantém o segredo
intacto. A mudança tem efeito na **próxima entrega** — não há janela de sobreposição,
então troque seu verificador para o novo segredo no mesmo momento, ou as assinaturas não
vão corresponder.

**Restrições e recomendações.**

- Use uma string aleatória de alta entropia com **16–64 caracteres** (o Portal exige
  o mínimo de 16 caracteres; o campo armazena até 64). Segredos gerados
  automaticamente têm 64 caracteres hex — prefira-os, a menos que tenha um motivo para
  usar o seu próprio.
- O segredo é **por canal** — cada canal tem o seu, independente dos demais.
- Reconectar um canal inativo **mantém** o segredo existente (veja
  [Reconectando um canal inativo](#reconectando-um-canal-inativo) abaixo).
- Trate-o como uma senha: armazene-o em um gerenciador de segredos, nunca o inclua no
  controle de versão e verifique as assinaturas usando comparação de tempo constante
  (como no exemplo de Webhooks).

### Gerenciando canais

| Tarefa | Endpoint |
|---|---|
| Listar todos os canais (ativos e inativos), cada um com seu estado de assinatura atual | `GET /api/v1/channels` |
| Inspecionar um canal | `GET /api/v1/channels/{id}` |
| Atualizar webhook URL / secret / events | `PATCH /api/v1/channels/{id}` |
| Desativar um canal | `DELETE /api/v1/channels/{id}` |

Cada canal traz um bloco `subscription` descrevendo seu estado de cobrança — veja
**[Assinaturas e Cobrança](/br/documentation/subscriptions)** para o que as
combinações significam. As listas completas de campos estão na **[Referência da API](/documentation/api)**.

**A desativação é um soft delete.** O `DELETE` impede o canal de enviar e
receber, mas não o apaga — seu `channel_id` e histórico são preservados para que
você possa reconectar depois. O Fiwano também cancela a inscrição do recurso de webhook da
Meta do canal apenas quando é seguro: uma inscrição de WABA é mantida se outro canal de
WhatsApp ativo usar a mesma WABA, e uma inscrição de Página é mantida se outro canal de
Instagram/Facebook ativo usar a mesma Página.

### Reconectando um canal inativo

Um canal fica inativo quando é desativado (`DELETE /api/v1/channels/{id}`) ou
quando sua conexão com a Meta não pode mais ser mantida (por exemplo, o dono da conta
revogou o acesso na Meta). Para trazê-lo de volta, execute o **mesmo fluxo de conexão
novamente para a mesma conta Meta** (mesmo número de WhatsApp, conta do Instagram ou
Página do Facebook):

- O canal existente é **reativado no lugar** — seu `channel_id`, webhook
  URL/secret/events e histórico são preservados. Nenhum canal novo é criado e o seu
  mapeamento de `channel_id` armazenado continua válido.
- A reconexão exige uma **licença ativa**: o canal ainda deve ter uma, ou
  você deve ter um slot de licença livre. Caso contrário, o fluxo é recusado — vincule uma
  licença em Billing primeiro.
- Uma conta Meta que esteja atualmente ativa em outra conta Fiwano não pode ser
  reconectada (`"already connected to another account"`).

---

## Enviando Mensagens

O Fiwano tem três endpoints de envio — texto simples, mídia e modelos do WhatsApp. Todos
recebem um `channel_id` e um `recipient`. O formato do `recipient` depende do
canal (número de telefone para WhatsApp, IGSID para Instagram, PSID para Facebook) — veja
a linha de destinatário em [Capacidades](/br/documentation/capabilities#channel-capabilities). Os
schemas completos de request/response estão na [Referência da API](/documentation/api); esta
página é o guia por tarefa.

### Mensagens de texto

`POST /api/v1/messages/send` — funciona em todos os tipos de canal.

```bash
curl -X POST https://fiwano.com/api/v1/messages/send \
  -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"channel_id": "a1b2c3d4e5f67890", "recipient": "1234567890", "text": "Hello! Your order is ready."}'
```

A resposta carrega um `message_id` (um UUID do Fiwano) que todo webhook posterior de status
de entrega referencia. O texto tem um limite de tamanho por plataforma (WhatsApp 4096,
Facebook 2000, Instagram 1000) — texto acima do limite é rejeitado com `400 text_too_long`
antes de chamar a Meta. O Fiwano **não** divide automaticamente; divida do seu lado para
preservar seu próprio fatiamento e ordenação. Veja [Capacidades](/br/documentation/capabilities).

### Mensagens de mídia

`POST /api/v1/messages/send-media` — **licença Pro obrigatória.** A Meta busca o arquivo
diretamente de `media_url`; o Fiwano nunca o baixa nem o armazena. Passe `media_type`
(`image`, `audio`, `video`, `document`) e uma `media_url` HTTPS.

**Use uma URL assinada para conteúdo não público** — pré-assinada de S3/GCS/R2, SAS do
Azure ou uma URL assinada por HMAC no seu próprio servidor, com expiração ≥ 5 min. Uma URL
pública é acessível por qualquer um que a descubra.

```bash
curl -X POST https://fiwano.com/api/v1/messages/send-media \
  -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "channel_id": "a1b2c3d4e5f67890",
    "recipient": "1234567890",
    "media_type": "image",
    "media_url": "https://my-bucket.s3.amazonaws.com/photo.jpg?X-Amz-Signature=...&X-Amz-Expires=600",
    "caption": "Your order photo"
  }'
```

Sempre verifique `success`. Em caso de falha, a resposta inclui um `error_code` da Meta —
por exemplo `131052` (a Meta não conseguiu baixar a URL) ou `131053` (formato/tamanho não
suportado, ou a Meta aplicou rate limit à rede do seu host — tente um grande provedor de
nuvem). Os limites de tamanho de arquivo e a tabela completa de error codes estão em
[Capacidades](/br/documentation/capabilities) e na [Referência da API](/documentation/api).

### Mensagens de modelo

`POST /api/v1/messages/send-template` — **somente WhatsApp, Pro obrigatório.** Use um
modelo pré-aprovado para iniciar uma conversa fora da janela de 24 horas (veja
[Capacidades](/br/documentation/capabilities#messaging-windows-24h)). Apenas modelos
`APPROVED` podem ser enviados — para criá-los e gerenciá-los, veja
[Modelos do WhatsApp](/br/documentation/templates).

Forneça os valores das variáveis por componente. Modelos **posicionais** (`{{1}}`,
`{{2}}`) recebem arrays:

```bash
curl -X POST https://fiwano.com/api/v1/messages/send-template \
  -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "channel_id": "a1b2c3d4e5f67890",
    "template_name": "order_confirmation",
    "language": "en_US",
    "recipient": "1234567890",
    "variables": {
      "header": ["Summer Sale"],
      "body": ["Pablo", "ORD-123", "25%"],
      "buttons": [{"index": 0, "value": "promo25"}]
    }
  }'
```

Modelos **nomeados** (`{{customer_name}}`) recebem objetos:

```bash
  -d '{
    "channel_id": "a1b2c3d4e5f67890",
    "template_name": "welcome_message",
    "language": "en_US",
    "recipient": "1234567890",
    "variables": {"body": {"customer_name": "Pablo", "order_number": "ORD-123"}}
  }'
```

Omita `variables` por completo se o modelo não tiver nenhuma.

### Entrega e novas tentativas

`send` e `send-media` retornam `200` com um campo `status`, porque o que acontece
depois que a Meta aceita a requisição importa:

- **`sent`** — a Meta aceitou. Acompanhe o resto pelos webhooks de status de entrega
  (`message.delivered` / `read` / `failed`) — veja
  [Recebendo Mensagens](/br/documentation/webhooks#delivery-status-tracking).
- **`queued`** — uma falha transitória da Meta (rede, 5xx, rate limit). O Fiwano
  tenta novamente em segundo plano (até 7 vezes ao longo de ~20 min). Você recebe um e-mail
  de aviso antecipado após 3 tentativas falhas e um e-mail final se elas se esgotarem.
- **`failed`** (`success: false`) — um erro permanente (destinatário inválido, texto acima
  do limite, payload malformado). **Não é retentado** — corrija a requisição e reenvie. O
  dono do canal é notificado por e-mail.

Portanto `200` por si só não significa "entregue" — sempre leia `success` e `status`.

---

## Recebendo Mensagens

Quando um usuário envia mensagem ao seu canal conectado, o Fiwano entrega a mensagem — e depois seus status de entrega — à `webhook_url` do seu canal como uma requisição `POST`. Esta página cobre a verificação de webhooks, os formatos de payload por canal, o download de mídia recebida e a consulta ao perfil de um remetente.

Defina a `webhook_url` e escolha quais `webhook_events` receber ao conectar um canal (veja [Canais](/br/documentation/channels)); por padrão nenhum evento está habilitado. Se o canal tiver um [`webhook_secret`](/br/documentation/channels#webhook-secret), cada entrega é assinada para você verificar que veio do Fiwano — fortemente recomendado. Enquanto você não definir um segredo, as entregas são enviadas sem assinatura.

### Verificando assinaturas

Quando o canal tem um `webhook_secret`, toda requisição de webhook inclui um header `X-Webhook-Signature`:

```
X-Webhook-Signature: sha256=<hmac_hex>
```

Para verificar: compute o `HMAC-SHA256` do corpo bruto (raw) da requisição usando seu `webhook_secret` como chave, depois compare o digest em hex. (Se nenhum segredo estiver configurado, esse header não é enviado — defina um para habilitar a verificação.)

```python
import hmac, hashlib

def verify_signature(body: bytes, secret: str, header: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    received = header.replace("sha256=", "")
    return hmac.compare_digest(expected, received)
```

### Tipos de evento

Cada tipo de canal suporta um conjunto específico de eventos de webhook. **Somente os eventos que você habilitar explicitamente via `webhook_events` são entregues.** Por padrão, nenhum evento está habilitado — você precisa configurá-los após conectar um canal.

| Evento | Descrição | Canais |
|---|---|---|
| `message.received` | Mensagem recebida de um usuário | Todos |
| `message.sent` | Sua mensagem foi aceita pela Meta | WhatsApp |
| `message.delivered` | Mensagem entregue no dispositivo do destinatário | WhatsApp, Instagram, Facebook |
| `message.read` | Mensagem lida pelo destinatário * | WhatsApp, Instagram, Facebook |
| `message.failed` | A entrega da mensagem falhou | WhatsApp |

\* `message.read` no WhatsApp depende das configurações de privacidade do destinatário — se as confirmações de leitura estiverem desativadas, o status `read` nunca chegará. Trate `delivered` como um estado terminal de sucesso.


### Rastreamento de status de entrega

Quando você envia uma mensagem via `/api/v1/messages/send`, recebe um `message_id` (UUID). Todos os webhooks de status subsequentes referenciam esse mesmo UUID.

- `message_id` está sempre presente em todos os eventos de status — é um UUID gerado pelo Fiwano, não um ID interno da Meta.
- Progressão de status: `sent → delivered → read`. Cada status implica todos os anteriores.
- `data.recipient` é o identificador do usuário: número de telefone (WhatsApp), IGSID (Instagram) ou PSID (Facebook).
- Todos os canais usam exatamente o mesmo formato de webhook.
- **Cascata de leitura:** quando um usuário lê uma conversa, o Fiwano envia um webhook `message.read` separado para *cada* mensagem não lida — não apenas a mais recente.

### Formato do payload

Todos os payloads compartilham a mesma estrutura de nível superior:

```json
{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": { ... }
}
```

#### message.received (WhatsApp)

```json
{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "text",
    "text": "Hello!"
  }
}
```

`data.from` — número de telefone do remetente sem `+`. Use diretamente como `recipient` ao responder.

#### message.received (Instagram)

```json
{
  "event": "message.received",
  "channel_id": "b2c3d4e5f6789012",
  "channel_type": "instagram",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "mid.xxx",
    "from": "6543217890123456",
    "from_name": null,
    "type": "text",
    "text": "Hi there!"
  }
}
```

`data.from` — IGSID. Use como `recipient` ao responder. `from_name` é sempre `null` (a Meta não inclui o nome do remetente nos webhooks do IG).

#### message.received (Facebook Messenger)

```json
{
  "event": "message.received",
  "channel_id": "c3f8a1b2e4d56789",
  "channel_type": "facebook",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "mid.xxx",
    "from": "7890123456789012",
    "from_name": null,
    "type": "text",
    "text": "Hello from Messenger!"
  }
}
```

`data.from` — PSID. Use como `recipient` ao responder. `from_name` é sempre `null` (a Meta não inclui o nome do remetente nos webhooks do FB).

#### message.received — mídia (Pro)

Com uma licença **Pro**, mensagens de mídia incluem o conteúdo do arquivo. O arquivo de mídia é baixado da Meta e armazenado temporariamente. Use o `download_url` para buscar o arquivo antes que ele expire.

Exemplo de imagem no WhatsApp:

```json
{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "image",
    "caption": "Check this photo",
    "media": {
      "media_id": "m1b2c3d4e5f67890",
      "mime_type": "image/jpeg",
      "file_size": 245760,
      "filename": null,
      "sha256": "abc123...",
      "duration_ms": null,
      "download_url": "https://fiwano.com/api/v1/media/m1b2c3d4e5f67890",
      "expires_at": "2025-01-15T11:30:00Z"
    }
  }
}
```

Exemplo de mensagem de voz no WhatsApp:

```json
{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "audio",
    "media": {
      "media_id": "m2b3c4d5e6f78901",
      "voice": true,
      "mime_type": "audio/ogg; codecs=opus",
      "file_size": 12345,
      "filename": null,
      "sha256": "def456...",
      "duration_ms": 5200,
      "download_url": "https://fiwano.com/api/v1/media/m2b3c4d5e6f78901",
      "expires_at": "2025-01-15T11:30:00Z"
    }
  }
}
```

Instagram e Facebook Messenger usam o mesmo bloco normalizado `data.media`. O remetente continua sendo `data.from` (IGSID para Instagram, PSID para Facebook), e `data.type` segue o tipo de anexo da Meta, como `"image"` ou `"audio"`. Use `data.type` como o tipo primário da mensagem e como o `media_type` de saída ao ecoar ou encaminhar mídia.

O Fiwano preserva o formato de arquivo original da Meta e não faz transcodificação de mídia. `media.mime_type` descreve os bytes do arquivo baixado, não a semântica da mensagem. Por exemplo, clipes em estilo de voz do Facebook Messenger costumam baixar como OGG/Opus (`audio/ogg`), enquanto mensagens de áudio do Instagram podem baixar como MP4 somente-áudio servido com `video/mp4`. Em ambos os casos o tipo da mensagem ainda é `data.type: "audio"`.

Apenas para WhatsApp recebido, a Meta fornece um indicador confiável de mensagem de voz. O Fiwano o expõe como `media.voice: true` quando presente. Instagram e Facebook Messenger não expõem um indicador de voz confiável equivalente pelo payload do webhook, então `media.voice` é omitido para esses canais.

O `download_url` é autenticado; busque-o com sua `X-API-Key`. Não o passe diretamente como `media_url` de saída, porque a Meta não enviará o header da sua API key — re-hospede os bytes atrás de uma URL HTTPS pública ou assinada primeiro.

**Campos do payload de mídia:**

| Campo | Tipo | Descrição |
|---|---|---|
| `media_id` | string | ID do arquivo de mídia — use em `GET /api/v1/media/{media_id}` para baixar |
| `voice` | bool | Presente apenas para mensagens de voz do WhatsApp (`true`). Omitido para IG/FB porque a Meta não fornece um indicador de voz confiável ali. |
| `mime_type` | string | Tipo MIME (ex.: `image/jpeg`, `audio/ogg; codecs=opus`) |
| `file_size` | int | Tamanho do arquivo em bytes |
| `filename` | string\|null | Nome de arquivo original (apenas documentos) |
| `sha256` | string\|null | Hash SHA-256 da Meta (apenas WhatsApp) |
| `duration_ms` | int\|null | Duração em milissegundos (apenas áudio/vídeo) |
| `download_url` | string\|null | URL de download autenticada. `null` se o download da Meta falhou. |
| `error` | string | Presente apenas quando o download falhou — descreve o erro |
| `expires_at` | string | Timestamp ISO 8601 — o arquivo é excluído após esse horário |

> **Observação:** Trate mensagens de voz como mensagens de áudio. `data.type: "audio"` é o valor estável entre canais para roteamento e encaminhamento. `media.voice` é uma dica opcional, exclusiva do WhatsApp, para UI/UX.

#### Baixando mídia recebida

Busque o arquivo em `data.media.download_url` (que é `GET /api/v1/media/{media_id}`) com sua `X-API-Key`:

```bash
curl https://fiwano.com/api/v1/media/m1b2c3d4e5f67890 \
  -H "X-API-Key: YOUR_API_KEY" \
  --output photo.jpg
```

A resposta são os bytes brutos do arquivo com o `Content-Type` original (e um nome de arquivo em `Content-Disposition` quando conhecido). **Os arquivos expiram 60 minutos após o webhook** — baixe prontamente e re-hospede o que precisar manter; após a expiração a URL retorna `410 Gone`. Os tamanhos estão em [Capacidades](/br/documentation/capabilities#media-limits); os códigos de status na [Referência da API](/documentation/api).

#### message.received — tipo não suportado (todos os canais)

Com uma licença **Starter**, mensagens de mídia são entregues com `type: "unsupported"` e um campo `upgrade_required` indicando qual licença é necessária:

```json
{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "unsupported",
    "unsupported_type": "image",
    "upgrade_required": "pro"
  }
}
```

O campo `upgrade_required` informa qual plano de licença é necessário. Faça upgrade pela página Billing no portal para receber o conteúdo completo de mídia.

> **Observação:** Mensagens com tipos realmente não suportados (ex.: `location`, `contacts`, `reaction`) ainda chegam como `type: "unsupported"` sem `upgrade_required`, independentemente do seu plano de licença.

#### message.delivered / message.read (todos os canais)

```json
{
  "event": "message.read",
  "channel_id": "b2c3d4e5f6789012",
  "channel_type": "instagram",
  "timestamp": "2025-01-15T10:30:10Z",
  "data": {
    "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "read",
    "recipient": "6543217890123456"
  }
}
```

Mesmo formato para todos os canais e todos os status (`sent`, `delivered`, `read`). `message_id` é o UUID da resposta de envio.

#### message.failed (WhatsApp)

```json
{
  "event": "message.failed",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:05Z",
  "data": {
    "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "recipient": "1234567890",
    "status": "failed",
    "error": "Message undeliverable",
    "errors": [{"code": 131047, "title": "Message undeliverable"}]
  }
}
```

### Política de novas tentativas

Se sua webhook URL retornar um status não-2xx ou estiver inacessível, o sistema tenta novamente automaticamente:

- **7 tentativas** com backoff exponencial: 30s, 1m, 2m, 2m, 2m, 2m, 2m (~12 minutos no total)
- **Prazo rígido de 20 minutos** — após o qual a entrega é marcada como permanentemente falha
- **E-mail de aviso** enviado após a 3ª tentativa falha (com as novas tentativas ainda em andamento)
- **Alerta por e-mail** enviado quando todas as tentativas se esgotam (falha permanente)
- Os payloads são criptografados em repouso durante as novas tentativas e apagados após a entrega ou expiração

**Importante:** Seu endpoint **deve responder com HTTP 2xx em até 5 segundos**. Respostas não-2xx ou timeouts disparam a fila de novas tentativas. Mensagens recebidas da Meta são sempre salvas do nosso lado, mesmo que o repasse falhe.

> **Dica:** Habilite apenas os eventos de webhook que você de fato trata. Eventos não tratados que recebem respostas não-2xx vão encher sua fila de novas tentativas desnecessariamente.

### Perfil do remetente

O WhatsApp inclui o nome do remetente em cada webhook (`data.from_name`) — nenhuma chamada extra necessária. Instagram e Facebook **não** (`data.from_name` é sempre `null`); para obter um nome ou avatar, chame o endpoint de perfil:

```
GET /api/v1/channels/{channel_id}/profile/{user_id}
```

Passe o valor de `data.from` (IGSID para Instagram, PSID para Facebook) como `user_id`. Ele retorna:

- **Instagram** — `username`, `name`, `profile_pic`, `follower_count`, `is_verified_user`
- **Facebook** — `first_name`, `last_name`, `profile_pic`

WhatsApp não é suportado (o nome já está no webhook). Os resultados são cacheados por 5 minutos — a flag `cached` da resposta indica se foi um acerto de cache. Request/response completos e códigos de status estão na [Referência da API](/documentation/api).

> **Dica:** chame isto uma vez quando você vir um novo `data.from` pela primeira vez, depois cacheie o resultado do seu lado — não há necessidade de chamar a cada mensagem.

---

## Modelos do WhatsApp

O WhatsApp exige **modelos pré-aprovados** para iniciar uma conversa fora da
janela de 24 horas (veja [Capacidades](/br/documentation/capabilities#messaging-windows-24h)).
Modelos são exclusivos do WhatsApp e exigem uma **licença Pro**. Esta página trata de
gerenciá-los; para *enviar* um modelo aprovado, veja
[Enviando → Mensagens de modelo](/br/documentation/sending-messages#template-messages).

### Ciclo de vida

```
Create → PENDING (revisão da Meta, ~24h) → APPROVED (pronto para envio)
                                          → REJECTED (corrija e reenvie)
```

Os modelos pertencem à WhatsApp Business Account (WABA) do canal. Gerencie-os
por estes endpoints — os schemas completos de request/response estão na
[Referência da API](/documentation/api):

| Tarefa | Endpoint |
|---|---|
| Listar (filtra por status; sincroniza da Meta por padrão) | `GET /api/v1/channels/{id}/templates` |
| Obter um (componentes + definições de variáveis) | `GET /api/v1/channels/{id}/templates/{template_id}` |
| Criar (→ enviado à Meta, começa em `PENDING`) | `POST /api/v1/channels/{id}/templates` |
| Atualizar componentes | `PUT /api/v1/channels/{id}/templates/{template_id}` |
| Excluir | `DELETE /api/v1/channels/{id}/templates/{template_id}` |

### Criando um modelo

Um modelo é um `name` + `category` (`MARKETING`, `UTILITY` ou `AUTHENTICATION`)
+ `language` + `components`. `BODY` é obrigatório; `HEADER` (somente texto), `FOOTER` e
`BUTTONS` são opcionais. As variáveis são `{{1}}, {{2}}` (posicionais) ou `{{name}}`
(nomeadas) — a Meta exige valores de `example` para a revisão.

```bash
curl -X POST https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890/templates \
  -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "order_confirmation",
    "category": "UTILITY",
    "language": "en_US",
    "components": [
      {"type": "BODY", "text": "Hi {{1}}, your order {{2}} is confirmed.",
       "example": {"body_text": [["Pablo", "ORD-123"]]}}
    ],
    "parameter_format": "positional"
  }'
```

### Regras importantes

- **Editar um modelo aprovado** o reenvia para revisão (volta a `PENDING`)
  e é limitado pela Meta: **no máximo 10 edições a cada 30 dias, 1 a cada 24 horas**. Você
  não pode alterar a categoria de um modelo aprovado.
- **Excluir um modelo aprovado** bloqueia seu **nome por 30 dias** — você não pode
  recriar um modelo com o mesmo nome até lá (restrição da Meta).
- **Criar** é limitado a ~100 modelos por WABA por hora.

Quando um modelo está `APPROVED`, envie-o com
[`POST /api/v1/messages/send-template`](/br/documentation/sending-messages#template-messages).

---

## Capacidades e Limites

O que cada canal suporta, os planos de licença e os limites da plataforma.

### Capacidades por canal

Os três canais são conectados da mesma forma (OAuth). A tabela abaixo mostra o que cada canal suporta.

| Recurso | WhatsApp | Instagram | Facebook Messenger |
|---|---|---|---|
| Texto de saída — tamanho máximo | 4096 chars | 1000 chars | 2000 chars |
| Mídia de saída (Pro) | image, audio, video, document | image, audio, video, document | image, audio, video, document |
| Mensagens de modelo (Pro) | ✅ Obrigatórias fora da janela de 24h | ❌ Não suportado | ❌ Não suportado |
| Webhooks recebidos — texto | ✅ `type: "text"` | ✅ `type: "text"` | ✅ `type: "text"` |
| Webhooks recebidos — mídia (Pro) | image, audio, video, document, sticker | image, audio, video, document | image, audio, video, document |
| Status de entrega | `sent` `delivered` `read` `failed` | `delivered` `read` | `delivered` `read` |
| Formato do destinatário | Número de telefone sem `+` | IGSID | PSID |
| Contorno da janela de 24h | Usar modelos aprovados | Nenhum — aguardar o usuário enviar mensagem | Nenhum — aguardar o usuário enviar mensagem |
| Identificador do canal | `phone_number_id` | `ig_account_id` | `page_id` |
| Perfil do remetente | `data.from_name` (dos contatos da Meta) | Via [endpoint de perfil](/br/documentation/webhooks#sender-profile) | Via [endpoint de perfil](/br/documentation/webhooks#sender-profile) |

> **Observação:** Cada conta Meta (número de telefone, conta do Instagram ou Página do Facebook) só pode estar conectada a um usuário Fiwano por vez.

### Planos de licença

O Fiwano oferece dois planos de licença. Cada canal conectado exige uma licença ativa.

| Plano | Mensal | Capacidades |
|---|---|---|
| **Starter** | US$ 12 | Mensagens de texto recebidas e enviadas ilimitadas, status de entrega |
| **Pro** | US$ 19 | Tudo do Starter **+** mídia recebida com arquivos, mídia enviada via URL HTTPS (URLs assinadas suportadas), gerenciamento e envio de modelos do WhatsApp |

Contas novas começam com um período de teste gratuito de 7 dias (plano Pro). Para o ciclo de cobrança e como o estado de assinatura de um canal é reportado, veja [Assinaturas e Cobrança](/br/documentation/subscriptions). Para entender como essa taxa fixa se relaciona com as tarifas por mensagem da própria Meta, veja [Custos de Mensagens Explicados](/br/documentation/messaging-costs).

### Limites de taxa

Cada chave de API tem direito a **~600 requisições/minuto (sustentadas)** com picos
curtos acima disso. Exceder esse limite retorna HTTP `429` com um header `Retry-After`
indicando quantos segundos esperar; respostas bem-sucedidas trazem `X-RateLimit-Limit` e
`X-RateLimit-Remaining` para você se autorregular. Sob carga agregada excepcional, a
plataforma pode descartar requisições brevemente com HTTP `503` + `Retry-After` —
trate `429` e `503` da mesma forma: respeite `Retry-After` e tente novamente. Isso é uma
proteção generosa, não um limite rígido do produto — se você precisa de mais throughput
sustentado, [fale conosco](mailto:contact@fiwano.com). A Meta tem seus próprios limites
por canal (exibidos no Meta Business Manager, fora do controle do Fiwano).

### Janelas de mensagem (24h)

A Meta restringe quando você pode enviar mensagem a um usuário fora de uma conversa aberta:

- **WhatsApp** — você só pode enviar texto comum dentro de **24 horas** da última
  mensagem do cliente. Fora da janela, use um modelo aprovado via
  `POST /api/v1/messages/send-template`. Isso é política da Meta.
- **Instagram e Facebook Messenger** — você só pode responder dentro de **24 horas** da
  última mensagem do usuário. Não há contorno por modelo — aguarde o usuário enviar
  mensagem novamente.

### Limites de mídia

- **Mídia recebida** (imagens, áudio, vídeo, documentos) é armazenada temporariamente por
  **60 minutos**. Baixe-a via `GET /api/v1/media/{media_id}` prontamente após o webhook;
  os arquivos são removidos automaticamente após a expiração. Tamanho máximo de arquivo:
  **10 MB**.
- **Licença Pro obrigatória** para enviar/receber mídia e usar modelos do WhatsApp. Com
  uma licença Starter, a mídia recebida chega como `type: "unsupported"` com
  `upgrade_required: "pro"`. Veja [Assinaturas e Cobrança](/br/documentation/subscriptions).

---

## Assinaturas e Cobrança

Todo canal retornado por `GET /api/v1/channels` traz um objeto `subscription`
descrevendo seu estado de cobrança atual. Esta página explica o que esses estados
significam e como mudam ao longo do ciclo de vida de um canal. Para os tipos dos
campos, veja a **[Referência da API](/documentation/api)**.

Um canal pode **enviar e receber mensagens apenas enquanto sua assinatura estiver
`active`.** Quando não está, as chamadas de envio/recebimento são rejeitadas até que
uma licença seja (re)vinculada.

### O objeto subscription

```json
"subscription": {
  "status": "active",
  "source": "paddle",
  "tier": "pro",
  "expires_at": "2025-02-15T10:30:00",
  "auto_renew": true
}
```

- **`status`** — `active`, `expired`, `canceled` ou `none` (nenhuma licença vinculada;
  o canal não pode enviar/receber).
- **`source`** — de onde veio o direito de uso: `trial` (concedido automaticamente no
  cadastro), `paddle` (assinatura paga) ou `enterprise` (assinatura customizada
  provisionada pela equipe Fiwano, por exemplo um acordo de parceria ou cobrança por
  fatura). `null` quando `status` é `none`.
- **`tier`** — `starter` ou `pro`. `pro` é obrigatório para mensagens de mídia e
  CRUD/envio de modelos do WhatsApp. `null` quando `status` é `none`.
- **`expires_at`** — timestamp ISO-8601 UTC de quando o período atual termina. Se
  `auto_renew` for `true`, esta é a próxima data de renovação; caso contrário, é o
  limite após o qual o canal para de funcionar.
- **`auto_renew`** — `true` somente para uma assinatura Paddle ativa que será renovada
  em `expires_at`. Sempre `false` para trial e Enterprise.

### O que as combinações significam

- **Assinatura Paddle ativa** —
  `{status: "active", source: "paddle", auto_renew: true, expires_at: <próxima renovação>}`.
- **Renovação Paddle em nova tentativa** —
  `{status: "active", source: "paddle", auto_renew: true, expires_at: <há pouco no passado>}`.
  Enquanto um pagamento de renovação é retentado, `status` permanece `active` e `expires_at`
  pode ficar ligeiramente no passado — **o serviço continua durante essa breve janela de
  tolerância.** Depois resolve para renovado (`expires_at` futuro) ou, se o pagamento seguir
  falhando, expira.
- **Paddle com cancelamento agendado** —
  `{status: "active", source: "paddle", auto_renew: false, expires_at: <limite>}`.
  O cliente cancelou no Paddle; o serviço continua até `expires_at`, depois o canal fica
  órfão.
- **Trial** —
  `{status: "active", source: "trial", tier: "pro", auto_renew: false, expires_at: <cadastro + 7 dias>}`.
- **Enterprise** —
  `{status: "active", source: "enterprise", auto_renew: false, expires_at: <fim do prazo acordado>}`.
  As renovações são combinadas com a equipe Fiwano antes de `expires_at`.
- **Sem assinatura ativa** —
  `{status: "none", source: null, tier: null, expires_at: null, auto_renew: false}`.
  Envio/recebimento falharão; vincule uma licença para restaurar o serviço.

> **Dica.** Trate `status` como a única fonte de verdade para saber se um canal pode
> operar. Não o deduza por conta própria a partir de `expires_at` — durante a janela de
> tolerância do Paddle, um canal `active` pode legitimamente ter um `expires_at` no passado.

---

## Integração n8n

O Fiwano é um **node comunitário verificado do n8n** — listado em [n8n.io/integrations/fiwano/](https://n8n.io/integrations/fiwano/). Use-o para construir automações de WhatsApp, Instagram e Facebook Messenger, fluxos com agentes de IA e chatbots.

### Instalação

#### Pelo editor do n8n (recomendado)

1. Abra o painel de nodes com **+** ou **N**
2. Pesquise por **Fiwano**
3. Selecione **Fiwano** em **More from the community**
4. Clique em **Install**

No n8n Cloud, a instalação pode precisar ser habilitada antes pelo dono da instância no Cloud Admin Panel.

#### Alternativa manual (npm)

Use apenas quando a instalação pelo app não estiver disponível no seu ambiente (por exemplo, um setup self-hosted restrito).

```bash
mkdir -p ~/.n8n/nodes && cd ~/.n8n/nodes
npm install n8n-nodes-fiwano
# Reinicie o n8n
```

Para Docker self-hosted: inclua este pacote em uma imagem n8n customizada — veja o [repositório no GitHub](https://github.com/fiwano-com/n8n-nodes-fiwano) para detalhes.

### Nodes

| Node | Tipo | Descrição |
|---|---|---|
| **Fiwano** | Action | Enviar mensagens, gerenciar canais, modelos do WhatsApp, enriquecimento de perfil de contato, redirect URIs |
| **Fiwano Trigger** | Webhook Trigger | Receber mensagens recebidas e webhooks de status de entrega com verificação de assinatura HMAC opcional |

### Node Action — operações

| Recurso | Operações |
|---|---|
| Message | Send Text, Send Template (WhatsApp), Send Media (image/audio/video/document) |
| Media | Download (baixa um arquivo de mídia recebido; expira 60 min após o webhook) |
| Channel | Get Many, Get, Generate OAuth URL, Exchange OAuth Code, Update Webhook, Delete |
| Contact | Get Profile (Instagram e Facebook — retorna nome/username e foto de perfil; Instagram também número de seguidores) |
| Template | Get Many, Get, Create, Update, Delete (somente WhatsApp) |
| Redirect URI | Get Many, Add, Delete |

### Node Trigger — eventos

Inicia seu workflow para qualquer um destes eventos (filtre por tipo de evento nas configurações do node):

| Evento | Canais |
|---|---|
| `message.received` | WhatsApp, Instagram, Facebook |
| `message.delivered` | WhatsApp, Instagram, Facebook |
| `message.read` | WhatsApp, Instagram, Facebook |
| `message.sent` | WhatsApp |
| `message.failed` | WhatsApp |

### Configuração automática do webhook

O trigger pode configurar o webhook nos seus canais sozinho, sem você chamar **Update Webhook** manualmente. Escolha um modo de **Webhook Auto-Setup** e anexe uma credencial da API Fiwano. Os modos automáticos (**All Active Channels** / **Specific Channel**) precisam dela para chamar a API — se estiver faltando, a ativação falha com um erro claro. No **Manual** a credencial é opcional, usada apenas para ler um segredo de webhook padrão:

| Modo | Ao ativar o fluxo | Ao desativar |
|---|---|---|
| **All Active Channels** | Aponta para este trigger todo canal ativo que **ainda não esteja apontando para outro lugar** (WhatsApp + Instagram + Facebook) — um fluxo atende aos três. Canais que já apontam para outra URL são **deixados intactos**. | Limpa o webhook nos canais que ainda apontam para este trigger. |
| **Specific Channel** | Aponta um **Channel ID** para este trigger — **assume o canal** mesmo que ele já tenha um webhook. | Limpa o webhook desse canal (apenas se ainda apontar para cá). |
| **Manual** *(padrão)* | Nada — você define o `webhook_url` via **Exchange OAuth Code** / **Update Webhook**. Sem credencial. | Nada. |

Os **Event Types** selecionados no trigger são registrados como `webhook_events` do canal (eventos que não se aplicam ao tipo de canal são ignorados — ex.: `message.sent`/`message.failed` no Instagram). Os canais começam sem eventos habilitados, então o auto-setup os ativa para você.

**Segredo do webhook.** Defina um **Webhook Secret** para verificar as assinaturas recebidas (HMAC-SHA256; divergências são rejeitadas com HTTP 401) e, no auto-setup, registrá-lo nos seus canais. Você pode defini-lo em dois lugares: o campo **Webhook Secret** do próprio trigger, ou — para reutilizar um único segredo em tudo — o campo **Webhook Secret** da credencial da API Fiwano. O campo do trigger tem prioridade; se estiver vazio, usa-se o segredo da credencial. Esse mesmo segredo da credencial também é usado pelas operações **Exchange OAuth Code** e **Update Webhook** quando você deixa o segredo delas vazio. Deixe ambos vazios para pular a verificação (não recomendado em produção).

**Quando roda:** apenas na **ativação / desativação** do fluxo (e quando o n8n reinicia fluxos ativos) — **nunca por mensagem**, portanto não adiciona custo ao processamento de mensagens. Pontos a observar:

- **Desativar remove o webhook** dos canais que apontam para este trigger. Isso apenas limpa a URL do webhook — **não** apaga o canal, mensagens ou quaisquer dados. Enquanto desativado, a Fiwano continua armazenando as mensagens recebidas, mas não as repassa; reative para retomar.
- **O All Active Channels pula canais silenciosamente.** Um canal que já aponta para outra URL é deixado intacto e o fluxo ativa sem erro. Então, se um canal não estiver respondendo, verifique se o webhook dele aponta para outro lugar — limpe-o ou use **Specific Channel** para assumi-lo.
- **Limpe antes de remover.** Desative o fluxo (não apenas o exclua, e não remova a credencial antes) para o trigger conseguir limpar o webhook. Se a limpeza não rodar, um canal continua apontando para uma URL do n8n inativa — a Fiwano então registra falhas de entrega e envia e-mails até você limpá-lo (via **Update Webhook** ou pelo portal).
- Conectou um **canal novo** depois de ativar? Reative o fluxo (desligue/ligue) para o trigger configurá-lo.
- Dois fluxos **All Active Channels** não disputam um canal — quem reivindicar primeiro um canal livre fica com ele; o outro o deixa intacto. Para mover um canal de propósito, limpe o webhook dele ou use **Specific Channel**.
- Seu n8n precisa estar **acessível publicamente** — a Fiwano entrega webhooks pela internet na URL que o trigger registra.

### Workflows de exemplo

Dois workflows prontos para importar estão no [repositório no GitHub](https://github.com/fiwano-com/n8n-nodes-fiwano/tree/main/workflows). Use-os nesta ordem:

1. **Connect a Channel** — gera um link de conexão Meta por canal e captura o `channel_id` conectado automaticamente via um callback de webhook. (Você também pode conectar canais no [portal Fiwano](https://fiwano.com).)
2. **Universal Auto-Responder** — um único trigger responde a **todas** as mensagens no WhatsApp, Instagram e Facebook: ecoa texto e responde a anexos com os detalhes do arquivo. O padrão de ping-pong unificado — **precisa de pelo menos um canal conectado** (passo 1).

Importe pelo editor (**Workflows → Import from File…**) ou pela CLI.

---

## API Reference (OpenAPI)

The complete machine-readable contract for the public `/api/v1` API, generated from the live service (also at https://fiwano.com/api/v1/openapi.json and https://fiwano.com/api/v1/openapi.yaml).

```yaml
openapi: 3.1.0
info:
  title: Fiwano API
  description: 'Unified REST API for WhatsApp, Instagram and Facebook Messenger.


    Authenticate every request with the `X-API-Key` header (keys start with `mip_live_`,
    created on the API Keys page in the portal). Base URL: `https://fiwano.com`.


    Human-readable guides: https://fiwano.com/documentation'
  version: 2.0.0
servers:
- url: https://fiwano.com
paths:
  /api/v1/channels:
    get:
      tags:
      - api
      summary: List Channels
      description: 'List all channels for the authenticated user.


        Returns both active and inactive channels. Each channel includes its current

        `subscription` (billing) state and the channel-type-specific identifiers

        (WhatsApp: phone_number_id/waba_id; Instagram: ig_account_id/ig_username;

        Instagram & Facebook: page_id).'
      operationId: list_channels_api_v1_channels_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelListResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/channels/{channel_id}:
    get:
      tags:
      - api
      summary: Get Channel
      description: 'Get one channel by ID.


        Same shape as the list endpoint — a single channel object including its

        `subscription` (billing) state. Returns 404 if the channel does not belong

        to the authenticated account.'
      operationId: get_channel_api_v1_channels__channel_id__get
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
    patch:
      tags:
      - api
      summary: Update Channel
      description: 'Update channel webhook settings.


        Set webhook_url to receive incoming messages. Must be HTTPS (or http://localhost
        for dev).

        Optionally provide a custom webhook_secret for HMAC verification.'
      operationId: update_channel_api_v1_channels__channel_id__patch
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChannelUpdateRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChannelUpdateResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
    delete:
      tags:
      - api
      summary: Delete Channel
      description: 'Deactivate a channel (soft delete).


        The channel stops sending and receiving, but is not erased — its channel_id

        and history are preserved so it can be reconnected later via a new OAuth

        flow. Fiwano unsubscribes the channel''s Meta webhook resource only when safe:

        a WABA subscription is kept if another active WhatsApp channel uses the same

        WABA, and a Page subscription is kept if another active Instagram/Facebook

        channel uses the same Page.'
      operationId: delete_channel_api_v1_channels__channel_id__delete
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/channels/setup-url:
    post:
      tags:
      - api
      summary: Create Setup Url
      description: 'Generate a URL for connecting a channel on behalf of a user (programmatic
        flow).


        The client opens the returned `setup_url` in a popup or browser. After the

        user completes Meta OAuth (or WhatsApp Embedded Signup), they are redirected

        to `redirect_uri` with a one-time `code`. Exchange that code via

        POST /api/v1/channels/exchange-code to obtain the channel_id.


        `redirect_uri` must already be whitelisted for this API key (add it via

        POST /api/v1/redirects), otherwise the request is rejected. The setup URL
        is

        valid until the `expires_at` returned in the response.'
      operationId: create_setup_url_api_v1_channels_setup_url_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetupUrlRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SetupUrlResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/channels/exchange-code:
    post:
      tags:
      - api
      summary: Exchange Code
      description: 'Exchange a one-time completion code for channel data.


        After the user completes the OAuth flow, the redirect_uri receives a `code`
        parameter.

        This endpoint exchanges that code for the channel_id and basic details.


        The code is one-time use and expires after 5 minutes.'
      operationId: exchange_code_api_v1_channels_exchange_code_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExchangeCodeRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExchangeCodeResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/channels/{channel_id}/profile/{user_id}:
    get:
      tags:
      - api
      summary: Get Sender Profile
      description: "Fetch sender profile from Meta Graph API.\n\nReturns profile data\
        \ for a user who messaged your channel.\nResults are cached for 5 minutes\
        \ to reduce Meta API calls.\n\n- Instagram: username, name, profile_pic, follower_count,\
        \ is_verified_user\n- Facebook: first_name, last_name, profile_pic\n- WhatsApp:\
        \ not supported (name comes inline in webhooks via data.from_name)\n\nArgs:\n\
        \    channel_id: Channel ID\n    user_id: Sender identifier (IGSID for Instagram,\
        \ PSID for Facebook)\n\nReturns:\n    SenderProfileResponse with profile data\n\
        \nRaises:\n    HTTPException 404: Channel not found or WhatsApp (not supported)\n\
        \    HTTPException 400: Channel inactive or missing token\n    HTTPException\
        \ 502: Meta API error"
      operationId: get_sender_profile_api_v1_channels__channel_id__profile__user_id__get
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      - name: user_id
        in: path
        required: true
        schema:
          type: string
          title: User Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SenderProfileResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/redirects:
    get:
      tags:
      - api
      summary: List Redirects
      description: 'List the redirect URIs whitelisted for this API key.


        These gate the programmatic channel-connection flow — the `redirect_uri` in

        POST /api/v1/channels/setup-url must match one of these patterns.'
      operationId: list_redirects_api_v1_redirects_get
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RedirectListResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
    post:
      tags:
      - api
      summary: Add Redirect
      description: 'Add an allowed redirect URI.


        Must be HTTPS. Supports wildcard patterns like https://*.example.com/*'
      operationId: add_redirect_api_v1_redirects_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RedirectCreateRequest'
      responses:
        '201':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RedirectCreateResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/redirects/{redirect_id}:
    delete:
      tags:
      - api
      summary: Remove Redirect
      description: Remove an allowed redirect URI.
      operationId: remove_redirect_api_v1_redirects__redirect_id__delete
      parameters:
      - name: redirect_id
        in: path
        required: true
        schema:
          type: string
          title: Redirect Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/messages/send:
    post:
      tags:
      - api
      summary: Send Message
      description: 'Send a text message through a connected channel.


        Only text messages are supported here (use /api/v1/messages/send-media for

        media and /api/v1/messages/send-template for WhatsApp templates).


        Recipient format by channel:

        - WhatsApp: phone number without + (e.g. "1234567890")

        - Instagram: Instagram-scoped user ID (IGSID)

        - Facebook Messenger: Page-scoped user ID (PSID)


        `text` has a per-platform hard limit (WhatsApp 4096, Facebook 2000,

        Instagram 1000); oversize text is rejected with 400 `text_too_long` before

        Meta is called. On a transient Meta failure the response is 200 with

        `status: "queued"` (retried in the background); on a permanent error it is

        200 with `success: false`, `status: "failed"`.'
      operationId: send_message_api_v1_messages_send_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendMessageResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/media/{media_id}:
    get:
      tags:
      - api
      summary: Download Media
      description: 'Download a media file previously received via webhook.


        Requires a Pro license. Files are temporary and expire after

        the configured TTL (default 60 minutes).


        Response: raw file bytes with appropriate Content-Type header.'
      operationId: download_media_api_v1_media__media_id__get
      parameters:
      - name: media_id
        in: path
        required: true
        schema:
          type: string
          title: Media Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/messages/send-media:
    post:
      tags:
      - api
      summary: Send Media Message
      description: 'Send a media message through a connected channel.


        Requires a Pro license. Meta fetches the file directly from `media_url`

        — Fiwano does not download or store it. For non-public content, use a

        signed URL (S3 presigned, GCS Signed, R2 signed, or HMAC-signed URL).


        Supported media types per channel:

        - WhatsApp: image, audio, video, document

        - Instagram: image, audio, video, document

        - Facebook Messenger: image, audio, video, document


        On failure, returns 200 with `success=false` and `error_code` (Meta

        code, when applicable). See public API docs for the error code table.'
      operationId: send_media_message_api_v1_messages_send_media_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMediaRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendMessageResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/channels/{channel_id}/templates:
    get:
      tags:
      - api
      summary: List Templates
      description: 'List message templates for a WhatsApp channel.


        By default, syncs templates from Meta before returning (sync=true).

        Set sync=false to return cached data only (faster, but may be stale).


        Templates are tied to the WhatsApp Business Account (WABA).

        Only WhatsApp channels support templates.'
      operationId: list_templates_api_v1_channels__channel_id__templates_get
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      - name: sync
        in: query
        required: false
        schema:
          type: boolean
          description: Sync templates from Meta before returning. Set false for faster
            cached data (may be stale).
          default: true
          title: Sync
        description: Sync templates from Meta before returning. Set false for faster
          cached data (may be stale).
      - name: status
        in: query
        required: false
        schema:
          anyOf:
          - type: string
          - type: 'null'
          description: 'Filter by status: APPROVED, PENDING, or REJECTED.'
          title: Status
        description: 'Filter by status: APPROVED, PENDING, or REJECTED.'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateListResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
    post:
      tags:
      - api
      summary: Create Template
      description: 'Create a new WhatsApp message template.


        The template is submitted to Meta for review (status=PENDING).

        Review typically takes up to 24 hours.


        Template name must be lowercase alphanumeric with underscores.

        BODY component is required. HEADER (TEXT only), FOOTER, and BUTTONS are optional.


        Variables use {{1}}, {{2}} syntax (positional) or {{name}} (named).

        Example values are required for Meta review.


        Rate limit: 100 templates created per WABA per hour.'
      operationId: create_template_api_v1_channels__channel_id__templates_post
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateCreateRequest'
      responses:
        '201':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/channels/{channel_id}/templates/{template_id}:
    get:
      tags:
      - api
      summary: Get Template
      description: 'Get a specific template by its ID.


        Returns cached template data including variable definitions

        with example values for each variable.'
      operationId: get_template_api_v1_channels__channel_id__templates__template_id__get
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      - name: template_id
        in: path
        required: true
        schema:
          type: string
          title: Template Id
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
    put:
      tags:
      - api
      summary: Update Template
      description: 'Update a template''s components.


        All components are replaced entirely — partial update is not supported by
        Meta.


        Restrictions:

        - Only APPROVED, REJECTED, or PAUSED templates can be edited

        - Approved templates: max 10 edits per 30 days, 1 per 24 hours

        - Cannot change category of an approved template


        After editing an approved template, it goes back to PENDING for re-review.'
      operationId: update_template_api_v1_channels__channel_id__templates__template_id__put
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      - name: template_id
        in: path
        required: true
        schema:
          type: string
          title: Template Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TemplateUpdateRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateOut'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
    delete:
      tags:
      - api
      summary: Delete Template
      description: 'Delete a message template.


        By default, deletes only the specific language version.

        Set all_languages=true to delete ALL language versions of this template.


        WARNING: After deleting an approved template, you cannot create a template

        with the same name for 30 days.'
      operationId: delete_template_api_v1_channels__channel_id__templates__template_id__delete
      parameters:
      - name: channel_id
        in: path
        required: true
        schema:
          type: string
          title: Channel Id
      - name: template_id
        in: path
        required: true
        schema:
          type: string
          title: Template Id
      - name: all_languages
        in: query
        required: false
        schema:
          type: boolean
          description: Delete all language versions of this template, not just this
            one.
          default: false
          title: All Languages
        description: Delete all language versions of this template, not just this
          one.
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema: {}
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
  /api/v1/messages/send-template:
    post:
      tags:
      - api
      summary: Send Template Message
      description: "Send a WhatsApp template message.\n\nUse this to initiate conversations\
        \ outside the 24-hour messaging window.\nOnly APPROVED templates can be sent.\n\
        \n## Variables\n\nEach template may have variables in HEADER, BODY, and BUTTON\
        \ components.\nAll variables must be provided — there are no defaults.\n\n\
        **Positional format** (for templates created with {{1}}, {{2}}):\n```json\n\
        {\n    \"variables\": {\n        \"header\": [\"Summer Sale\"],\n        \"\
        body\": [\"Pablo\", \"ORD-123\", \"25%\"],\n        \"buttons\": [{\"index\"\
        : 0, \"value\": \"promo25\"}]\n    }\n}\n```\n\n**Named format** (for templates\
        \ created with {{customer_name}}):\n```json\n{\n    \"variables\": {\n   \
        \     \"body\": {\"customer_name\": \"Pablo\", \"order_number\": \"ORD-123\"\
        }\n    }\n}\n```\n\nOmit `variables` entirely if the template has no variables."
      operationId: send_template_message_api_v1_messages_send_template_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendTemplateRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendTemplateResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
components:
  schemas:
    ChannelListResponse:
      properties:
        channels:
          items:
            $ref: '#/components/schemas/ChannelOut'
          type: array
          title: Channels
        total:
          type: integer
          title: Total
      type: object
      required:
      - channels
      - total
      title: ChannelListResponse
      description: List of channels.
    ChannelOut:
      properties:
        id:
          type: string
          title: Id
          description: Channel ID. Use this value in every other channel/message API
            call.
        channel_type:
          type: string
          title: Channel Type
          description: 'Channel type: ''whatsapp'', ''instagram'', or ''facebook''.'
        name:
          anyOf:
          - type: string
          - type: 'null'
          title: Name
          description: 'Display name: business name (WhatsApp), username (Instagram),
            or Page name (Facebook).'
        is_active:
          type: boolean
          title: Is Active
          description: True if the channel can currently send and receive messages.
        phone_number_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Phone Number Id
          description: WhatsApp only — Meta's phone number ID.
        phone_number:
          anyOf:
          - type: string
          - type: 'null'
          title: Phone Number
          description: WhatsApp only — human-readable phone number.
        waba_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Waba Id
          description: WhatsApp only — WhatsApp Business Account (WABA) ID.
        quality_rating:
          anyOf:
          - type: string
          - type: 'null'
          title: Quality Rating
          description: WhatsApp only — Meta's current quality rating for the number,
            when available.
        ig_account_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Ig Account Id
          description: Instagram only — Instagram account ID.
        ig_username:
          anyOf:
          - type: string
          - type: 'null'
          title: Ig Username
          description: Instagram only — Instagram username.
        page_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Page Id
          description: Instagram/Facebook — linked Facebook Page ID.
        webhook_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Url
          description: Where incoming messages and delivery statuses are delivered.
        has_webhook_secret:
          type: boolean
          title: Has Webhook Secret
          description: Whether a webhook secret is configured for HMAC signature verification.
          default: false
        webhook_events:
          anyOf:
          - items:
              type: string
            type: array
          - type: 'null'
          title: Webhook Events
          description: Enabled event types, e.g. ["message.received", "message.delivered"].
        connected_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Connected At
          description: ISO-8601 UTC timestamp when the channel was connected.
        created_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Created At
          description: ISO-8601 UTC timestamp when the channel record was first created.
        subscription:
          $ref: '#/components/schemas/SubscriptionInfo'
          description: Current subscription/billing state. The channel can send/receive
            only while status='active'.
      type: object
      required:
      - id
      - channel_type
      - is_active
      - subscription
      title: ChannelOut
      description: 'A connected channel — one WhatsApp number, Instagram account,
        or Facebook

        Page. Channel-type-specific fields are populated only for the relevant type

        (e.g. `phone_number_id`/`waba_id` for WhatsApp, `ig_username` for Instagram);

        the rest are null.'
    ChannelUpdateRequest:
      properties:
        webhook_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Url
          description: HTTPS URL for incoming webhook delivery
        webhook_secret:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Secret
          description: Set a specific HMAC secret (also use this to rotate to a new
            value). Omit to keep the current secret — except that setting webhook_url
            for the first time with no secret auto-generates one, returned in the
            response.
        webhook_events:
          anyOf:
          - items:
              type: string
            type: array
          - type: 'null'
          title: Webhook Events
          description: List of event types to deliver. Available events depend on
            channel type.
      type: object
      title: ChannelUpdateRequest
      description: Request to update channel settings.
    ChannelUpdateResponse:
      properties:
        id:
          type: string
          title: Id
        webhook_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Url
        webhook_secret:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Secret
        webhook_events:
          anyOf:
          - items:
              type: string
            type: array
          - type: 'null'
          title: Webhook Events
      type: object
      required:
      - id
      title: ChannelUpdateResponse
      description: Response after channel update.
    ExchangeCodeRequest:
      properties:
        code:
          type: string
          title: Code
          description: One-time completion code from OAuth redirect
        webhook_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Url
          description: HTTPS URL for incoming webhook delivery. If provided, webhook
            is configured automatically — no separate PATCH needed.
        webhook_secret:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Secret
          description: 'HMAC secret used to sign webhook deliveries (verify it via
            the X-Webhook-Signature header). Optional: if you set webhook_url without
            supplying this, Fiwano auto-generates a secret and returns it in the response.
            Pass your own value to use a specific secret instead.'
        webhook_events:
          anyOf:
          - items:
              type: string
            type: array
          - type: 'null'
          title: Webhook Events
          description: List of event types to deliver. Available events depend on
            channel type. If omitted, no events are delivered until configured.
      type: object
      required:
      - code
      title: ExchangeCodeRequest
      description: Request to exchange completion code for channel data.
    ExchangeCodeResponse:
      properties:
        channel_id:
          type: string
          title: Channel Id
        channel_type:
          type: string
          title: Channel Type
        name:
          anyOf:
          - type: string
          - type: 'null'
          title: Name
        phone_number_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Phone Number Id
        phone_number:
          anyOf:
          - type: string
          - type: 'null'
          title: Phone Number
        ig_account_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Ig Account Id
        ig_username:
          anyOf:
          - type: string
          - type: 'null'
          title: Ig Username
        page_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Page Id
        webhook_url:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Url
        webhook_secret:
          anyOf:
          - type: string
          - type: 'null'
          title: Webhook Secret
        webhook_events:
          anyOf:
          - items:
              type: string
            type: array
          - type: 'null'
          title: Webhook Events
      type: object
      required:
      - channel_id
      - channel_type
      title: ExchangeCodeResponse
      description: Response with channel data after code exchange.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    RedirectCreateRequest:
      properties:
        uri_pattern:
          type: string
          title: Uri Pattern
          description: 'HTTPS URL pattern. Supports wildcards: https://*.example.com/*'
      type: object
      required:
      - uri_pattern
      title: RedirectCreateRequest
      description: Request to add redirect URI.
    RedirectCreateResponse:
      properties:
        id:
          type: string
          title: Id
        uri_pattern:
          type: string
          title: Uri Pattern
      type: object
      required:
      - id
      - uri_pattern
      title: RedirectCreateResponse
      description: Response after creating redirect.
    RedirectListResponse:
      properties:
        redirects:
          items:
            $ref: '#/components/schemas/RedirectOut'
          type: array
          title: Redirects
      type: object
      required:
      - redirects
      title: RedirectListResponse
      description: List of redirect URIs.
    RedirectOut:
      properties:
        id:
          type: string
          title: Id
        uri_pattern:
          type: string
          title: Uri Pattern
        created_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Created At
      type: object
      required:
      - id
      - uri_pattern
      title: RedirectOut
      description: Allowed redirect URI.
    SendMediaRequest:
      properties:
        channel_id:
          type: string
          title: Channel Id
          description: Channel ID to send from
        recipient:
          type: string
          title: Recipient
          description: Recipient identifier
        media_type:
          type: string
          title: Media Type
          description: 'Media type: image, audio, video, or document'
        media_url:
          type: string
          maxLength: 2048
          title: Media Url
          description: HTTPS URL of the media file. For non-public content, use a
            signed URL (S3 presigned, GCS Signed, R2 signed, or HMAC). Max length
            2048.
        caption:
          anyOf:
          - type: string
            maxLength: 1024
          - type: 'null'
          title: Caption
          description: Caption (WhatsApp image/video/document)
        filename:
          anyOf:
          - type: string
            maxLength: 255
          - type: 'null'
          title: Filename
          description: Filename (WhatsApp document only)
      type: object
      required:
      - channel_id
      - recipient
      - media_type
      - media_url
      title: SendMediaRequest
      description: 'Request to send a media message. Requires a Pro license.


        Meta fetches the file directly from `media_url`. For non-public

        content, use a signed URL — presigned S3, GCS Signed URL, Cloudflare

        R2 signed URL, or HMAC-signed URL on your own server. Public URLs

        are accessible to anyone who learns them; only use them for

        non-sensitive content.


        Use image, audio, video, or document across all supported channels.

        Provider-specific attachment names are handled internally.'
    SendMessageRequest:
      properties:
        channel_id:
          type: string
          title: Channel Id
          description: Channel ID to send from
        recipient:
          type: string
          title: Recipient
          description: Recipient identifier (phone number for WhatsApp, IGSID for
            Instagram, PSID for Facebook)
        text:
          type: string
          title: Text
          description: Message text
      type: object
      required:
      - channel_id
      - recipient
      - text
      title: SendMessageRequest
      description: Request to send a message. Only text messages are supported.
    SendMessageResponse:
      properties:
        success:
          type: boolean
          title: Success
        message_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Message Id
        error:
          anyOf:
          - type: string
          - type: 'null'
          title: Error
        error_code:
          anyOf:
          - type: integer
          - type: 'null'
          title: Error Code
          description: Meta error code (when failure originates at Meta). See documentation
            for common media error codes.
        status:
          anyOf:
          - type: string
          - type: 'null'
          title: Status
          description: 'Delivery state: ''sent'' (accepted by Meta), ''queued'' (transient
            failure — retried in the background), or ''failed'' (permanent error;
            see error/error_code).'
      type: object
      required:
      - success
      title: SendMessageResponse
      description: Response after sending a message.
    SendTemplateRequest:
      properties:
        channel_id:
          type: string
          title: Channel Id
          description: WhatsApp channel ID to send from
        template_name:
          type: string
          title: Template Name
          description: Template name (e.g., 'order_confirmation')
        language:
          type: string
          title: Language
          description: Template language code (e.g., 'en_US')
        recipient:
          type: string
          title: Recipient
          description: Recipient phone number (without +, e.g., '1234567890')
        variables:
          anyOf:
          - type: object
          - type: 'null'
          title: Variables
          description: 'Variable values keyed by component type. Positional: {"header":
            ["Sale"], "body": ["Pablo", "ORD-123"], "buttons": [{"index": 0, "value":
            "promo"}]}. Named: {"body": {"customer_name": "Pablo", "order_number":
            "ORD-123"}}. Omit if template has no variables.'
      type: object
      required:
      - channel_id
      - template_name
      - language
      - recipient
      title: SendTemplateRequest
      description: "Request to send a WhatsApp template message.\n\nVariables must\
        \ match the template's parameter definitions.\nOnly APPROVED templates can\
        \ be sent.\n\nFor positional templates, provide variables as arrays:\n   \
        \ {\"body\": [\"Pablo\", \"ORD-123\"]}\n\nFor named templates, provide variables\
        \ as objects:\n    {\"body\": {\"customer_name\": \"Pablo\", \"order_number\"\
        : \"ORD-123\"}}"
    SendTemplateResponse:
      properties:
        success:
          type: boolean
          title: Success
        message_id:
          anyOf:
          - type: string
          - type: 'null'
          title: Message Id
        error:
          anyOf:
          - type: string
          - type: 'null'
          title: Error
      type: object
      required:
      - success
      title: SendTemplateResponse
      description: Response after sending a template message.
    SenderProfileResponse:
      properties:
        channel_id:
          type: string
          title: Channel Id
        channel_type:
          type: string
          title: Channel Type
        user_id:
          type: string
          title: User Id
        profile:
          anyOf:
          - type: object
          - type: 'null'
          title: Profile
          description: 'Profile data from Meta, passed through as-is. Fields vary
            by channel type. null if profile is unavailable. Instagram: username,
            name, profile_pic, follower_count, is_verified_user. Facebook: first_name,
            last_name, profile_pic. WhatsApp is not supported.'
          examples:
          - follower_count: 46
            is_verified_user: false
            name: Roman Babakin
            profile_pic: https://scontent.cdninstagram.com/v/t51.2885-19/...
            username: winnerzzz
          - first_name: Roman
            last_name: Babakin
            profile_pic: https://platform-lookaside.fbsbx.com/platform/profilepic/...
        cached:
          type: boolean
          title: Cached
          description: Whether the result was served from cache
          default: false
      type: object
      required:
      - channel_id
      - channel_type
      - user_id
      title: SenderProfileResponse
      description: Sender profile from Meta Graph API.
    SetupUrlRequest:
      properties:
        channel_type:
          type: string
          pattern: ^(whatsapp|instagram|facebook)$
          title: Channel Type
          description: 'Channel type: whatsapp, instagram, or facebook'
        redirect_uri:
          type: string
          title: Redirect Uri
          description: URL to redirect after OAuth completion. Must be in allowed_redirects.
      type: object
      required:
      - channel_type
      - redirect_uri
      title: SetupUrlRequest
      description: Request to generate channel setup URL.
    SetupUrlResponse:
      properties:
        setup_url:
          type: string
          title: Setup Url
          description: URL to open in popup/browser for channel setup
        session_id:
          type: string
          title: Session Id
        expires_at:
          type: string
          title: Expires At
      type: object
      required:
      - setup_url
      - session_id
      - expires_at
      title: SetupUrlResponse
      description: Response with channel setup URL.
    SubscriptionInfo:
      properties:
        status:
          type: string
          title: Status
          description: 'Subscription status: ''active'', ''expired'', ''canceled'',
            or ''none'' (no license bound — channel cannot send/receive messages).'
        source:
          anyOf:
          - type: string
          - type: 'null'
          title: Source
          description: 'Origin of the license: ''trial'' (auto-created on signup),
            ''paddle'' (paid subscription), or ''enterprise'' (admin-granted custom
            subscription). Null when status=''none''.'
        tier:
          anyOf:
          - type: string
          - type: 'null'
          title: Tier
          description: 'License tier: ''starter'' or ''pro''. Null when status=''none''.'
        expires_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Expires At
          description: ISO-8601 UTC timestamp when the current billing period / license
            ends. Null when status='none' or for perpetual enterprise licenses.
        auto_renew:
          type: boolean
          title: Auto Renew
          description: True only for an active Paddle subscription that is set to
            renew automatically at `expires_at`. False for trial, enterprise, or any
            Paddle subscription where the user has scheduled/performed a cancellation.
          default: false
      type: object
      required:
      - status
      title: SubscriptionInfo
      description: 'Subscription/license state for a channel.


        Always present on ChannelOut. When the channel is not bound to any license

        (orphaned after expiry/cancellation, or briefly between connect and

        auto-assign), `status` is `"none"` and all other fields are null/false.


        `expires_at` is `null` when no license is bound; in normal operation

        every active license (trial / Paddle / Enterprise) carries an explicit

        expiration date. (The schema still permits `NULL` for legacy admin-

        granted rows — clients should treat that as "no announced end date".)


        `auto_renew` is `true` only for an active Paddle subscription with no

        scheduled cancellation. Once the user cancels in Paddle (or the

        subscription enters a non-renewing state) it flips to `false` and the

        license will lapse at `expires_at` unless resumed.'
    TemplateComponentInput:
      properties:
        type:
          type: string
          title: Type
          description: 'Component type: HEADER, BODY, FOOTER, BUTTONS'
        format:
          anyOf:
          - type: string
          - type: 'null'
          title: Format
          description: 'Header format: TEXT (media not yet supported)'
        text:
          anyOf:
          - type: string
          - type: 'null'
          title: Text
          description: Component text. Use {{1}}, {{2}} for positional or {{name}}
            for named variables
        example:
          anyOf:
          - type: object
          - type: 'null'
          title: Example
          description: Example values for variables (required by Meta for review)
        buttons:
          anyOf:
          - items:
              type: object
            type: array
          - type: 'null'
          title: Buttons
          description: Button definitions (for BUTTONS component)
      type: object
      required:
      - type
      title: TemplateComponentInput
      description: 'A single template component for creation/update.


        Components define the structure of a WhatsApp message template.'
    TemplateCreateRequest:
      properties:
        name:
          type: string
          maxLength: 512
          pattern: ^[a-z0-9_]+$
          title: Name
          description: Template name. Lowercase alphanumeric and underscores only.
            Max 512 chars.
        category:
          type: string
          pattern: ^(MARKETING|UTILITY|AUTHENTICATION)$
          title: Category
          description: 'Template category: MARKETING, UTILITY, or AUTHENTICATION'
        language:
          type: string
          title: Language
          description: Language code (e.g., en_US, ru, es)
        components:
          items:
            $ref: '#/components/schemas/TemplateComponentInput'
          type: array
          title: Components
          description: Template components (HEADER, BODY, FOOTER, BUTTONS). BODY is
            required.
        parameter_format:
          type: string
          pattern: ^(positional|named)$
          title: Parameter Format
          description: 'Variable format: ''positional'' for {{1}}, {{2}} or ''named''
            for {{customer_name}}'
          default: positional
      type: object
      required:
      - name
      - category
      - language
      - components
      title: TemplateCreateRequest
      description: 'Request to create a WhatsApp message template.


        The template will be submitted to Meta for review (status=PENDING).

        Review typically takes up to 24 hours.'
    TemplateListResponse:
      properties:
        templates:
          items:
            $ref: '#/components/schemas/TemplateOut'
          type: array
          title: Templates
        total:
          type: integer
          title: Total
        synced:
          type: boolean
          title: Synced
          default: false
      type: object
      required:
      - templates
      - total
      title: TemplateListResponse
      description: List of templates.
    TemplateOut:
      properties:
        id:
          type: string
          title: Id
        meta_template_id:
          type: string
          title: Meta Template Id
        name:
          type: string
          title: Name
        language:
          type: string
          title: Language
        category:
          type: string
          title: Category
        status:
          type: string
          title: Status
        components:
          items: {}
          type: array
          title: Components
        parameter_format:
          type: string
          title: Parameter Format
          default: positional
        variables:
          anyOf:
          - $ref: '#/components/schemas/TemplateVariablesSummary'
          - type: 'null'
        synced_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Synced At
        created_at:
          anyOf:
          - type: string
          - type: 'null'
          title: Created At
      type: object
      required:
      - id
      - meta_template_id
      - name
      - language
      - category
      - status
      - components
      title: TemplateOut
      description: Template data returned by API.
    TemplateUpdateRequest:
      properties:
        components:
          items:
            $ref: '#/components/schemas/TemplateComponentInput'
          type: array
          title: Components
          description: New components (replaces all existing)
        category:
          anyOf:
          - type: string
          - type: 'null'
          title: Category
          description: New category (only for REJECTED or PAUSED templates)
      type: object
      required:
      - components
      title: TemplateUpdateRequest
      description: 'Request to update a template''s components.


        All components are replaced entirely (partial update not supported by Meta).

        Approved templates: max 10 edits per 30 days, 1 per 24 hours.'
    TemplateVariableInfo:
      properties:
        position:
          type: integer
          title: Position
        name:
          anyOf:
          - type: string
          - type: 'null'
          title: Name
        example:
          anyOf:
          - type: string
          - type: 'null'
          title: Example
      type: object
      required:
      - position
      title: TemplateVariableInfo
      description: Variable info for display/documentation.
    TemplateVariablesSummary:
      properties:
        total_count:
          type: integer
          title: Total Count
          default: 0
        parameter_format:
          type: string
          title: Parameter Format
          default: positional
        header:
          anyOf:
          - items:
              $ref: '#/components/schemas/TemplateVariableInfo'
            type: array
          - type: 'null'
          title: Header
        body:
          anyOf:
          - items:
              $ref: '#/components/schemas/TemplateVariableInfo'
            type: array
          - type: 'null'
          title: Body
        buttons:
          anyOf:
          - items:
              type: object
            type: array
          - type: 'null'
          title: Buttons
      type: object
      title: TemplateVariablesSummary
      description: Summary of all variables in a template.
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
            - type: string
            - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
      type: object
      required:
      - loc
      - msg
      - type
      title: ValidationError
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key in the format mip_live_xxx. Create one on the API Keys
        page in the portal.
security:
- ApiKeyAuth: []
```
