# Sobre a SMSFunnel

SMS Envia é uma plataforma de envio de mesagens SMS através de API Rest.

A SMS Funnel é integrada nas maiores plataformas de vendas online do mercado através de **webhooks**. Após criar uma nova integração, o sistema cria um link de integração que deverá ser configurado dentro da plataforma de vendas e após isso receberá notificação a cada evento selecionado na plataforma de origem.

{% hint style="info" %}
**Atenção:** O primeiro passo é você possuir uma conta validada na nossa plataforma. Entre em [`https://smsfunnel.com.br`](https://smsfunnel.com.br) e inicie o seu cadastro, caso ainda não tenha.
{% endhint %}

Nesse canal sempre haverá informações atualizadas sobre a integração com a nossa plataforma.

## Principais Eventos

* Carrinho abandonado
* Boleto Gerado
* Boleto Pago
* Boleto Vencido
* Pix Gerado
* Pix Pago
* Cartão de Crédito Pago
* Assinatura Criada
* Assinatura aguardando pagamento
* Assinatura Paga
* Assinatura Vencida
* Pedido cancelado


# Integrações

A integração com a SMS Funnel é realizada através da utilização de webhooks contendo as informações dos leads, evento, forma de pagamento e para vendas no pix, é necessário também os dados do produto.

{% hint style="info" %}
**Atenção:** O primeiro passo é você possuir uma conta validada na nossa plataforma. Entre em [`https://smsfunnel.com.br`](https://smsfunnel.com.br) e inicie o seu cadastro, caso ainda não tenha.
{% endhint %}

#### Sobre o webhook

O webhook é um termo comum do mercado de requisição para um servidor web através do verbo HTTP POST.

#### Dados enviados

Por meio dos webhooks, ficou convencionado no mercado o envio das seguintes informações:

```json
{
    "tipo_evento": "pix_pago",
    "forma_pagamento": "pix",
    "url_checkout": "https://checkout.minhaloja.com.br/payment/asdfqwe12312dq12e",
    "dados_pagamento": {
        "url_pix": "https://pay.minhaloja.com.br/payment/asdfqwe12312dq12e",
        "vencimento_pix": "2024-07-01 12:10:53",
        "qrcode": "00020101021226770014BR.GOV.BCB.PIX2555api.itau/pix/qr/v2/084e5804-11da-4495-adec-ca66f0cff3585204000053039865802BR5906APPMAX6009SAO PAULO62070503***63047F7B"
    },
    "dados_cliente":{
        "nome":"Teste Teste",
        "telefone":"61974022942",
        "email":"teste@gmail.com"
    },
    "dados_produto":{
        "id": "OZYUHYYWG4645OFD",
        "nome": "Meu produto",
        "valor_unitario": 0,
        "quantidade": 0,
        "valor_total": 97.00,
        "url_checkout":"https://pay.minhaloja.com.br/asdfqwe12312dq12e"
    }
}
```

### Como funciona a organização dos dados

A plataforma SMS Funnel possui o conceito de listas, onde cada evento da plataforma irá gerar uma lista.&#x20;

À partir do recebimento do postback pelo link de integração, o sistema utilizará o evento e a forma de pagamento para direcionar o lead para sua respectiva lista.

O sistema irá controlá em qual parte do funil o seu cliente está, sendo que a cada evento ele irá mudar de lista.

***Exemplo:*** O cliente entra como um **carrinho abandonado**. Após um certo período, se ele clicar em finalizar pagamento e gerar um PIX, automaticamente ele irá sair da lista de carrinho abandonado e será movimentado para a lista de **PIX Gerado**. &#x20;

### Tipos de Evento

```php
pix_gerado
pix_pago
pix_expirado
cartao_pago
boleto_gerado
boleto_pago
boleto_expirado
carrinho_abandonado
pedido_cancelado
assinatura_criada
assinatura_aguardando_pagamento
assinatura_paga
assinatura_vencida
```

### Formas de Pagamento

```
pix
boleto
cartao
```

### Dados Pagamento - PIX

```
"url_pix": "https://pay.minhaloja.com.br/payment/asdfqwe12312dq12e",
"vencimento_pix": "2024-07-01 12:10:53",
"qrcode": "0
```

### Dados Pagamento - Boleto

```
"url_boleto": "https://pay.minhaloja.com.br/payment/asdfqwe12312dq12e",
"vencimento_boleto": "2024-07-01 12:10:53",
"linha_digitavel_boleto": "00190000090281239800215312977174811080000234627"
```

### Sobre a automação

À partir das listas criadas, você deverá entrar no menu **Automações** para configuar as automações baseada na fase em que seu cliente se encontra, podendo configurar mensagens específicas para cada lista.&#x20;

Nossa equipe comercial vai lhe ajudar montar suas mensagens, sendo que você tem à disposição na ferramenta a possibilidade de personalizar mensagens com os dados do seu cliente, tornando elas mais pessoais.

O controle e movimentação dos leads fará, por exemplo, que um cliente que tinha abandonado o carrinho receba uma mensagem de recuperação e quando ele realizar o pagamento, que a(s) próxima(s) mensagens sejam de confirmação do pagamento por exemplo e com isso evite dele continuar recebendo mensagens de recuperação.

###


# Como integrar com SMSFunnel?

Um passo a passo detalhado sobre como integrar sua plataforma com a SMSFunnel de forma simples e prática.

## Integração SMSFunnel

A integração entre SMSFunnel e sua plata ocorre via **webhooks**, permitindo a comunicação em tempo real entre os sistemas.

### Sumário

1. Eventos Suportados
2. Estrutura do Payload
3. Exemplo de Requisição
4. Testando Integrações
5. Quero integrar minha plataforma<br>

***

### Eventos Suportados

Os seguintes eventos são capturados pela integração. O campo `event_id` deve ser enviado no payload para determinar o evento correspondente:

<table data-header-hidden><thead><tr><th></th><th width="187"></th><th></th><th></th></tr></thead><tbody><tr><td><strong>Evento</strong></td><td><strong><code>event_id</code></strong></td><td><strong>Campos Adicionais Necessários</strong></td><td><strong>Descrição</strong></td></tr><tr><td>Abandono de Checkout</td><td><code>abandono_checkout</code></td><td><code>link_checkout</code></td><td>Lead abandonou o checkout</td></tr><tr><td>Boleto Gerado</td><td><code>boleto_gerado</code></td><td><code>data_vencimento url_boleto linha_digitavel_boleto</code></td><td>Um boleto foi gerado</td></tr><tr><td>Boleto Pago</td><td><code>boleto_pago</code></td><td></td><td>Um boleto foi pago</td></tr><tr><td>Boleto Vencido</td><td><code>boleto_vencido</code></td><td></td><td>Um boleto venceu</td></tr><tr><td>Cartão de Crédito Pago</td><td><code>cartao_pago</code></td><td></td><td>Transação no cartão de crédito aprovada</td></tr><tr><td>Pix Gerado</td><td><code>pix_gerado</code></td><td><code>url_pix vencimento_pix</code></td><td>Um código PIX foi gerado</td></tr><tr><td>Pix Pago</td><td><code>pix_pago</code></td><td></td><td>Pagamento via PIX foi efetuado</td></tr><tr><td>Pedido Cancelado</td><td><code>pedido_cancelado</code></td><td></td><td>Um pedido foi cancelado</td></tr><tr><td>Assinatura Criada</td><td><code>assinatura_criada</code></td><td></td><td>Nova assinatura criada</td></tr><tr><td>Assinatura Aguardando Pagamento</td><td><code>assinatura_aguardando_pagamento</code></td><td><code>url_pix vencimento_pix url_boleto vencimento_boleto</code></td><td>Assinatura está aguardando o pagamento</td></tr><tr><td>Assinatura Paga</td><td><code>assinatura_paga</code></td><td></td><td>Assinatura paga</td></tr><tr><td>Assinatura Vencida</td><td><code>assinatura_vencida</code></td><td></td><td>Assinatura vencida</td></tr></tbody></table>

***

### Estrutura do Payload

Todos os eventos seguem o mesmo formato básico, com campos adicionais dependendo do `event_id`.

#### Campos Obrigatórios

* `phone`: Número de telefone do lead.
* `name`: Nome do lead.
* `email`: E-mail do lead.
* `event_id`: Identificador do evento.
* `value`: Valor associado ao evento (obrigatório para eventos financeiros como pagamento ou cancelamento).

#### Campos Adicionais

* `url_checkout`: URL do checkout (quando `event_id` for `abandono_checkout`).
* `url_boleto`: URL do boleto (quando `event_id` for `boleto_gerado`).
* `linha_digitavel_boleto`: Número do boleto (quando `event_id` for `boleto_gerado`).
* `vencimento_boleto`: Data de vencimento do boleto (quando `event_id` for `boleto_gerado`).
* `qrcode`: Código para pagamento PIX (quando `event_id` for `pix_gerado`).
* `vencimento_pix`: Data de vencimento do PIX (quando `event_id` for `pix_gerado`).
* `url_pix`: URL para pagamento PIX (quando `event_id` for `pix_gerado`).

***

### Exemplo de Requisição

Segue um exemplo para o evento `pix_gerado`:

```json
{
    "tipo_evento": "pix_gerado",
    "forma_pagamento": "pix",
    "url_checkout": "https://checkout.minhaloja.com.br/payment/asdfqwe12312dq12e",
    "dados_pagamento": {
        "url_pix": "https://pay.minhaloja.com.br/payment/asdfqwe12312dq12e",
        "vencimento_pix": "2024-07-01 12:10:53",
        "qrcode": "00020101021226770014BR.GOV.BCB.PIX2555api.itau/pix/qr/v2/084e5804-11da-4495-adec-ca66f0cff3585204000053039865802BR5906APPMAX6009SAO PAULO62070503***63047F7B"
    },
    "dados_cliente":{
        "nome":"Teste Teste",
        "telefone":"61974022942",
        "email":"teste@gmail.com"
    },
    "dados_produto":{
        "id": "OZYUHYYWG4645OFD",
        "nome": "Meu produto",
        "valor_unitario": 0,
        "quantidade": 0,
        "valor_total": 97.00,
        "url_checkout":"https://pay.minhaloja.com.br/asdfqwe12312dq12e"
    }
}
```

Para o evento `cartao_pago`:

```json
{
    "tipo_evento": "cartao_pago",
    "forma_pagamento": "cartao",
    "url_checkout": "https://checkout.minhaloja.com.br/payment/asdfqwe12312dq12e",
    "dados_pagamento": {
        "bandeira": "VISA",
        "data_pagamento": "2024-07-01 12:10:53"
    },
    "dados_cliente":{
        "nome":"Teste Teste",
        "telefone":"61974022942",
        "email":"teste@gmail.com"
    },
    "dados_produto":{
        "id": "OZYUHYYWG4645OFD",
        "nome": "Meu produto",
        "valor_unitario": 0,
        "quantidade": 0,
        "valor_total": 97.00,
        "url_checkout":"https://pay.minhaloja.com.br/asdfqwe12312dq12e"
    }
}
```

\ <mark style="color:red;">**Recomenda-se o uso da ferramenta**</mark> [**Webhook.site**](http://webhook.site) <mark style="color:red;">**para validar payloads antes de enviar ao endpoint.**</mark>

***

### Testando a integração

Uma vez que você tenha desenvolvido a integração, será hora de testá-la. Para isso, certifique-se de que você já tem:

* Uma conta com crédito no SMSFunnel. (Se não tiver, realize o cadastro em [cadastro.smsfunnel.com.br](https://cadastro.smsfunnel.com.br))
* Acesso a uma conta com perfil de cliente em sua plataforma, que lhe permita configurar o webhook e validá-lo.

Com todos os requisitos em mãos, acesso o SMSFunnel e realize [ESSE PROCESSO (Clique no link)](https://scribehow.com/shared/Como_cadastrar_integracao_no_SMSFunnel__5At8-ITRTXeSv0hmhrya9A).<br>

***

### Quero integrar minha plataforma

Ficamos muito felizes em saber que você deseja integrar sua plataforma com o SMSFunnel. Essa é uma excelente decisão, pois seus clientes terão uma maior taxa de conversão como resultado.

O tempo médio para disponibilização de uma nova integração em produção é de 30 dias. Por favor, preencha[ **ESTE FORMULÁRIO** (clique no link) ](https://forms.clickup.com/9013484465/f/8ckxpxh-873/O1YSJS8BZRWD7ZK7H2)para que possamos iniciar o processo de integração.

Nossa equipe técnica entrará em contato com você em até 16 horas úteis após o preenchimento deste formulário.


# Parceiros

Saiba como acessar e interagir com nossa API de Parceiros

Disponibilizamos para alguns de nossos parceiros uma API de Parceiros, com ela é possível coletar métricas de seus usuários afiliados para melhorar suas estratégias e controles financeiros. Nesta seção você aprenderá à como integrar-se com esta API.


# Autenticação

Como gerar e autenticar-se com sua Chave de API

### Gerando sua Chave de API

1. Acesse a seção "API" em seu perfil

<div align="center"><figure><img src="https://2334899636-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0zOT5nzTB4m2xwoPYBsk%2Fuploads%2FS6Udgup7M9OVREeICbVK%2Fscreen-capture%20(61).gif?alt=media&amp;token=46ed9807-5874-48b7-8bb4-c02f7a684642" alt="" width="563"><figcaption><p>Acessando seção "API"</p></figcaption></figure></div>

2. Clique em "Gerar Chave de API"

<figure><img src="https://2334899636-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0zOT5nzTB4m2xwoPYBsk%2Fuploads%2FSQ84XzKJ546BNRAN9SpW%2Fscreen-capture%20(62).gif?alt=media&amp;token=cbbadd42-d826-4799-a562-4244b7065489" alt="" width="563"><figcaption><p>Gerando Chave de API</p></figcaption></figure>

{% hint style="danger" %}
**ATENÇÃO:** Não compartilhe sua chave com terceiros! Este conteúdo é de uso confidencial e exclusivo seu.
{% endhint %}

{% hint style="warning" %}
**Lembre-se de guardar em local seguro a Chave de API na hora, pois caso não copie terá que gerar uma nova Chave de API.**
{% endhint %}

### Validade da Chave de API

Sua Chave de API será válida por 1 ano, após esse período você deverá gerar uma nova Chave de API para continuar utilizando nossa API de Parceiros.

### Utilizando sua Chave de API nas requisições

Para utilizar sua Chave de API nas suas requisições, basta inserir o header `Authorization`.

```json
Authorization: "Bearer <SUA CHAVE DE API AQUI>"
```


# Requisições

Utilização de cada endpoint e padrões de requisição

Contamos atualmente com 4 endpoints de consulta e 3 de blacklist em nossa API de Parceiros, aprenda a seguir como utilizá-los da maneira correta e a função de cada um.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

#### Padrão de corpo e regras das requisições

1. **A conta consultada é opcional.** Se você não enviar `email` nem `user_id`, a consulta retorna os dados da **própria conta dona do token**. É o caminho natural para contas do tipo Cliente, que não gerenciam contas filhas e portanto não têm um e-mail de terceiro a informar.
2. **Se você informar a conta**, ela precisa ser o próprio dono do token ou uma **conta filha ativa** dele. Qualquer outra conta responde `404`. Use `user_id` (ID do usuário) **ou** `email` — não envie as duas chaves na mesma requisição; se ambas vierem, o `user_id` prevalece e o `email` é ignorado.
3. **Chave presente e vazia é erro.** Enviar `"email": ""` ou `"user_id": ""` responde `422`. Quem manda a chave quis nomear uma conta — cair silenciosamente na própria conta devolveria dados da conta errada. Para consultar a si mesmo, **omita a chave**.
4. Você só terá acesso às informações da sua conta e dos seus usuários afiliados.
5. As requisições não poderão constar headers como `set-cookie` e afins.
6. Os parâmetros podem ser enviados na **query string** (`?email=...&from_date=...`) ou no **corpo JSON** da requisição — inclusive nos verbos `GET`.
7. O payload não aceita campos além dos documentados em cada endpoint; campos desconhecidos respondem `422`.

{% hint style="info" %}
**O que mudou:** antes o campo `email` era obrigatório em todas as consultas. Agora ele é opcional.

**Quem já envia `email` ou `user_id` não precisa alterar nada** — o comportamento é idêntico ao de antes. A novidade serve para quem consulta a própria conta e não tinha um e-mail de terceiro para informar.
{% endhint %}

#### Autenticação

Todas as requisições exigem o header:

```
Authorization: Bearer SUA_CHAVE_DE_API
```

#### Código de Erros Comuns

* `400 Bad Request`: A requisição não pôde ser processada devido a um erro genérico.
* `401 Unauthorized`: Sua Chave de API é inválida ou o header `Authorization` está ausente/malformado.
* `403 Forbidden`: Seu usuário não tem a API de Parceiros habilitada, está inativo ou sua chave expirou.
* `404 Not Found`: O usuário solicitado não foi encontrado, está inativo ou não pertence aos seus afiliados.
* `422 Unprocessable Entity`: O payload tem algum problema — campo obrigatório ausente, campo vazio, valor inválido, campo não permitido ou intervalo de datas maior que 90 dias.
* `500 Internal Server Error`: Ocorreu um erro interno ao processar a requisição.

#### Corpo de Resposta de Erros Comuns

```json
{
    "error": "The error message here" 
}
```

***

### Endpoints

#### <mark style="color:green;">GET</mark> /recharges

**Função:** Obter dados de recargas do usuário.

**Parâmetros:**

| Campo       | Obrigatório | Descrição                                                                                                |
| ----------- | ----------- | -------------------------------------------------------------------------------------------------------- |
| `email`     | Não         | E-mail da conta a consultar. Omita para consultar a própria conta do token.                              |
| `user_id`   | Não         | ID da conta a consultar. Alternativa ao `email`.                                                         |
| `from_date` | Não         | Data inicial. Se enviada, `to_date` passa a ser obrigatória.                                             |
| `to_date`   | Não         | Data final. Máximo de 90 dias de diferença para `from_date`. Sem datas, o padrão são os últimos 90 dias. |

**Corpo de envio — consultando a própria conta:**

```json5
{
    // Nenhum campo obrigatório: sem email/user_id, a consulta é da própria conta do token.
    "from_date": "2026-01-01", // Data inicial (Opcional)
    "to_date": "2026-02-01"    // Data final (Opcional)
}
```

**Corpo de envio — consultando uma conta filha:**

```json5
{ 
    "email": "meu_usuario@example.com", // ou "user_id": "9fe358e0-5ffa-4aee-90f4-c010eef2aede"
    "from_date": "2026-01-01",
    "to_date": "2026-02-01"
}
```

**Corpo de resposta:**

```json
{
    "recharges": [
        {
            "created_at": "2026-11-21 00:00:00", // Data da recarga
            "description": "Recarga Direta", // Descrição
            "service": "SMS", // Serviço
            "credits": 0, // Quantidade de créditos recarregados
            "price": 0, // Preço
            "total": 0, // Valor
            "situation": "APPROVED" // Situação
        }, ...
    ]
}
```

***

#### <mark style="color:green;">GET</mark> /sent-messages

**Função:** Obter mensagens enviadas do usuário.

**Parâmetros:** os mesmos de `/recharges`.

**Corpo de envio — consultando a própria conta:**

```json5
{
    "from_date": "2026-01-01", // Opcional
    "to_date": "2026-02-01"    // Opcional
}
```

Uma requisição **sem nenhum parâmetro** também é válida e retorna os últimos 90 dias da própria conta.

**Corpo de envio — consultando uma conta filha:**

```json5
{ 
    "email": "meu_usuario@example.com",
    "from_date": "2026-01-01",
    "to_date": "2026-02-01"
}
```

**Corpo de resposta:**

```json
{
    "sms": 123, // Quantidade de SMS enviados no período
    "whatsapp": 321, // Quantidade de mensagens WhatsApp enviadas no período
    "clicks": 10 // Quantidade de clicks nos links no período
}
```

***

#### <mark style="color:green;">GET</mark> /credits

**Função:** Obter dados de créditos gerais do usuário. Não aceita filtro de datas — devolve sempre a posição atual.

**Parâmetros:**

| Campo     | Obrigatório | Descrição                                                                   |
| --------- | ----------- | --------------------------------------------------------------------------- |
| `email`   | Não         | E-mail da conta a consultar. Omita para consultar a própria conta do token. |
| `user_id` | Não         | ID da conta a consultar. Alternativa ao `email`.                            |

**Corpo de envio — consultando a própria conta:**

```json5
{
    // Corpo vazio, ou nenhum parâmetro na query string.
}
```

**Corpo de envio — consultando uma conta filha:**

```json
{
    "email": "meu_usuario@example.com" 
}
```

**Corpo de resposta:**

```json
{
    "sms": {
        "contracted": 0, // SMS Contratados
        "sent": 0, // SMS Enviados
        "available": 0 // SMS Disponíveis
    },
    "whatsapp": {
        "contracted": 0, // WhatsApp Contratados
        "sent": 0, // WhatsApp Enviados
        "available": 0 // WhatsApp Disponíveis
    },
    "call": {
        "contracted": 0, // Ligações Contratadas
        "sent": 0, // Ligações Enviadas
        "available": 0 // Ligações Disponíveis
    }
}
```

***

#### <mark style="color:green;">GET</mark> /clicks

**Função:** Obter o número de clicks nos envios do usuário.

**Parâmetros:**

| Campo             | Obrigatório | Descrição                                                                             |
| ----------------- | ----------- | ------------------------------------------------------------------------------------- |
| `from_date`       | **Sim**     | Data inicial.                                                                         |
| `to_date`         | **Sim**     | Data final. Máximo de 90 dias de diferença para `from_date`.                          |
| `email`           | Não         | E-mail da conta a consultar. Omita para consultar a própria conta do token.           |
| `user_id`         | Não         | ID da conta a consultar. Alternativa ao `email`.                                      |
| `id`              | Não         | ID da automação ou do broadcast. Se preenchido, retorna dados apenas do ID informado. |
| `include_message` | Não         | `true` para incluir o texto das mensagens na resposta.                                |

{% hint style="warning" %}
Diferente dos demais endpoints, `from_date` e `to_date` são **obrigatórios** em `/clicks`. Sem eles a resposta é `422`.
{% endhint %}

{% hint style="info" %}
As listas `campaigns` e `broadcasts` só aparecem na resposta quando existem campanhas ou broadcasts no período consultado.

Os textos das mensagens (`messages` nas campanhas e `message`, `url`, `short_url` nos broadcasts) só são retornados quando você envia `"include_message": true`.
{% endhint %}

**Corpo de envio — consultando a própria conta:**

{% code fullWidth="false" %}

```json5
{
    "from_date": "2026-01-01", // Obrigatório
    "to_date": "2026-02-01"    // Obrigatório
}
```

{% endcode %}

**Corpo de envio — consultando uma conta filha:**

```json5
{
    "email": "meu_usuario@example.com", // Opcional — omita para consultar a própria conta
    "id": "id da automação ou id do broadcast", // Opcional
    "from_date": "2026-01-01", // Obrigatório
    "to_date": "2026-02-01" // Obrigatório
}
```

**Corpo de resposta:**

```json
{
    "sms": 123, // quantidade total de envios de SMS
    "whatsapp": 321, // quantidade total de envios de mensagem WhatsApp
    "clicks": 10, // quantidade total de clicks
    "campaigns": [
        {
            "id": "9fe358e0-5ffa-4aee-90f4-c010eef2aede", // ID da campanha
            "name": "pix gerado", // nome da campanha de automação 
            "sms": 23, // quantidade de SMS disparados na automação 
            "clicks": 2, // quantidade de clicks da campanha em específico da automação 
            "messages": [
                {
                    "text": "Ola {first_name}, seu pix foi gerado. Clique no link para 
                        efetuar o pagamento: {url_pix}",
                    "interval": 0,
                    "interval_type": "Minuto(s)",
                    "url": "https://meupix.com/019c7116-3c27-797d-91b3-aa49a5bea42a"
                },
                {
                    "text": "Ola {first_name}, ainda nao identificamos o seu pagamento. 
                        Clique no link para o pagamento: {url_pix}",
                    "interval": 5,
                    "interval_type": "Minuto(s)",
                    "url": "https://meupix.com/019c7116-3c27-797d-91b3-aa49a5bea42a"
                }
            ]
        }, 
        { 
            "id": "019c7114-3b02-7d1d-83b9-a8a23052aa37",
            "name": "cartao pago", 
            "sms": 10,
            "clicks": 2, 
            "messages": [
                {
                    "text": "Ola {first_name}, seu pagamento foi confirmado. Em breve
                        voce recebera o codigo de rastreio",
                    "interval": 0,
                    "interval_type": "Minuto(s)",
                    "url": "https://minhaloja.com/ck=019c7116-3c27-797d-91b3-aa49a5bea42a"
                }
            ] 
        }
    ], 
    "broadcasts": [
        { 
            "id": "019c7114-7c4b-77b6-8deb-12cb5c885ea5", // ID do broadcast
            "name": "promoção Carnaval", // nome da campanha do broadcast 
            "total_leads": 100, // quantidade de leads
            "sent": 92, // quantidade de envios (subtraindo inválidos - cancelados)
            "cancelled": 8, // quantidade de envios cancelados (números inválidos)
            "sent_date": "2026-01-30 15:06:54", // Data/hora do envio
            "clicks": 54, // quantidade de clicks da campanha em específico do broadcast 
            "message": "Ola {first_name}, aproveite nossa promocao de carnaval. Faca
                uma compra no link {meu_link} e ganhe 10% OFF", // mensagem enviada,
            "url": "https://minhaloja.com/ck=019c7116-3c27&c=10OFF",
            "short_url": "https://gosite.cc/3c2710of"
        }, 
        {
            "id": "019c7114-abab-7da9-9a46-4da50bd49147",
            "name": "aposta tbt", 
            "total_leads": 5800,
            "sent": 5671,
            "cancelled": 129,
            "clicks": 4684, 
            "message": "MINHASORTE: aproveite nossa promocao aposta TBT e faca
                uma aposta no link {meu_link} e ganhe 10% de cashback", // mensagem enviada,
            "url": "https://minhaloja.com/?source=sms&p=cash10",
            "short_url": "https://gosite.cc/3c4810of"
        }
    ]
}
```

***

#### <mark style="color:green;">GET</mark> /phones-blacklist

Recupera todos os números da blacklist da conta informada — ou **da própria conta do token**, se você não informar `email` nem `user_id`. A paginação é de 1.000 registros por vez.

**Parâmetros:**

| Campo      | Obrigatório | Descrição                                                    |
| ---------- | ----------- | ------------------------------------------------------------ |
| `email`    | Não         | E-mail da conta. Omita para operar a própria conta do token. |
| `user_id`  | Não         | ID da conta. Alternativa ao `email`.                         |
| `page`     | Não         | Página desejada. Padrão `1`.                                 |
| `per_page` | Não         | Registros por página. Padrão `1000`, máximo `5000`.          |

**Corpo de envio — própria conta:**

```json5
{
    // Campos opcionais de paginação
    "page": 1,
    "per_page": 1000
}
```

**Corpo de envio — conta filha:**

```json
{
    "email": "johndoe@email.com",
    "page": 1,
    "per_page": 1000
}
```

**Corpo de resposta:**

```json
{
    "phones": [
        "51989261101",
        "51989261102",
        "51989261103"
    ],
    "total": 6000,
    "page": 1,
    "per_page": 3,
    "total_pages": 2000
}
```

***

#### <mark style="color:green;">POST</mark> /phones-blacklist

Inclusão de telefones na blacklist da conta informada — ou **da própria conta do token**, se você não informar `email` nem `user_id`. Caso o número seja inválido, ele será descartado, não sendo incluído na blacklist.

**Parâmetros:**

| Campo     | Obrigatório | Descrição                                                    |
| --------- | ----------- | ------------------------------------------------------------ |
| `phones`  | **Sim**     | Array de telefones.                                          |
| `email`   | Não         | E-mail da conta. Omita para operar a própria conta do token. |
| `user_id` | Não         | ID da conta. Alternativa ao `email`.                         |

**Corpo de envio — própria conta:**

```json
{
    "phones": [
        51989261101,
        "51989261103"
    ]
}
```

**Corpo de envio — conta filha:**

```json
{
    "email": "johndoe@email.com",
    "phones": [
        51989261101,
        "51989261103"
    ]
}
```

**Corpo de resposta:**

```json
{
    "message": "Blacklist atualizada com sucesso",
    "phones_count": 2
}
```

***

#### <mark style="color:red;">DELETE</mark> /phones-blacklist

Exclusão de telefones da blacklist da conta informada — ou **da própria conta do token**, se você não informar `email` nem `user_id`.

**Parâmetros:** os mesmos do `POST /phones-blacklist`.

**Corpo de envio — própria conta:**

```json
{
    "phones": [
        51989261101,
        "51989261103"
    ]
}
```

**Corpo de envio — conta filha:**

```json
{
    "email": "johndoe@email.com",
    "phones": [
        51989261101,
        "51989261103"
    ]
}
```

**Corpo de resposta:**

```json
{
    "message": "Números removidos da blacklist com sucesso",
    "phones_removed": 2
}
```

***

#### Perguntas frequentes

**Já uso `email` em todas as chamadas. Preciso mudar alguma coisa?**\
Não. O comportamento de quem envia `email` ou `user_id` é exatamente o mesmo de antes.

**Sou uma conta que não gerencia outras contas. O que faço?**\
Simplesmente não envie `email` nem `user_id`. A consulta será da sua própria conta.

**Enviei `"email": ""` e recebi `422`. Por quê?**\
Uma chave presente e vazia é tratada como erro, não como "consulta a mim mesmo" — assim uma variável não preenchida na sua integração não vira, sem aviso, uma consulta de outra conta. Para consultar a própria conta, remova a chave do payload.

**Posso consultar qualquer conta pelo `email`?**\
Não. A conta indicada precisa ser a sua própria ou uma conta filha ativa sua. Qualquer outra responde `404`.

#### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.

***


# Quickstart

Este guia mostra, em poucos minutos, como autenticar-se e realizar seu **primeiro envio de SMS** através da API Oficial da SMS Funnel.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

#### Pré-requisitos

1. Ter a **API de Parceiros habilitada** em sua conta. Caso ainda não tenha, entre em contato com nosso suporte.
2. Possuir uma **Chave de API** válida. Veja como gerá-la em Autenticação.
3. Possuir **créditos de SMS** disponíveis em sua conta.

***

### Passo 1 — Autenticação

Todas as requisições devem conter o header `Authorization` com sua Chave de API no formato `Bearer`.

```json
Authorization: Bearer <SUA CHAVE DE API AQUI>
```

{% hint style="danger" %}
**ATENÇÃO:** Não compartilhe sua Chave de API com terceiros. Este conteúdo é de uso confidencial e exclusivo seu.
{% endhint %}

***

### Passo 2 — Enviar seu primeiro SMS

<mark style="color:green;">**POST**</mark>**&#x20;/sms/messages**

**Função:** Enfileira o disparo de uma ou mais mensagens SMS. Cada chamada gera um **broadcast** que agrupa as mensagens enviadas.

**Exemplo de requisição:**

{% code title="cURL" %}

```bash
curl -X POST "https://web.smsfunnel.com.br/api/parceiros/v1/sms/messages" \
  -H "Authorization: Bearer <SUA CHAVE DE API AQUI>" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "to": "5511999999999",
        "message": "Ola! Esta e sua primeira mensagem via SMS Funnel."
      }
    ]
  }'
```

{% endcode %}

**Corpo de envio:**

```json5
{
    "messages": [ // Lista de mensagens (1 a 5000)
        {
            "to": "5511999999999", // Telefone do destinatário (10 a 15 dígitos, com DDI/DDD)
            "message": "Ola! Esta e sua primeira mensagem via SMS Funnel.", // Texto (1 a 1530 caracteres)
            "reference": "pedido-123" // Opcional: seu identificador. Se omitido, geramos um automaticamente.
        }
    ]
}
```

**Corpo de resposta — `202 Accepted`:**

```json5
{
    "broadcast_id": "019c7114-7c4b-77b6-8deb-12cb5c885ea5", // ID do broadcast gerado
    "messages": [
        {
            "id": "019c7114-abab-7da9-9a46-4da50bd49147", // ID da mensagem
            "reference": "pedido-123", // Sua referência (ou a gerada por nós)
            "phone": "5511999999999", // Telefone normalizado
            "status": "queued", // Status inicial: enfileirada
            "bill_factor": 1 // Quantidade de créditos consumidos por esta mensagem
        }
    ],
    "unsupported_features": [] // Recursos solicitados que não foram aplicados, se houver
}
```

{% hint style="info" %}
O status `202 Accepted` indica que as mensagens foram **aceitas e enfileiradas** para envio. O disparo ocorre de forma assíncrona — utilize o **Passo 3** para acompanhar o andamento.
{% endhint %}

***

### Passo 3 — Acompanhar o envio

<mark style="color:green;">**GET**</mark>**&#x20;/sms/broadcasts/{broadcast\_id}**

**Função:** Consulta os detalhes e as métricas de um broadcast. Utilize o `broadcast_id` retornado no Passo 2.

**Exemplo de requisição:**

{% code title="cURL" %}

```bash
curl -X GET "https://web.smsfunnel.com.br/api/parceiros/v1/sms/broadcasts/019c7114-7c4b-77b6-8deb-12cb5c885ea5" \
  -H "Authorization: Bearer <SUA CHAVE DE API AQUI>"
```

{% endcode %}

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "019c7114-7c4b-77b6-8deb-12cb5c885ea5", // ID do broadcast
    "name": "Broadcast #019c7114", // Nome do broadcast
    "message": "Ola! Esta e sua primeira mensagem via SMS Funnel.", // Mensagem enviada
    "status": "completed", // Status atual do broadcast
    "scheduled_date": null, // Data de agendamento, se houver
    "leads_count": 1, // Quantidade de destinatários
    "partner_reference": null, // Referência informada por você no envio
    "flash_sms": false, // Se foi enviado como flash SMS
    "concat": false, // Se a concatenação de mensagens longas está ativa
    "from": null, // Remetente personalizado, se houver
    "idempotency_key": "019c7114-...", // Chave de idempotência do envio
    "created_at": "2026-06-30 12:00:00", // Data/hora de criação
    "metrics": {
        "sent": 1, // Mensagens enviadas
        "failed": 0, // Mensagens com falha
        "total": 1 // Total de mensagens
    }
}
```

***

### Personalizando com "Meu Link"

Para incluir um link encurtado e rastreável em sua mensagem, utilize a variável `{meu_link}` no texto e informe o campo `url` na requisição. Nós encurtamos o link automaticamente e contabilizamos os cliques.

```json5
{
    "url": "https://minhaloja.com/promo", // Obrigatório quando a mensagem contém {meu_link}
    "messages": [
        {
            "to": "5511999999999",
            "message": "Aproveite nossa promocao! Acesse: {meu_link}"
        }
    ]
}
```

{% hint style="warning" %}
O campo `url` é **obrigatório** quando alguma mensagem contém `{meu_link}` e **não deve ser enviado** caso nenhuma mensagem utilize a variável.
{% endhint %}

***

### Evitando envios duplicados (Idempotência)

Para garantir que um reenvio da mesma requisição (por exemplo, após um timeout de rede) não gere disparos duplicados, envie o header opcional `Idempotency-Key` com um valor único por operação (máx. 80 caracteres).

```json
Idempotency-Key: pedido-123-envio-01
```

Se recebermos uma nova requisição com a mesma chave em até **24 horas**, retornamos a **mesma resposta original** sem reprocessar o envio. Nesses casos, a resposta incluirá o header `Idempotency-Replayed: true`.

***

### Código de Erros Comuns

* `401 Unauthorized`: Chave de API ausente, inválida ou não foi possível identificar o parceiro.
* `402 Payment Required` (`INSUFFICIENT_CREDITS`): Créditos de SMS insuficientes para o envio.
* `403 Forbidden`: Sua conta não possui a API de Parceiros habilitada.
* `404 Not Found`: Recurso (broadcast/campanha) não encontrado ou não pertence à sua conta.
* `409 Conflict`: Operação não permitida no estado atual (ex.: cancelar broadcast já finalizado) ou requisição concorrente.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo da requisição é inválido.
* `429 Too Many Requests` (`RATE_LIMIT_EXCEEDED`): Limite de **60 requisições por minuto** excedido. Consulte o header `Retry-After`.
* `502 Bad Gateway` (`SHORTENER_UNAVAILABLE`): Falha temporária ao encurtar o link de "Meu Link". Tente novamente.
* `500 Internal Server Error`: Ocorreu um erro interno ao processar a requisição.

**Corpo de resposta de erro:**

```json5
{
    "error": {
        "code": "VALIDATION_ERROR", // Código do erro
        "message": "Mensagem descritiva do erro", // Descrição
        "details": { // Opcional: detalhes por campo (presente em erros de validação)
            "messages.0.to": [
                "O campo to e obrigatorio."
            ]
        }
    }
}
```


# Listas

As **Listas** permitem agrupar e gerenciar seus leads. Através destes endpoints você pode criar listas, consultar, atualizar, remover e gerenciar os leads contidos em cada uma delas.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
Os endpoints de listagem utilizam **paginação simples**. A resposta traz `current_page`, `data`, `per_page`, `next_page_url` e `prev_page_url` — não há campos `total` ou `last_page`. Para navegar, utilize o parâmetro de query `page`.
{% endhint %}

***

### Endpoints

<mark style="color:green;">**GET**</mark>**&#x20;/lists**

**Função:** Lista todas as suas listas, da mais recente para a mais antiga.

**Parâmetros de query (opcionais):**

```json5
{
    "per_page": 20, // Itens por página. Padrão: 20. Mínimo: 1, Máximo: 100.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "018f8e2a-1c4d-7a3b-9e21-3f6a2b8c1d0e", // ID da lista
            "name": "Black Friday VIP", // Nome da lista
            "leads_count": 1280, // Quantidade de leads na lista
            "campaigns_count": 2, // Quantidade de campanhas que utilizam a lista
            "created_at": "2026-06-29T14:03:11+00:00", // Data de criação
            "updated_at": "2026-06-29T14:03:11+00:00" // Data da última atualização
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/lists?page=1",
    "from": 1,
    "next_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/lists?page=2",
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/lists",
    "per_page": 20,
    "prev_page_url": null,
    "to": 20
}
```

***

<mark style="color:green;">**POST**</mark>**&#x20;/lists**

**Função:** Cria uma nova lista.

**Corpo de envio:**

```json5
{
    "name": "Black Friday VIP" // Obrigatório. String de 1 a 100 caracteres.
}
```

**Corpo de resposta — `201 Created`:**

```json5
{
    "id": "018f8e2a-1c4d-7a3b-9e21-3f6a2b8c1d0e", // ID da lista criada
    "name": "Black Friday VIP",
    "leads_count": 0,
    "campaigns_count": 0,
    "created_at": "2026-06-30T12:00:00+00:00",
    "updated_at": "2026-06-30T12:00:00+00:00"
}
```

***

<mark style="color:green;">**GET**</mark>**&#x20;/lists/{id}**

**Função:** Obtém os detalhes de uma lista específica.

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "018f8e2a-1c4d-7a3b-9e21-3f6a2b8c1d0e",
    "name": "Black Friday VIP",
    "leads_count": 1280,
    "campaigns_count": 2,
    "created_at": "2026-06-29T14:03:11+00:00",
    "updated_at": "2026-06-29T14:03:11+00:00"
}
```

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/lists/{id}**

**Função:** Atualiza (renomeia) uma lista existente.

**Corpo de envio:**

```json5
{
    "name": "Black Friday VIP 2026" // Opcional. Se informado, deve ter de 1 a 100 caracteres.
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "018f8e2a-1c4d-7a3b-9e21-3f6a2b8c1d0e",
    "name": "Black Friday VIP 2026",
    "leads_count": 1280,
    "campaigns_count": 2,
    "created_at": "2026-06-29T14:03:11+00:00",
    "updated_at": "2026-06-30T12:05:00+00:00"
}
```

***

<mark style="color:red;">**DELETE**</mark>**&#x20;/lists/{id}**

**Função:** Remove uma lista.

**Corpo de resposta:** `204 No Content` (sem corpo).

{% hint style="warning" %}
Não é possível remover uma lista que ainda possua **campanhas vinculadas** a ela. Nesse caso, a resposta será `409 Conflict` com o código `LIST_HAS_DEPENDENT_CAMPAIGNS` e a lista das campanhas dependentes em `details.dependent_campaigns`.
{% endhint %}

***

<mark style="color:green;">**GET**</mark>**&#x20;/lists/{id}/leads**

**Função:** Lista os leads contidos em uma lista, do mais recente para o mais antigo.

**Parâmetros de query (opcionais):**

```json5
{
    "per_page": 50, // Itens por página. Padrão: 50. Mínimo: 1, Máximo: 200.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "018f8e2a-9a1b-7c2d-8e3f-aa11bb22cc33", // ID do lead
            "phone": "5511999998888", // Telefone (apenas dígitos)
            "name": "Maria Silva", // Nome do lead
            "email": "maria@example.com", // E-mail (pode ser nulo)
            "custom_fields": ["Sao Paulo", "premium"], // Campos personalizados (até 3)
            "blacklisted": false, // Indica se o número está na blacklist
            "created_at": "2026-06-29T14:05:00+00:00" // Data de criação
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/lists/018f8e2a-1c4d-7a3b-9e21-3f6a2b8c1d0e/leads?page=1",
    "from": 1,
    "next_page_url": null,
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/lists/018f8e2a-1c4d-7a3b-9e21-3f6a2b8c1d0e/leads",
    "per_page": 50,
    "prev_page_url": null,
    "to": 1
}
```

***

<mark style="color:green;">**POST**</mark>**&#x20;/lists/{id}/leads**

**Função:** Insere um lote de leads na lista. Leads aceitos (não pertencentes à blacklist) acionam automaticamente as campanhas/sequências ativas vinculadas à lista.

**Corpo de envio:**

```json5
{
    "leads": [ // Obrigatório. Lista de 1 a 500 leads.
        {
            "phone": "5511999998888", // Obrigatório. 10 a 15 dígitos, com DDI/DDD (aceita "+" inicial).
            "name": "Maria Silva", // Obrigatório. Até 150 caracteres.
            "email": "maria@example.com", // Opcional. E-mail válido, até 150 caracteres.
            "custom_fields": ["Sao Paulo", "premium"] // Opcional. Até 3 valores, até 255 caracteres cada.
        }
    ]
}
```

{% hint style="info" %}
**Idempotência (opcional):** envie o header `Idempotency-Key` (máx. 80 caracteres) para evitar inserções duplicadas. Se recebermos a mesma chave para a mesma lista em até **24 horas**, retornamos a resposta original sem reprocessar, incluindo o header `Idempotency-Replayed: true`.
{% endhint %}

**Corpo de resposta — `202 Accepted`:**

```json5
{
    "accepted_count": 2, // Leads aceitos e enfileirados
    "blacklisted_count": 1, // Leads inseridos, porém na blacklist (não disparam sequências)
    "leads": [
        { "lead_id": "018f8e2a-...-1", "status": "accepted", "blacklisted": false },
        { "lead_id": "018f8e2a-...-2", "status": "accepted", "blacklisted": false },
        { "lead_id": "018f8e2a-...-3", "status": "blacklisted", "blacklisted": true }
    ]
}
```

***

<mark style="color:red;">**DELETE**</mark>**&#x20;/lists/{id}/leads/{lead\_id}**

**Função:** Remove um lead específico de uma lista.

**Corpo de resposta:** `204 No Content` (sem corpo).

***

### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `404 Not Found` (`NOT_FOUND`): A lista (ou o lead) não foi encontrada ou não pertence à sua conta.
* `409 Conflict` (`LIST_HAS_DEPENDENT_CAMPAIGNS`): Tentativa de remover uma lista com campanhas vinculadas.
* `409 Conflict` (`CONCURRENT_REQUEST`): Já existe uma requisição em processamento com o mesmo `Idempotency-Key`.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo da requisição é inválido.

**Corpo de resposta de erro:**

```json5
{
    "error": {
        "code": "VALIDATION_ERROR", // Código do erro
        "message": "The given data was invalid.", // Descrição
        "details": { // Opcional: detalhes por campo (presente em erros de validação)
            "name": [
                "Field `name` is required."
            ]
        }
    }
}
```

#### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.


# Campanhas

As **Campanhas** definem fluxos automatizados de mensagens (sequências) disparados aos leads de uma lista. Através destes endpoints você pode criar, consultar, atualizar e remover suas campanhas.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
O endpoint de listagem utiliza **paginação simples**. A resposta traz `current_page`, `data`, `per_page`, `next_page_url` e `prev_page_url` — não há campos `total` ou `last_page`. Para navegar, utilize o parâmetro de query `page`.
{% endhint %}

***

### Endpoints

<mark style="color:green;">**GET**</mark>**&#x20;/sms/campaigns**

**Função:** Lista todas as suas campanhas, da mais recente para a mais antiga.

**Parâmetros de query (opcionais):**

```json5
{
    "per_page": 20, // Itens por página. Padrão: 20. Mínimo: 1, Máximo: 100.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "018f9c2a-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID da campanha
            "name": "Black Friday", // Nome da campanha
            "active": true, // Indica se a campanha está ativa
            "lead_list_id": "018f9c2a-1111-7c4e-9a1d-aaaabbbbcccc", // ID da lista vinculada
            "created_at": "2026-06-30T14:05:00+00:00", // Data de criação
            "updated_at": "2026-06-30T14:05:00+00:00" // Data da última atualização
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/campaigns?page=1",
    "from": 1,
    "next_page_url": null,
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/campaigns",
    "per_page": 20,
    "prev_page_url": null,
    "to": 1
}
```

{% hint style="info" %}
A listagem **não** retorna o detalhamento das sequências. Para obter as sequências de uma campanha, utilize o endpoint de consulta individual (`GET /sms/campaigns/{id}`).
{% endhint %}

***

<mark style="color:green;">**POST**</mark>**&#x20;/sms/campaigns**

**Função:** Cria uma nova campanha. Caso não informe `lead_list_id`, uma lista própria será criada e vinculada automaticamente. As sequências podem ser criadas junto, no mesmo corpo da requisição.

**Corpo de envio:**

```json5
{
    "name": "Black Friday", // Obrigatório. String de 1 a 50 caracteres.
    "active": true, // Opcional. Padrão: true.
    "lead_list_id": "018f9c2a-1111-7c4e-9a1d-aaaabbbbcccc", // Opcional. ID de uma lista sua. Se omitido, criamos uma lista automaticamente.
    "sequences": [ // Opcional. Até 50 sequências.
        {
            "interval": 1, // Obrigatório (se houver sequências). Inteiro >= 1.
            "interval_type_id": 3, // Obrigatório. 1=Minuto, 2=Hora, 3=Dia, 4=Semana, 5=Mês.
            "text": "Ola {first_name}, confira {meu_link}", // Obrigatório. 1 a 1530 caracteres.
            "active": true, // Opcional. Padrão: true.
            "url": "https://example.com/promo" // Opcional. Obrigatório quando o texto contém {meu_link}.
        }
    ]
}
```

{% hint style="warning" %}
Quando o texto de uma sequência contém a variável `{meu_link}`, o campo `url` é **obrigatório** naquela sequência (e vice-versa). Nós encurtamos o link automaticamente e o retornamos em `short_url`.
{% endhint %}

**Corpo de resposta — `201 Created`:**

```json5
{
    "id": "018f9c2a-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "name": "Black Friday",
    "active": true,
    "lead_list_id": "018f9c2a-1111-7c4e-9a1d-aaaabbbbcccc",
    "created_at": "2026-06-30T14:05:00+00:00",
    "updated_at": "2026-06-30T14:05:00+00:00",
    "sequences": [
        {
            "id": "018f9c2a-9999-7c4e-9a1d-1234567890ab", // ID da sequência
            "campaign_id": "018f9c2a-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID da campanha
            "interval": 1, // Intervalo
            "interval_type_id": 3, // Tipo de intervalo (3 = Dia)
            "position": 1, // Posição na sequência
            "text": "Ola {first_name}, confira {meu_link}", // Texto da mensagem
            "active": true, // Sequência ativa
            "url": "https://example.com/promo", // Link "Meu Link" (ou nulo)
            "short_url": "https://go.site/abc123", // Link encurtado (ou nulo)
            "created_at": "2026-06-30T14:05:00+00:00",
            "updated_at": "2026-06-30T14:05:00+00:00"
        }
    ]
}
```

***

<mark style="color:green;">**GET**</mark>**&#x20;/sms/campaigns/{id}**

**Função:** Obtém os detalhes de uma campanha específica, incluindo suas sequências.

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "018f9c2a-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "name": "Black Friday",
    "active": true,
    "lead_list_id": "018f9c2a-1111-7c4e-9a1d-aaaabbbbcccc",
    "created_at": "2026-06-30T14:05:00+00:00",
    "updated_at": "2026-06-30T14:05:00+00:00",
    "sequences": [
        {
            "id": "018f9c2a-9999-7c4e-9a1d-1234567890ab",
            "campaign_id": "018f9c2a-7b31-7c4e-9a1d-2f3b4c5d6e7f",
            "interval": 1,
            "interval_type_id": 3,
            "position": 1,
            "text": "Ola {first_name}, confira {meu_link}",
            "active": true,
            "url": "https://example.com/promo",
            "short_url": "https://go.site/abc123",
            "created_at": "2026-06-30T14:05:00+00:00",
            "updated_at": "2026-06-30T14:05:00+00:00"
        }
    ]
}
```

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/sms/campaigns/{id}**

**Função:** Atualiza uma campanha. Apenas os campos `name` e `active` podem ser alterados.

**Corpo de envio:**

```json5
{
    "name": "Black Friday 2026", // Opcional. Se informado, 1 a 50 caracteres.
    "active": false // Opcional. Boolean.
}
```

{% hint style="warning" %}
Os campos `lead_list_id` e `sequences` **não** podem ser enviados neste endpoint — o envio retornará `422 VALIDATION_ERROR`. A lista e as sequências são definidas apenas na criação da campanha.
{% endhint %}

**Corpo de resposta — `200 OK`:** mesmo formato do `GET /sms/campaigns/{id}`.

***

<mark style="color:red;">**DELETE**</mark>**&#x20;/sms/campaigns/{id}**

**Função:** Remove uma campanha. As sequências vinculadas são removidas automaticamente em cascata.

**Corpo de resposta:** `204 No Content` (sem corpo).

***

### Tabela de tipos de intervalo

| `interval_type_id` | Tipo   |
| ------------------ | ------ |
| 1                  | Minuto |
| 2                  | Hora   |
| 3                  | Dia    |
| 4                  | Semana |
| 5                  | Mês    |

***

### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `404 Not Found` (`NOT_FOUND`): A campanha (ou a lista informada em `lead_list_id`) não foi encontrada ou não pertence à sua conta.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo da requisição é inválido (inclui enviar `lead_list_id`/`sequences` no `PUT`, ou `{meu_link}` sem `url`).
* `500 Internal Server Error` (`INTERNAL_ERROR`): Ocorreu um erro interno ao processar a requisição.

**Corpo de resposta de erro:**

```json5
{
    "error": {
        "code": "VALIDATION_ERROR", // Código do erro
        "message": "The given data was invalid.", // Descrição
        "details": { // Opcional: detalhes por campo (presente em erros de validação)
            "name": [
                "Field `name` is required."
            ]
        }
    }
}
```

#### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.


# Sequências

As **Sequências** são as mensagens que compõem uma campanha. Cada sequência define o texto, o intervalo de envio e, opcionalmente, um link rastreável. Através destes endpoints você pode listar, criar, consultar, atualizar e remover sequências de uma campanha.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
Os endpoints de listagem de Sequências retornam um **array direto** (sem envelope `data` e sem paginação). Os endpoints de recurso único retornam um **objeto direto**.
{% endhint %}

***

### Endpoints

<mark style="color:green;">**GET**</mark>**&#x20;/sms/sequences/interval-types**

**Função:** Lista os tipos de intervalo disponíveis, para uso no campo `interval_type_id`.

**Corpo de resposta — `200 OK`:**

```json5
[
    { "id": 1, "name": "MINUTE", "type": "MINUTE", "label": "Minuto(s)" },
    { "id": 2, "name": "HOUR",   "type": "HOUR",   "label": "Hora(s)" },
    { "id": 3, "name": "DAY",    "type": "DAY",    "label": "Dia(s)" },
    { "id": 4, "name": "WEEK",   "type": "WEEK",   "label": "Semana(s)" },
    { "id": 5, "name": "MONTH",  "type": "MONTH",  "label": "Mês(ses)" }
]
```

***

<mark style="color:green;">**GET**</mark>**&#x20;/sms/campaigns/{campaign\_id}/sequences**

**Função:** Lista todas as sequências de uma campanha, ordenadas pela posição.

**Corpo de resposta — `200 OK`:**

```json5
[
    {
        "id": "0190c8e2-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID da sequência
        "campaign_id": "0190aaaa-1111-7c4e-9a1d-aaaabbbbcccc", // ID da campanha
        "interval": 1, // Intervalo
        "interval_type_id": 3, // Tipo de intervalo (3 = Dia)
        "position": 1, // Posição na sequência
        "text": "Ola {first_name}, confira: {meu_link}", // Texto da mensagem
        "active": true, // Sequência ativa
        "url": "https://example.com/promo", // Link "Meu Link" (ou nulo)
        "short_url": "https://go.site/abc123", // Link encurtado (ou nulo)
        "created_at": "2026-06-30T12:00:00-03:00",
        "updated_at": "2026-06-30T12:00:00-03:00"
    }
]
```

***

<mark style="color:green;">**POST**</mark>**&#x20;/sms/campaigns/{campaign\_id}/sequences**

**Função:** Cria uma nova sequência dentro de uma campanha. A `position` é atribuída automaticamente (sempre ao final).

**Corpo de envio:**

```json5
{
    "interval": 2, // Obrigatório. Inteiro >= 1.
    "interval_type_id": 2, // Obrigatório. 1=Minuto, 2=Hora, 3=Dia, 4=Semana, 5=Mês.
    "text": "Promo: {meu_link}", // Obrigatório. 1 a 1600 caracteres.
    "active": true, // Opcional. Padrão: true.
    "url": "https://example.com/promo" // Opcional. Obrigatório quando o texto contém {meu_link}.
}
```

{% hint style="warning" %}
Quando o texto contém a variável `{meu_link}`, o campo `url` é **obrigatório** (e vice-versa). Nós encurtamos o link automaticamente e o retornamos em `short_url`.
{% endhint %}

**Corpo de resposta — `201 Created`:**

```json5
{
    "id": "0190c8e2-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "campaign_id": "0190aaaa-1111-7c4e-9a1d-aaaabbbbcccc",
    "interval": 2,
    "interval_type_id": 2,
    "position": 2,
    "text": "Promo: {meu_link}",
    "active": true,
    "url": "https://example.com/promo",
    "short_url": "https://go.site/abc123",
    "created_at": "2026-06-30T12:05:00-03:00",
    "updated_at": "2026-06-30T12:05:00-03:00"
}
```

***

<mark style="color:green;">**GET**</mark>**&#x20;/sms/sequences/{id}**

**Função:** Obtém os detalhes de uma sequência específica.

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "0190c8e2-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "campaign_id": "0190aaaa-1111-7c4e-9a1d-aaaabbbbcccc",
    "interval": 2,
    "interval_type_id": 2,
    "position": 2,
    "text": "Promo: {meu_link}",
    "active": true,
    "url": "https://example.com/promo",
    "short_url": "https://go.site/abc123",
    "created_at": "2026-06-30T12:05:00-03:00",
    "updated_at": "2026-06-30T12:05:00-03:00"
}
```

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/sms/sequences/{id}**

**Função:** Atualiza uma sequência. Todos os campos são opcionais — apenas os enviados serão alterados.

**Corpo de envio:**

```json5
{
    "interval": 3, // Opcional. Inteiro >= 1.
    "interval_type_id": 3, // Opcional. 1=Minuto, 2=Hora, 3=Dia, 4=Semana, 5=Mês.
    "text": "Ola {first_name}, confira: {meu_link}", // Opcional. 1 a 1600 caracteres.
    "active": false, // Opcional. Boolean.
    "url": "https://example.com/nova-promo" // Opcional. Envie null para remover o link.
}
```

{% hint style="info" %}
A `position` da sequência **não** é alterável por este endpoint. A regra do `{meu_link}` ⇄ `url` também se aplica aqui, considerando os valores efetivos (campos enviados + valores atuais).
{% endhint %}

**Corpo de resposta — `200 OK`:** mesmo formato do `GET /sms/sequences/{id}`.

***

<mark style="color:red;">**DELETE**</mark>**&#x20;/sms/sequences/{id}**

**Função:** Remove uma sequência.

**Corpo de resposta:** `204 No Content` (sem corpo).

***

### Tabela de tipos de intervalo

| `interval_type_id` | Tipo   |
| ------------------ | ------ |
| 1                  | Minuto |
| 2                  | Hora   |
| 3                  | Dia    |
| 4                  | Semana |
| 5                  | Mês    |

***

### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `404 Not Found` (`NOT_FOUND`): A campanha ou a sequência não foi encontrada ou não pertence à sua conta.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo da requisição é inválido (inclui `{meu_link}` sem `url`, ou `url` sem `{meu_link}` no texto).

**Corpo de resposta de erro:**

```json5
{
    "error": {
        "code": "VALIDATION_ERROR", // Código do erro
        "message": "The given data was invalid.", // Descrição
        "details": { // Opcional: detalhes por campo (presente em erros de validação)
            "url": [
                "O campo url é obrigatório quando a mensagem contém a TAG {meu_link}."
            ]
        }
    }
}
```

#### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.


# Envios de uma Sequência

O que aconteceu com os SMS que uma sequência disparou

Enquanto os endpoints de Sequências definem o que será enviado, este endpoint mostra o que aconteceu com cada mensagem já disparada por uma sequência: se saiu, quando saiu e, quando a operadora informa, se foi entregue.\
\
É o equivalente de broadcasts/{id}/contacts para a automação.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
Diferente dos endpoints de Sequências, este retorna um envelope paginado (com data). A paginação não traz total nem last\_page — para percorrer a lista inteira, siga o next\_page\_url até que venha null.
{% endhint %}

***

### Endpoints

<mark style="color:green;">**GET**</mark> /sms/sequences/{idSequencia}/messages

**Função:** Lista os envios de SMS feitos por uma sequência, do mais antigo para o mais recente.

**Corpo de resposta — `200 OK`:**

```json5
{
  "current_page": 1,
  "data": [
    {
      "id": "019ff28e-0085-71c3-8832-ce476be5f754",
      "phone": "5511999990001",
      "message": "Olá Fulano, sua oferta está ativa!",
      "status": "delivered",
      "cancelled": false,
      "created_at": "2026-08-11T14:23:01-03:00",
      "sent_at": "2026-08-11T14:25:13-03:00"
    }
  ],
  "per_page": 50,
  "from": 1,
  "to": 1,
  "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/sequences/{id}/messages?page=1",
  "next_page_url": null,
  "prev_page_url": null,
  "path": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/sequences/{id}/messages"
}
```

***

### Tabela de status

{% hint style="info" %}
Esses são os mesmos valores aceitos no filtro ?status=. Qualquer outro valor retorna 422.
{% endhint %}

| `status`           | Significado                                                           |
| ------------------ | --------------------------------------------------------------------- |
| queued             | Na fila, ainda não despachado                                         |
| sent\_to\_carrier  | Despachado da nossa fila para a operadora                             |
| delivered          | Entrega confirmada pela operadora                                     |
| blocked\_blacklist | Bloqueado por blacklist                                               |
| cancelled          | Não saiu: cancelamento seu, crédito insuficiente ou telefone inválido |

{% hint style="warning" %}
**sent\_to\_carrier** pode ser o estado final, pois nem todos os retornos podem chegar. Use delivered como confirmação positiva, mas não interprete sent\_to\_carrier como pendência que será resolvida — ela pode permanecer assim para sempre, mesmo tendo sido entregue.
{% endhint %}

{% hint style="info" %}
Envios anteriores a 12 de agosto de 2026 não aparecem. Eles não possuem identificador público nem retorno da operadora.&#x20;
{% endhint %}

***

### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `404 Not Found` (`NOT_FOUND`): A sequência não foi encontrada ou não pertence à sua conta.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo da requisição é inválido (inclui `{meu_link}` sem `url`, ou `url` sem `{meu_link}` no texto).

**Corpo de resposta de erro:**

```json5
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid status filter.",
    "details": {
      "status": [
        "Expected one of: queued, sent_to_carrier, delivered, blocked_blacklist, blocked_content, cancelled."
      ]
    }
  }
}
```

#### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.


# Broadcasts

Os **Broadcasts** representam o envio de SMS em massa. Através destes endpoints você pode disparar mensagens, listar e consultar seus envios, acompanhar o status individual de cada mensagem e cancelar um envio em andamento.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
Os endpoints de listagem utilizam **paginação simples**. A resposta traz `current_page`, `data`, `per_page`, `next_page_url` e `prev_page_url` — não há campos `total` ou `last_page`. Para navegar, utilize o parâmetro de query `page`.
{% endhint %}

#### Status possíveis

**Status do broadcast:** `pending`, `scheduled`, `sending`, `sent`, `sent_with_errors`, `cancelled`, `sanitizing`, `importing`.

**Status da mensagem:** `queued` (aceita, aguardando disparo), `sent_to_carrier` (despachada para envio — estado final da maioria dos envios), `delivered` (entrega confirmada no aparelho), `cancelled` (não saiu: cancelada por você, telefone inválido ou crédito insuficiente — crédito estornado), `blocked_blacklist` (não saiu: telefone na sua blacklist — crédito estornado) e `blocked_content` (não saiu: barrada por política de conteúdo).

***

### Endpoints

<mark style="color:green;">**POST**</mark>**&#x20;/sms/messages**

**Função:** Enfileira o disparo de uma ou mais mensagens SMS. Cada chamada gera um ou mais broadcasts (um por grupo de agendamento) que agrupam as mensagens enviadas.

**Corpo de envio:**

```json5
{
    // Campos globais (opcionais) - aplicados a todas as mensagens, salvo quando sobrescritos por mensagem.
    "from": "SMSFunnel", // Remetente personalizado (até 20 caracteres)
    "flashSms": false, // Envio como flash SMS
    "concat": true, // Concatenação de mensagens longas
    "schedule": "2026-07-01T10:00:00Z", // Agendamento global
    "url": "https://example.com/promo", // "Meu Link". Obrigatório quando alguma mensagem contém {meu_link}.
    "messages": [ // Obrigatório. De 1 a 5000 mensagens.
        {
            "to": "+5511999999999", // Obrigatório. 10 a 15 dígitos, com DDI/DDD (aceita "+").
            "message": "Oferta: {meu_link}", // Obrigatório. 1 a 1530 caracteres.
            "reference": "order-123" // Opcional. Seu identificador (até 100 caracteres). Se omitido, geramos um.
        },
        {
            "to": "5511888888888",
            "message": "Ola!",
            "schedule": "2026-07-01T11:00:00", // Sobrescreve o agendamento global desta mensagem
            "from": "LOJA" // Sobrescreve o remetente global desta mensagem
        }
    ]
}
```

{% hint style="info" %}
**Agendamento (`schedule`):** aceita os formatos `2026-07-01T10:00:00Z` (ISO 8601 com offset), `2026-07-01T10:00:00` (ISO 8601 sem offset) ou `2026-07-01 10:00:00`. A data deve ser futura. Os campos por mensagem têm prioridade sobre os globais.
{% endhint %}

{% hint style="warning" %}
Quando alguma mensagem contém a variável `{meu_link}`, o campo `url` é **obrigatório** (e vice-versa). Nós encurtamos o link automaticamente e substituímos `{meu_link}` pelo link curto em cada mensagem.
{% endhint %}

{% hint style="info" %}
**Idempotência (opcional):** envie o header `Idempotency-Key` (máx. 80 caracteres) para evitar envios duplicados. Se recebermos a mesma chave em até **24 horas**, retornamos a resposta original sem reprocessar, incluindo o header `Idempotency-Replayed: true`.
{% endhint %}

**Corpo de resposta — `202 Accepted`:**

```json5
{
    "broadcast_id": "9b1f8e2a-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID do broadcast gerado
    "messages": [
        {
            "id": "0190c8e2-abab-7da9-9a46-4da50bd49147", // ID da mensagem (external_message_id)
            "reference": "order-123", // Sua referência (ou a gerada por nós)
            "phone": "5511999999999", // Telefone normalizado
            "status": "queued", // Status inicial
            "bill_factor": 1 // Créditos consumidos por esta mensagem (sempre 1)
        }
    ],
    "unsupported_features": ["flashSms", "concat", "from"] // Recursos solicitados que não foram aplicados, se houver
}
```

***

<mark style="color:green;">**GET**</mark>**&#x20;/sms/broadcasts**

**Função:** Lista seus broadcasts, do mais recente para o mais antigo.

**Parâmetros de query (opcionais):**

```json5
{
    "status": "sending", // Filtra por status (ver lista de status do broadcast)
    "start_date": "2026-06-01", // Filtra por created_at >= data (YYYY-MM-DD)
    "end_date": "2026-06-30", // Filtra por created_at <= data (YYYY-MM-DD)
    "text": "Carnaval", // Busca pelo nome do broadcast
    "per_page": 50, // Itens por página. Padrão: 50. Mínimo: 1, Máximo: 200.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "9b1f8e2a-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID do broadcast
            "name": "Campanha X", // Nome do broadcast
            "message": "Oferta: {meu_link}", // Mensagem enviada
            "status": "sending", // Status atual
            "scheduled_date": "2026-07-01T10:00:00-03:00", // Data de agendamento (ou nulo)
            "leads_count": 1500, // Quantidade de destinatários
            "partner_reference": "ref-abc", // Sua referência
            "flash_sms": false, // Enviado como flash SMS
            "concat": true, // Concatenação ativa
            "from": "LOJA", // Remetente personalizado (ou nulo)
            "idempotency_key": "key-123", // Chave de idempotência
            "created_at": "2026-06-30T14:00:00-03:00" // Data de criação
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/broadcasts?page=1",
    "from": 1,
    "next_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/broadcasts?page=2",
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/broadcasts",
    "per_page": 50,
    "prev_page_url": null,
    "to": 50
}
```

***

<mark style="color:green;">**GET**</mark>**&#x20;/sms/broadcasts/{id}**

**Função:** Obtém os detalhes de um broadcast específico, incluindo suas métricas.

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "9b1f8e2a-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "name": "Campanha X",
    "message": "Oferta: {meu_link}",
    "status": "sent_with_errors",
    "scheduled_date": null,
    "leads_count": 1500,
    "partner_reference": "ref-abc",
    "flash_sms": false,
    "concat": true,
    "from": "LOJA",
    "idempotency_key": "key-123",
    "created_at": "2026-06-30T14:00:00-03:00",
    "metrics": {
        "sent": 1450, // Mensagens enviadas/entregues
        "failed": 50, // Mensagens com falha/não entregues
        "total": 1500 // Total de mensagens
    }
}
```

***

<mark style="color:green;">**GET**</mark>**&#x20;/sms/broadcasts/{id}/contacts**

**Função:** Lista os contatos (mensagens individuais) de um broadcast.

**Parâmetros de query (opcionais):**

```json5
{
    "phone": "5511", // Busca por trecho do telefone
    "status": "delivered", // Filtra por status (ver tabela abaixo)
    "per_page": 50, // Itens por página. Padrão: 50. Mínimo: 1, Máximo: 200.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "0190c8e2-abab-7da9-9a46-4da50bd49147", // ID da mensagem (external_message_id)
            "reference": "order-123", // Sua referência
            "phone": "5511999999999", // Telefone
            "message": "Ola!", // Mensagem enviada ao contato
            "status": "delivered", // Status da mensagem
            "cancelled": false, // Indica se o envio foi cancelado
            "created_at": "2026-06-30T14:00:01-03:00",
            "sent_at": "2026-06-30T14:00:13-03:00", // Despacho para a operadora
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/broadcasts/9b1i.../contacts?page=1",
    "from": 1,
    "next_page_url": null,
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/sms/broadcasts/9b1i.../contacts",
    "per_page": 50,
    "prev_page_url": null,
    "to": 1
}
```

### Tabela de status

{% hint style="info" %}
Esses são os mesmos valores aceitos no filtro ?status=. Qualquer outro valor retorna 422.
{% endhint %}

{% hint style="warning" %}
Cuidado com o ?status= na página de listagem de broadcasts. Esse filtro difere do **GET /sms/broadcasts** (a lista de broadcasts), o ?status= de lá aceita um vocabulário diferente: **pending, scheduled, sent, sent\_with\_errors, sending, sanitizing, importing, cancelled.** Os status aqui se refente a status de envio de cada lead entre processos internos e retornos da operadora.
{% endhint %}

| `status`           | Significado                                                           |
| ------------------ | --------------------------------------------------------------------- |
| queued             | Na fila, ainda não despachado                                         |
| sent\_to\_carrier  | Despachado da nossa fila para a operadora                             |
| delivered          | Entrega confirmada pela operadora                                     |
| blocked\_blacklist | Bloqueado por blacklist                                               |
| cancelled          | Não saiu: cancelamento seu, crédito insuficiente ou telefone inválido |

{% hint style="warning" %}
**sent\_to\_carrier** pode ser o estado final, pois nem todos os retornos podem chegar. Use delivered como confirmação positiva, mas não interprete sent\_to\_carrier como pendência que será resolvida — ela pode permanecer assim para sempre, mesmo tendo sido entregue.
{% endhint %}

{% hint style="info" %}
Envios anteriores a 12 de agosto de 2026 não aparecem. Eles não possuem identificador público nem retorno da operadora.&#x20;
{% endhint %}

***

<mark style="color:green;">**GET**</mark>**&#x20;/sms/messages/{external\_message\_id}**

**Função:** Consulta o status de uma mensagem específica, pelo `id` retornado no envio.

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "0190c8e2-abab-7da9-9a46-4da50bd49147", // ID da mensagem (external_message_id)
    "reference": "order-123", // Sua referência
    "phone": "5511999999999", // Telefone
    "status": "delivered", // Status da mensagem
    "sent_at": "2026-06-30T14:00:05-03:00", // Data de envio (ou nulo)
}
```

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/sms/broadcasts/{id}/cancel**

**Função:** Cancela um broadcast. Apenas broadcasts nos status `pending`, `scheduled` ou `sending` podem ser cancelados.

**Corpo de resposta:**

* `200 OK` — broadcast `pending` ou `scheduled`: cancelamento completo (`cancellation: "complete"`).
* `202 Accepted` — broadcast `sending`: cancelamento parcial; mensagens já enfileiradas ainda podem ser disparadas (`cancellation: "partial"`).

```json5
{
    "id": "9b1f8e2a-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "name": "Campanha X",
    "message": "...",
    "status": "cancelled",
    "scheduled_date": "2026-07-01T10:00:00-03:00",
    "leads_count": 1500,
    "partner_reference": "ref-abc",
    "flash_sms": false,
    "concat": true,
    "from": "LOJA",
    "idempotency_key": "key-123",
    "created_at": "2026-06-30T14:00:00-03:00",
    "cancellation": "complete" // "complete" (200) ou "partial" (202)
}
```

{% hint style="warning" %}
Broadcasts já finalizados (`sent`, `sent_with_errors`) ou já cancelados (`cancelled`) retornam `409 INVALID_STATUS_TRANSITION`, com o status atual em `details.current_status`.
{% endhint %}

***

### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `402 Payment Required` (`INSUFFICIENT_CREDITS`): Créditos de SMS insuficientes para o envio.
* `404 Not Found` (`NOT_FOUND`): O broadcast ou a mensagem não foi encontrada ou não pertence à sua conta.
* `409 Conflict` (`INVALID_STATUS_TRANSITION`): O broadcast não pode ser cancelado no status atual.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo da requisição é inválido (inclui `{meu_link}` sem `url` e `Idempotency-Key` acima de 80 caracteres).
* `429 Too Many Requests` (`RATE_LIMIT_EXCEEDED`): Limite de **60 requisições por minuto** excedido. Consulte o header `Retry-After`.
* `502 Bad Gateway` (`SHORTENER_UNAVAILABLE`): Falha temporária ao encurtar o link de "Meu Link". Tente novamente.

**Corpo de resposta de erro:**

```json5
{
    "error": {
        "code": "INVALID_STATUS_TRANSITION", // Código do erro
        "message": "Broadcast cannot be cancelled in its current status.", // Descrição
        "details": { // Opcional: detalhes adicionais
            "current_status": "sent"
        }
    }
}
```

#### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.


# Ligações - Áudios

Os **Áudios** são as gravações que a plataforma toca quando disca para os seus contatos. Através destes endpoints você pode criar um áudio (enviando um arquivo ou gerando a locução a partir de um texto), listar, consultar, renomear, configurar as ações de teclado (DTMF) e remover.

É o mesmo recurso da tela **Áudios** do painel: o áudio criado pela API aparece lá, e vice-versa. O mesmo áudio serve tanto para um **Broadcast de Voz** quanto para uma **sequência de voz** dentro de uma campanha.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
Os endpoints de listagem utilizam **paginação simples**. A resposta traz `current_page`, `data`, `per_page`, `next_page_url` e `prev_page_url` — não há campos `total` ou `last_page`. Para navegar, utilize o parâmetro de query `page`.
{% endhint %}

{% hint style="info" %}
Os endpoints ficam sob `/calls/`. O prefixo é do **canal**, não do fornecedor — os endpoints legados `/call4u/*` (ligação avulsa) continuam existindo e não mudaram.
{% endhint %}

**Fluxo de uso**

```
1. POST /calls/audios          → cria o áudio                (audio_id)
2. GET  /calls/audios/{id}     → repete até "ready": true
3. POST /calls/broadcasts      → dispara usando o audio_id
```

**Status possíveis**

**Status do áudio (`status`):** `processing` (medindo duração e transcodificando), `approved` (pronto para uso), `rejected` (recusado — veja `rejection_reason`) e `error` (falha no processamento).

{% hint style="warning" %}
Não interprete o `status` diretamente. Use o booleano **`ready`**: ele é `true` apenas quando o áudio já foi processado e pode ser usado em um disparo. Enquanto `ready` for `false`, os campos `duration_seconds` e `credits_per_call` ainda valem `0`.
{% endhint %}

**Créditos**

O custo é de **1 crédito de ligação a cada 30 segundos de áudio**, arredondado para cima, por ligação **atendida** — é o valor devolvido em `credits_per_call`. Multiplicado pelo tamanho da lista, ele dá o custo máximo de um disparo.

Criar um áudio por **upload não consome crédito**. Criar por **TTS consome**, porque a locução é gerada por um provedor pago — inclusive na pré-escuta (`preview-tts`).

***

#### Endpoints

<mark style="color:green;">**GET**</mark>**&#x20;/calls/audios**

**Função:** Lista os seus áudios, do mais recente para o mais antigo.

**Parâmetros de query (opcionais):**

```json5
{
    "ready": true, // Quando true, devolve apenas os áudios já processados (prontos para uso)
    "per_page": 50, // Itens por página. Padrão: 50. Mínimo: 1, Máximo: 200.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "0192f100-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID do áudio (audio_id)
            "title": "Oferta de agosto", // Nome do áudio
            "type": "tts", // "tts" (locução gerada) ou "upload" (arquivo enviado)
            "status": "approved", // Status do processamento
            "ready": true, // true = pode ser usado em um disparo
            "duration_seconds": 42, // Duração medida. Vale 0 enquanto ready for false
            "credits_per_call": 2, // Créditos por ligação atendida. Vale 0 enquanto ready for false
            "file_name": "tts_66c1f0a9e21b4.mp3", // Nome do arquivo
            "tts_text": "Olá! Temos uma oferta especial...", // Texto da locução ou transcrição informada
            "tts_voice_id": "21m00Tcm4TlvDq8ikWAM", // Voz utilizada (apenas em type "tts")
            "created_at": "2026-08-18T12:00:00+00:00",
            "updated_at": "2026-08-18T12:00:41+00:00",
            "actions": [ // Ações de teclado (DTMF) configuradas
                {
                    "dtmf_key": "1", // Tecla digitada pelo contato
                    "action_type": "mark_interested", // Ação executada
                    "action_params": {} // Parâmetros da ação, quando houver
                }
            ]
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/calls/audios?page=1",
    "from": 1,
    "next_page_url": null,
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/calls/audios",
    "per_page": 50,
    "prev_page_url": null,
    "to": 1
}
```

{% hint style="info" %}
O campo `rejection_reason` só aparece quando o `status` é `rejected` ou `error`, explicando o motivo da recusa.

O caminho do arquivo (`file_path`/`file_url`) **não** faz parte do contrato: o áudio é servido ao discador, não ao integrador.
{% endhint %}

***

<mark style="color:green;">**POST**</mark>**&#x20;/calls/audios**

**Função:** Cria um áudio. Há dois caminhos, escolhidos pelo campo `type`: enviar um arquivo (`upload`) ou deixar a plataforma gerar a locução a partir de um texto (`tts`).

**Corpo de envio — locução gerada (`type: "tts"`, `application/json`):**

```json5
{
    "title": "Oferta de agosto", // Obrigatório. Até 255 caracteres.
    "type": "tts", // Obrigatório. "upload" ou "tts".
    "tts_text": "Olá! Temos uma oferta. Digite 1 para falar com um consultor.", // Obrigatório quando type = "tts". Até 5000
caracteres.
    "tts_voice_id": "21m00Tcm4TlvDq8ikWAM", // Opcional. Ausente = voz padrão da plataforma.
    "actions": [ // Opcional. Até 12 ações de teclado (DTMF). Ver tabela abaixo.
        {
            "dtmf_key": "1", // Obrigatório. Uma tecla: 0-9, * ou #.
            "action_type": "mark_interested" // Obrigatório. Ver tabela de ações.
        }
    ]
}
```

**Corpo de envio — arquivo (`type: "upload"`, `multipart/form-data`):**

| Campo      | Valor                                                                                                     |
| ---------- | --------------------------------------------------------------------------------------------------------- |
| `title`    | Obrigatório. Até 255 caracteres.                                                                          |
| `type`     | Obrigatório. `upload`.                                                                                    |
| `file`     | Obrigatório. MP3, WAV, OGG ou M4A. Até **10 MB** e **30 segundos**.                                       |
| `tts_text` | Opcional. Transcrição do áudio, para você reconhecê-lo depois sem precisar ouvi-lo.                       |
| `actions`  | Opcional. Neste formato, envie como **string JSON**: `[{"dtmf_key":"1","action_type":"mark_interested"}]` |

**Corpo de resposta — `202 Accepted`:**

```json5
{
    "id": "0192f100-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "title": "Oferta de agosto",
    "type": "tts",
    "status": "processing", // Nasce sempre em processing
    "ready": false, // Ainda não pode ser usado em um disparo
    "duration_seconds": 0, // Só é medida ao final do processamento
    "credits_per_call": 0, // Só é calculado ao final do processamento
    "file_name": "tts_66c1f0a9e21b4.mp3",
    "tts_text": "Olá! Temos uma oferta. Digite 1 para falar com um consultor.",
    "tts_voice_id": "21m00Tcm4TlvDq8ikWAM",
    "created_at": "2026-08-18T12:00:00+00:00",
    "updated_at": "2026-08-18T12:00:00+00:00",
    "actions": [
        { "dtmf_key": "1", "action_type": "mark_interested", "action_params": {} }
    ]
}
```

{% hint style="warning" %}
**A resposta é `202`, e não `201`.** O áudio já existe, mas **ainda não serve para disparar**: a plataforma precisa medir a duração e transcodificá-lo para o formato que o discador reproduz.

Consulte `GET /calls/audios/{id}` até que `ready` seja `true`. Só então `duration_seconds` e `credits_per_call` são válidos, e só então um disparo aceita este `audio_id` (antes disso, `422 AUDIO_NOT_READY`).
{% endhint %}

{% hint style="warning" %}
**Áudio acima de 30 segundos é recusado — inclusive no TTS.** No TTS a recusa acontece **depois** de gerar a locução: um texto longo custa a chamada ao provedor e ainda assim retorna `422`. Encurte o texto antes de enviar.
{% endhint %}

***

<mark style="color:green;">**GET**</mark>**&#x20;/calls/audios/{id}**

**Função:** Consulta um áudio específico. A resposta tem exatamente o mesmo formato dos itens de `data` na listagem.

É este o endpoint a ser consultado repetidamente após a criação, até que `ready` seja `true`.

Áudio inexistente ou de outra conta retorna `404 NOT_FOUND`.

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/calls/audios/{id}** **Função:** Renomeia o áudio e reconfigura as suas ações de teclado (DTMF). Atualização parcial: envie apenas os campos que deseja alterar.

**Corpo de envio:**

```json5
{
    "title": "Oferta de agosto — v2", // Opcional. Até 255 caracteres.
    "tts_text": "Transcrição corrigida", // Opcional. Corrige apenas o texto gravado (ver aviso abaixo).
    "actions": [ // Opcional. Presente, SUBSTITUI o conjunto inteiro de ações.
        { "dtmf_key": "1", "action_type": "mark_interested" }
    ]
}
```

**Corpo de resposta — `200 OK`:** o áudio atualizado, no mesmo formato do `GET /calls/audios/{id}`.

{% hint style="warning" %}
**O campo `actions`, quando presente, substitui todas as ações existentes.** Envie `[]` para remover todas, ou **omita o campo** para preservar as atuais.
{% endhint %}

{% hint style="warning" %}
**Este endpoint não troca o áudio.** Os campos `type`, `file` e `tts_voice_id` são recusados com `422`, e `tts_text` aqui **apenas corrige a transcrição gravada — não regera a locução**. O que o contato escuta continua sendo o mesmo arquivo.

Para mudar o áudio, crie outro e aponte o disparo para o novo `audio_id`. O motivo: a duração já entrou no cálculo de créditos dos disparos que usam este áudio, e trocar o arquivo mudaria o custo de um disparo em andamento.
{% endhint %}

***

<mark style="color:red;">**DELETE**</mark>**&#x20;/calls/audios/{id}**

**Função:** Remove o áudio. A remoção apaga **o registro e o arquivo**.

**Corpo de resposta:** `204 No Content`.

{% hint style="warning" %}
Como o arquivo é apagado, um áudio em uso não pode ser removido — as próximas ligações tocariam um arquivo inexistente. Retorna `409 AUDIO_IN_USE` quando:

* o áudio é usado por um **broadcast de voz** que ainda não encerrou (qualquer status que não seja `completed` ou `cancelled`) — os IDs vêm em `details.broadcast_ids`;
* o áudio é usado por uma **sequência de voz** de uma campanha, **ativa ou não** — os IDs vêm em `details.sequence_ids`. Uma automação não "termina" sozinha: troque o áudio da sequência (`PUT /sms/sequences/{id}`) ou remova a sequência antes.

Broadcast já `completed` ou `cancelled` **não** impede a remoção — o histórico e os relatórios dele seguem intactos.
{% endhint %}

***

<mark style="color:green;">**POST**</mark>**&#x20;/calls/audios/preview-tts**

**Função:** Gera a locução e devolve o **binário MP3** (`Content-Type: audio/mpeg`), **sem gravar nada**. Existe para quem monta a própria tela de criação de áudio e precisa deixar o usuário ouvir antes de confirmar.

**Corpo de envio:**

```json5
{
    "tts_text": "Olá! Temos uma oferta especial para você.", // Obrigatório. Até 5000 caracteres.
    "tts_voice_id": "21m00Tcm4TlvDq8ikWAM" // Opcional. Ausente = voz padrão da plataforma.
}
```

**Corpo de resposta — `200 OK`:** o arquivo MP3 em binário. É o **único** endpoint da API de Parceiros que não responde JSON.

{% hint style="danger" %}
**Cada chamada é paga.** Mesmo sem criar nenhum áudio, cada pré-escuta é uma chamada ao provedor de locução e exige saldo de créditos de ligação. Não utilize em laço de teste.
{% endhint %}

***

#### Ações de teclado (DTMF)

Uma ação define o que acontece quando o contato digita uma tecla durante a ligação. Elas são configuradas no áudio, em `actions`, e valem para todos os disparos que o utilizam.

\| `action_type` | `action_params` | O que faz\
\| | ----------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- | | `mark_interested` | — | Marca o contato como interessado\
\| | `mark_not_interested` | — | Marca o contato como não interessado\
\| | `add_tag` | `tag_id` | Adiciona a tag ao contato\
\| | `remove_tag` | `tag_id` | Remove a tag do contato\
\| | `move_to_list` | `list_id` | **Move** o lead para outra lista sua | | `add_to_list` | `list_id` | **Copia** o lead para outra lista sua (mantém na atual) | | `trigger_sequence` | `sequence_id` | Inscreve o lead em uma sequência sua | | `webhook` | `url` | Envia um `POST` ao seu endereço com `{lead_id, name, phone, dtmf_key, sequence_id, broadcast_call_id, action_type}` |

**Exemplo com parâmetros:**

```json5
{
    "actions": [
        { "dtmf_key": "1", "action_type": "mark_interested" },
        { "dtmf_key": "2", "action_type": "move_to_list", "action_params": { "list_id": "0192e789-..." } },
        { "dtmf_key": "9", "action_type": "webhook", "action_params": { "url": "https://seu-dominio.com/dtmf" } }
    ]
}
```

* `dtmf_key` é **uma** tecla do teclado telefônico: `0` a `9`, `*` ou `#`.
* **Uma ação por tecla.** Repetir a mesma tecla no array retorna `422`: a plataforma executaria apenas uma delas, e não haveria como dizer qual.
* São aceitas no máximo **12 ações** por áudio.
* `list_id` e `sequence_id` precisam pertencer **à sua conta**. Um ID desconhecido e um ID de outro parceiro retornam o mesmo `422`, com a mesma mensagem — de propósito, para não revelar o que existe na conta alheia.
* A `url` de `webhook` não pode apontar para endereços privados ou internos (`422`).

***

#### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `402 Payment Required` (`INSUFFICIENT_CREDITS`): Créditos de ligação insuficientes. Ocorre apenas em `type: "tts"` e em `preview-tts` — o upload de arquivo não consome crédito. O saldo atual vem em `details.available_credits`.
* `404 Not Found` (`NOT_FOUND`): O áudio não foi encontrado ou não pertence à sua conta.
* `409 Conflict` (`AUDIO_IN_USE`): O áudio está em uso e não pode ser removido. Os IDs vêm em `details.broadcast_ids` e `details.sequence_ids`.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo da requisição é inválido — inclui arquivo que não é um áudio válido, áudio acima de 30 segundos, tecla DTMF repetida, ação sem o parâmetro obrigatório e `list_id`/`sequence_id` que não pertence à sua conta. à sua conta.
* `429 Too Many Requests` (`RATE_LIMIT_EXCEEDED`): Limite de **60 requisições por minuto** excedido. Consulte o header `Retry-After`.
* `502 Bad Gateway` (`AUDIO_PROVIDER_ERROR`): O provedor de locução falhou. Nada foi gravado; repetir a chamada mais tarde costuma resolver.
* `502 Bad Gateway` (`AUDIO_UPLOAD_FAILED`): Falha ao armazenar o arquivo enviado. Nada foi gravado.

**Corpo de resposta de erro:**

```json5
{   
    "error": {
        "code": "AUDIO_IN_USE", // Código do erro
        "message": "O áudio está em uso por broadcast de voz que ainda não encerrou. Cancele ou aguarde o broadcast antes de
remover.", // Descrição
        "details": { // Opcional: detalhes adicionais
            "broadcast_ids": ["9b1f8e2a-7b31-7c4e-9a1d-2f3b4c5d6e7f"],
            "sequence_ids": []
        }
    }
}
```

**Dúvidas?**

Em caso de dúvidas, entre em contato com nosso suporte.


# Ligações - Broadcasts

Um **Broadcast de Voz** disca para **todos os leads de uma lista**, toca um áudio gravado e reporta o desfecho de cada ligação — atendeu, escutou até o fim, digitou uma tecla, não atendeu.

É o equivalente exato dos Broadcasts de SMS — mesmo nome e mesmo formato — e o mesmo recurso da tela **Broadcasts de Voz** do painel: o disparo criado pela API aparece lá, e vice-versa.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
Os endpoints de listagem utilizam **paginação simples**. A resposta traz `current_page`, `data`, `per_page`, `next_page_url` e `prev_page_url` — não há campos `total` ou `last_page`. Para navegar, utilize o parâmetro de query `page`.
{% endhint %}

{% hint style="info" %}
Os endpoints ficam sob `/calls/`. O prefixo é do **canal**, não do fornecedor — os endpoints legados `/call4u/*` (ligação avulsa) continuam existindo e não mudaram.
{% endhint %}

**Fluxo completo**

```
1. POST /calls/audios                    → cria o áudio      (audio_id)
2. POST /lists                           → cria a lista      (list_id)
3. POST /lists/{id}/leads                → insere os telefones
4. POST /calls/broadcasts                → cria e dispara    (broadcast_id)
5. GET  /calls/broadcasts/{id}/contacts  → desfecho de cada ligação
```

Os telefones **sempre** vêm de uma Lista — não há inserção de números no corpo do disparo. O caminho da lista já normaliza o telefone, aplica a blacklist e é idempotente por `Idempotency-Key`, e a mesma lista pode alimentar uma campanha de SMS e um disparo de voz.

**Status possíveis**

**Status do broadcast:** `pending` (criado, importando os contatos), `processing` (discando), `paused` (pausado), `completed` (encerrado), `cancelled` (cancelado).

**Desfecho de cada ligação:** `waiting`, `no_answer`, `answered_hangup`, `answered_listened`, `answered_dtmf`, `cancelled` e `error` — a tabela completa está mais abaixo.

{% hint style="warning" %}
**Janela de silêncio: 23:00–07:30 (America/Sao\_Paulo).** Nada é discado nesse intervalo. Um `scheduled_date` que caia dentro dele é **reagendado para 07:30**, e não recusado — o valor devolvido na resposta é o horário efetivo.
{% endhint %}

**Créditos**

O custo é de **1 crédito de ligação a cada 30 segundos de áudio**, arredondado para cima, por ligação **atendida**. O valor por ligação é o `credits_per_call` do áudio.

{% hint style="info" %}
**Saldo parcial não impede a criação.** Apenas o saldo **zerado** retorna `402 INSUFFICIENT_CREDITS`. Ter menos crédito que o `estimated_credits` é aceito de propósito: quando o crédito acaba no meio do disparo, o contato volta para a fila em vez de se perder, e é discado novamente após a recarga.
{% endhint %}

***

#### Endpoints

<mark style="color:green;">**POST**</mark>**&#x20;/calls/broadcasts**

**Função:** Cria o disparo e inicia a importação dos telefones da lista.

**Corpo de envio:**

```json5
{
    "name": "Promoção de Agosto", // Obrigatório. De 3 a 100 caracteres.
    "lead_list_id": "0192e789-7b31-7c4e-9a1d-2f3b4c5d6e7f", // Obrigatório. Lista sua, com ao menos 1 lead.
    "audio_id": "0192f100-7b31-7c4e-9a1d-2f3b4c5d6e7f", // Obrigatório. Áudio seu e já processado ("ready": true).
    "max_attempts": 3, // Opcional. De 1 a 5. Padrão: 1. Tentativas por telefone quando não atende.
    "retry_interval_minutes": 60, // Opcional. De 1 a 120. Padrão: 30. Intervalo entre as tentativas.
    "scheduled_date": "2026-08-21T13:00:00-03:00", // Opcional. Data futura. Ausente = disca imediatamente.
    "scheduled_end_date": "2026-08-21T20:00:00-03:00", // Opcional. Fim da janela: nada é discado depois disso.
    "postback_url": "https://meu-sistema.com/webhooks/ligacoes" // Opcional. Até 2048 caracteres. Recebe o desfecho de cada ligação.
}
```

**Corpo de resposta — `201 Created`:**

```json5
{
    "id": "0192f200-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID do broadcast
    "name": "Promoção de Agosto",
    "status": "pending", // Nasce em pending e vai para processing quando a importação termina
    "lead_list_id": "0192e789-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "audio_id": "0192f100-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "max_attempts": 3,
    "retry_interval_minutes": 60,
    "scheduled_date": "2026-08-21T16:00:00+00:00", // Horário EFETIVO, já ajustado pela janela de silêncio
    "scheduled_end_date": "2026-08-21T23:00:00+00:00",
    "postback_url": "https://meu-sistema.com/webhooks/ligacoes",
    "total_contacts": 0, // Contatos já importados. Nasce 0: a importação é assíncrona
    "total_credits_used": 0, // Créditos efetivamente consumidos até agora
    "created_at": "2026-08-21T12:00:00+00:00",
    "updated_at": "2026-08-21T12:00:00+00:00",
    "estimated_credits": 1200, // Custo se TODOS os contatos forem discados e atenderem
    "credits_per_call": 2, // Créditos por ligação atendida (vem do áudio)
    "leads_count": 600 // Leads encontrados na lista no momento da criação
}
```

{% hint style="info" %}
`estimated_credits`, `credits_per_call` e `leads_count` aparecem **apenas nesta resposta de criação** — eles são conhecidos no momento do disparo. O custo real acumulado vive em `total_credits_used`, disponível em todas as consultas.
{% endhint %}

{% hint style="warning" %}
As validações são checadas **antes** de qualquer coisa ser gravada, nesta ordem:

1. Lista inexistente ou de outra conta → `404 NOT_FOUND`
2. Áudio inexistente ou de outra conta → `404 NOT_FOUND`
3. Áudio ainda em processamento → `422 AUDIO_NOT_READY` (com `details.audio_status`)
4. Lista sem nenhum lead → `422 EMPTY_LEAD_LIST`
5. Saldo de ligação zerado → `402 INSUFFICIENT_CREDITS`
   {% endhint %}

***

<mark style="color:green;">**GET**</mark>**&#x20;/calls/broadcasts**

**Função:** Lista os seus broadcasts de voz, do mais recente para o mais antigo.

**Parâmetros de query (opcionais):**

```json5
{
    "status": "processing", // Filtra por status: pending, processing, paused, completed, cancelled
    "start_date": "2026-08-01", // Filtra por created_at >= data (YYYY-MM-DD)
    "end_date": "2026-08-31", // Filtra por created_at <= data (YYYY-MM-DD)
    "text": "Promoção", // Busca parcial pelo nome do broadcast
    "per_page": 50, // Itens por página. Padrão: 50. Mínimo: 1, Máximo: 200.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "0192f200-7b31-7c4e-9a1d-2f3b4c5d6e7f",
            "name": "Promoção de Agosto",
            "status": "processing",
            "lead_list_id": "0192e789-7b31-7c4e-9a1d-2f3b4c5d6e7f",
            "audio_id": "0192f100-7b31-7c4e-9a1d-2f3b4c5d6e7f",
            "max_attempts": 3,
            "retry_interval_minutes": 60,
            "scheduled_date": "2026-08-21T16:00:00+00:00",
            "scheduled_end_date": null,
            "postback_url": "https://meu-sistema.com/webhooks/ligacoes",
            "total_contacts": 600, // Contatos importados
            "total_credits_used": 742, // Créditos consumidos até agora
            "created_at": "2026-08-21T12:00:00+00:00",
            "updated_at": "2026-08-21T16:41:02+00:00"
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/calls/broadcasts?page=1",
    "from": 1,
    "next_page_url": null,
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/calls/broadcasts",
    "per_page": 50,
    "prev_page_url": null,
    "to": 1
}
```

{% hint style="info" %}
Um valor desconhecido em `?status=` é **ignorado** (a listagem vem completa), em vez de retornar erro ou uma lista vazia sem explicação.
{% endhint %}

***

<mark style="color:green;">**GET**</mark>**&#x20;/calls/broadcasts/{id}**

**Função:** Detalhe do broadcast, acrescido do bloco `report` — o mesmo recorte da tela de acompanhamento do painel.

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "0192f200-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "name": "Promoção de Agosto",
    "status": "processing",
    "lead_list_id": "0192e789-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "audio_id": "0192f100-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "max_attempts": 3,
    "retry_interval_minutes": 60,
    "scheduled_date": "2026-08-21T16:00:00+00:00",
    "scheduled_end_date": null,
    "postback_url": "https://meu-sistema.com/webhooks/ligacoes",
    "total_contacts": 600,
    "total_credits_used": 742,
    "created_at": "2026-08-21T12:00:00+00:00",
    "updated_at": "2026-08-21T16:41:02+00:00",
    "report": {
        "total_sent": 600, // Ligações despachadas
        "total_refused": 210, // Não atendeu, ocupado, congestionado ou indisponível
        "total_answered_hangup": 150, // Atendeu e desligou antes do fim do áudio
        "total_answered_listened": 180, // Atendeu e escutou 95% ou mais do áudio
        "total_answered_dtmf": 60, // Atendeu e digitou uma tecla
        "avg_duration_seconds": 21.4, // Duração média DAS ATENDIDAS
        "avg_listened_percentage": 71.83, // Percentual médio ouvido nas atendidas
        "total_credits_used": 742
    }
}
```

{% hint style="info" %}
O `report` não conta os contatos cancelados nem os que ainda estão na fila — por isso, num broadcast em andamento, a soma dos desfechos é menor que `total_contacts`.
{% endhint %}

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/calls/broadcasts/{id}**

**Função:** Atualização parcial do broadcast. Envie apenas os campos que deseja alterar.

**Corpo de envio:**

```json5
{
    "name": "Promoção de Agosto — v2", // Opcional. De 3 a 100 caracteres.
    "audio_id": "0192f101-...", // Opcional. Áudio seu e já processado.
    "max_attempts": 2, // Opcional. De 1 a 5.
    "retry_interval_minutes": 45, // Opcional. De 1 a 120.
    "scheduled_date": "2026-08-22T09:00:00-03:00", // Opcional. Data futura.
    "scheduled_end_date": "2026-08-22T20:00:00-03:00", // Opcional. Posterior ao início efetivo.
    "postback_url": null // Opcional. Envie null para DESLIGAR o postback.
}
```

**Corpo de resposta — `200 OK`:** o broadcast atualizado, no mesmo formato da listagem (sem o bloco `report`).

{% hint style="warning" %}
**`lead_list_id` é proibido neste endpoint** (`422`). A lista já foi copiada para os contatos durante a importação, e trocá-la deixaria o broadcast apontando para uma lista que não corresponde ao que foi discado. Para outra lista, crie outro broadcast.
{% endhint %}

{% hint style="warning" %}
**A rediscagem só vale para importações futuras.** `max_attempts` e `retry_interval_minutes` são **copiados para cada contato** no momento da importação. Alterá-los depois que a importação terminou **não** reescreve os contatos já criados. É o mesmo comportamento do painel.
{% endhint %}

Broadcast já encerrado (`completed` ou `cancelled`) não pode ser editado → `409 INVALID_STATUS_TRANSITION`, com o status atual em `details.current_status`.

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/calls/broadcasts/{id}/cancel**

**Função:** Cancela o broadcast. Permitido nos status `pending`, `processing` e `paused` — nos demais, `409 INVALID_STATUS_TRANSITION`.

**Corpo de resposta:**

* `200 OK` — broadcast `pending` ou `paused`: cancelamento completo (`"cancellation": "complete"`), nada estava no ar.
* `202 Accepted` — broadcast `processing`: cancelamento parcial (`"cancellation": "partial"`); ligações já publicadas no discador ainda podem completar.

```json5
{
    "id": "0192f200-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "name": "Promoção de Agosto",
    "status": "cancelled",
    "lead_list_id": "0192e789-...",
    "audio_id": "0192f100-...",
    "max_attempts": 3,
    "retry_interval_minutes": 60,
    "scheduled_date": "2026-08-21T16:00:00+00:00",
    "scheduled_end_date": null,
    "postback_url": "https://meu-sistema.com/webhooks/ligacoes",
    "total_contacts": 600,
    "total_credits_used": 742,
    "created_at": "2026-08-21T12:00:00+00:00",
    "updated_at": "2026-08-21T17:10:00+00:00",
    "cancellation": "partial" // "complete" (200) ou "partial" (202)
}
```

Os contatos ainda não discados recebem o desfecho `cancelled`; quem já foi discado mantém o desfecho original.

***

<mark style="color:blue;">**PUT**</mark>**&#x20;/calls/broadcasts/{id}/pause**

<mark style="color:blue;">**PUT**</mark>**&#x20;/calls/broadcasts/{id}/resume**

**Função:** Interrompe e retoma a discagem.

* `pause` aceita `pending` e `processing` → passa para `paused`.
* `resume` aceita apenas `paused` → volta para `processing`. Fora dessas transições, `409 INVALID_STATUS_TRANSITION` com o status atual em `details.current_status`.

**Corpo de resposta — `200 OK`:** o broadcast atualizado, no mesmo formato da listagem.

{% hint style="info" %}
Pausar **não** descarta contatos: o que faltava discar continua na fila e volta a ser processado no `resume`.
{% endhint %}

***

<mark style="color:green;">**GET**</mark>**&#x20;/calls/broadcasts/{id}/contacts**

**Função:** Lista os contatos do broadcast com o desfecho individual de cada ligação.

**Parâmetros de query (opcionais):**

```json5
{
    "status": "answered_dtmf", // Filtra pelo desfecho (ver tabela abaixo). Valor desconhecido é ignorado.
    "phone": "11999", // Busca parcial no telefone
    "per_page": 50, // Itens por página. Padrão: 50. Mínimo: 1, Máximo: 200.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "0192f300-7b31-7c4e-9a1d-2f3b4c5d6e7f", // ID do contato nesta tentativa
            "phone": "11999990001", // Telefone discado
            "name": "Ana Silva", // Nome do lead
            "status": "answered_dtmf", // Desfecho em slug estável — use este para programar
            "status_label": "Atendida com Digitação", // Texto em português exibido no painel
            "pabx_status": "ANSWER", // Retorno cru do discador
            "call_duration_seconds": 27, // Duração da ligação (nulo quando não atendida)
            "listened_percentage": 64.3, // Percentual do áudio ouvido (nulo quando não atendida)
            "dtmf_response": "1", // Tecla digitada (nulo quando não houve)
            "tries": 1, // Número desta tentativa
            "max_attempts": 3, // Tentativas contratadas para este contato
            "sent_at": "2026-08-21T16:02:11+00:00", // Momento do despacho para o discador
            "answered_at": "2026-08-21T16:02:39+00:00" // Momento em que o desfecho voltou do discador
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/calls/broadcasts/0192f200-.../contacts?page=1",
    "from": 1,
    "next_page_url": null,
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/calls/broadcasts/0192f200-.../contacts",
    "per_page": 50,
    "prev_page_url": null,
    "to": 1
}
```

{% hint style="info" %}
Cada **tentativa** de rediscagem é uma linha própria, preservando o histórico. Um contato com `max_attempts: 3` que não atendeu duas vezes aparece em até três linhas, com `tries` 1, 2 e 3.
{% endhint %}

**Desfecho de cada ligação (`status`)**

| `status`           | Significado                                                                      |
| ------------------ | -------------------------------------------------------------------------------- |
| waiting            | Ainda não discado, ou aguardando a próxima tentativa                             |
| no\_answer         | Não atendeu, ocupado, congestionado ou número indisponível                       |
| answered\_hangup   | Atendeu e desligou antes do fim do áudio (ouviu menos de 95%)                    |
| answered\_listened | Atendeu e escutou 95% ou mais do áudio                                           |
| answered\_dtmf     | Atendeu e digitou uma tecla — a tecla está em `dtmf_response`                    |
| cancelled          | O broadcast foi cancelado antes deste contato ser discado                        |
| error              | Falhou antes de chegar ao discador (telefone fora do formato brasileiro, p. ex.) |

{% hint style="warning" %}
Use `status` para programar em cima do retorno. O `status_label` é o texto em português que o painel exibe e **pode mudar**.

`answered_at` é o momento em que o desfecho voltou do discador — não é o horário exato do atendimento.
{% endhint %}

***

#### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `402 Payment Required` (`INSUFFICIENT_CREDITS`): Saldo de créditos de ligação zerado. Vêm em `details` o `estimated_credits` e o `available_credits`.
* `404 Not Found` (`NOT_FOUND`): O broadcast, a lista ou o áudio não foi encontrado ou não pertence à sua conta.
* `409 Conflict` (`INVALID_STATUS_TRANSITION`): O broadcast não aceita a operação no status atual (editar encerrado, cancelar encerrado, pausar pausado, retomar o que não está pausado). O status atual vem em `details.current_status`.
* `422 Unprocessable Entity` (`AUDIO_NOT_READY`): O áudio ainda não terminou de ser processado. O status dele vem em `details.audio_status`; aguarde `ready: true` em `GET /calls/audios/{id}`.
* `422 Unprocessable Entity` (`EMPTY_LEAD_LIST`): A lista informada não tem nenhum lead. Insira leads antes de criar o disparo.
* `422 Unprocessable Entity` (`EMPTY_LEAD_LIST`): A lista informada não tem nenhum lead. Insira leads antes de criar o disparo.
* `422 Unprocessable Entity` (`VALIDATION_ERROR`): Algum campo do corpo é inválido — inclui `lead_list_id` no `PUT`, `scheduled_date` no passado, `scheduled_end_date` anterior ao início efetivo e `postback_url` apontando para endereço privado ou interno.
* `429 Too Many Requests` (`RATE_LIMIT_EXCEEDED`): Limite de **60 requisições por minuto** excedido. Consulte o header `Retry-After`.

**Corpo de resposta de erro:**

```json5
{
    "error": {
        "code": "AUDIO_NOT_READY", // Código do erro
        "message": "O áudio ainda não terminou de ser processado. Consulte GET /parceiros/v1/calls/audios e aguarde `ready: true`.", 
// Descrição
        "details": { // Opcional: detalhes adicionais
            "audio_status": "processing"
        }
    }
}
```

**Dúvidas?**

Em caso de dúvidas, entre em contato com nosso suporte.


# Agências

## Contas Gerenciadas (Agências)

As **Contas Gerenciadas** permitem que uma conta **agência** opere as **contas filhas** vinculadas a ela utilizando a **própria Chave de API**, sem precisar de um token por cliente. Através destes endpoints você pode listar suas contas filhas, consultar os dados de uma delas e executar qualquer chamada da API no contexto de um cliente específico.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="warning" %}
**Recurso exclusivo de contas agência.** Uma Chave de API comum recebe `403 ON_BEHALF_NOT_ALLOWED` ao enviar o header `X-On-Behalf-Of` e `403 FORBIDDEN` ao chamar `GET /accounts`. A conta filha **não** precisa ter a API habilitada — a agência opera com a própria chave; o único requisito é o vínculo entre as contas.
{% endhint %}

{% hint style="info" %}
O endpoint de listagem utiliza **paginação simples**. A resposta traz `current_page`, `data`, `per_page`, `next_page_url` e `prev_page_url` — não há campos `total` ou `last_page`. Para navegar, utilize o parâmetro de query `page`.
{% endhint %}

**Como funciona**

1. A agência chama `GET /accounts` com a própria Chave de API e obtém o `id` (UUID) de cada conta filha.
2. Em qualquer outra chamada da API, envia o header `X-On-Behalf-Of` com esse `id`.
3. A requisição passa a ser executada **inteiramente no contexto da conta filha** — listas, campanhas, sequências, leads, broadcasts e chamadas de voz. A **blacklist consultada é a da filha** e os **créditos debitados são os da filha**.
4. A resposta devolve o header `X-Effective-Account` confirmando sobre qual conta a operação ocorreu.

**Headers utilizados:**

```json5
{
    "Authorization": "Bearer {CHAVE_DA_AGENCIA}", // Obrigatório. Sempre a chave da agência.
    "X-On-Behalf-Of": "0190a1b2-7b31-7c4e-9a1d-2f3b4c5d6e7f", // Opcional. user_id da conta filha.
    "Accept": "application/json"
}
```

**Headers devolvidos na resposta:**

```json5
{
    "X-Effective-Account": "0190a1b2-7b31-7c4e-9a1d-2f3b4c5d6e7f" // Conta sobre a qual a requisição operou
}
```

***

#### Endpoints

<mark style="color:green;">**GET**</mark>**&#x20;/accounts**

**Função:** Lista as contas filhas **ativas** da agência, em ordem alfabética. É aqui que você obtém o `id` utilizado no header `X-On-Behalf-Of`.

**Parâmetros de query (opcionais):**

```json5
{
    "per_page": 20, // Itens por página. Padrão: 20. Mínimo: 1, Máximo: 100.
    "page": 1 // Página desejada
}
```

**Corpo de resposta — `200 OK`:**

```json5
{
    "current_page": 1,
    "data": [
        {
            "id": "0190a1b2-7b31-7c4e-9a1d-2f3b4c5d6e7f", // user_id da conta filha (use em X-On-Behalf-Of)
            "name": "Cliente A", // Nome da conta
            "email": "clientea@exemplo.com", // E-mail da conta
            "active": true, // Somente contas ativas são listadas
            "created_at": "2026-07-01T12:00:00-03:00" // Data de criação
        }
    ],
    "first_page_url": "https://web.smsfunnel.com.br/api/parceiros/v1/accounts?page=1",
    "from": 1,
    "next_page_url": null,
    "path": "https://web.smsfunnel.com.br/api/parceiros/v1/accounts",
    "per_page": 20,
    "prev_page_url": null,
    "to": 1
}
```

{% hint style="info" %}
O `email` é retornado **completo no corpo** da resposta, para que a agência consiga desambiguar clientes com nomes parecidos. Ele nunca é aceito ou exposto na URL — a identificação da conta filha é sempre feita pelo `id`.
{% endhint %}

***

<mark style="color:green;">**GET**</mark>**&#x20;/accounts/{id}**

**Função:** Obtém os detalhes de uma conta filha específica.

**Corpo de resposta — `200 OK`:**

```json5
{
    "id": "0190a1b2-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "name": "Cliente A",
    "email": "clientea@exemplo.com",
    "active": true,
    "created_at": "2026-07-01T12:00:00-03:00"
}
```

{% hint style="warning" %}
Um `id` que não pertence à sua agência (ou que não existe) retorna `404 NOT_FOUND` — nunca `403`. Não informamos a existência de contas de outros parceiros.
{% endhint %}

***

#### Operando uma conta filha

Para executar qualquer endpoint da API no contexto de um cliente, mantenha a `Authorization` da agência e adicione o header `X-On-Behalf-Of` com o `id` da conta filha.

**Exemplo de requisição:**

```json5
// GET /lists
{
    "Authorization": "Bearer {CHAVE_DA_AGENCIA}",
    "X-On-Behalf-Of": "0190a1b2-7b31-7c4e-9a1d-2f3b4c5d6e7f",
    "Accept": "application/json"
}

// Resposta: 200 OK
// X-Effective-Account: 0190a1b2-7b31-7c4e-9a1d-2f3b4c5d6e7f
```

**Comportamento do header:**

* **Sem o header** — o escopo é o da própria conta do token (comportamento padrão).
* **Agência + conta filha ativa e vinculada** — a requisição opera a filha e a resposta traz `X-Effective-Account`.
* **Agência + conta que não é filha ativa** (inexistente, inativa ou de outro parceiro) — `404 NOT_FOUND`.
* **Chave de API que não é de agência + header** — `403 ON_BEHALF_NOT_ALLOWED`.

{% hint style="success" %}
**Confirme sempre o `X-Effective-Account`.** Ele é devolvido em toda resposta em que o `X-On-Behalf-Of` foi aceito. Validá-lo evita que uma operação seja executada na conta errada.
{% endhint %}

{% hint style="info" %}
**Auditoria.** Toda chamada realizada em nome de uma conta filha é registrada de forma assíncrona e durável (quem operou, qual conta, qual ação e quando). Uma agência operando a **própria** conta não gera registro.
{% endhint %}

***

#### Limite de Requisições

* **60 requisições por minuto por conta efetiva.** Sem o header `X-On-Behalf-Of`, a conta efetiva é a própria agência. Com o header, **cada conta filha tem seu próprio limite** — operar o Cliente A não consome o limite do Cliente B.
* **600 requisições por minuto por Chave de API**, como teto global. Ao atingi-lo, a chave recebe `429` mesmo que ainda haja limite disponível na conta filha.
* Os limites são contabilizados por conta/chave, não por endpoint — todas as chamadas competem pelo mesmo orçamento.
* Ao exceder, a resposta traz o header `Retry-After` com o tempo de espera em segundos.

{% hint style="warning" %}
O limite real de uma Chave de API é sempre **600 requisições por minuto**. O limite de 60/min por conta efetiva existe para garantir o **isolamento entre as contas filhas**, e não representa uma vazão máxima garantida por chave.
{% endhint %}

***

#### Código de Erros Comuns

* `401 Unauthorized` (`UNAUTHORIZED`): Não foi possível identificar o parceiro a partir da Chave de API.
* `403 Forbidden` (`FORBIDDEN`): `GET /accounts` acessado por uma chave que não pertence a uma conta agência.
* `403 Forbidden` (`ON_BEHALF_NOT_ALLOWED`): Header `X-On-Behalf-Of` enviado por uma chave que não pertence a uma conta agência.
* `404 Not Found` (`NOT_FOUND`): A conta informada não existe, não está ativa ou não é uma conta filha da sua agência.
* `429 Too Many Requests` (`RATE_LIMIT_EXCEEDED`): Limite de requisições excedido. Consulte o header `Retry-After`.

**Corpo de resposta de erro:**

```json5
{
    "error": {
        "code": "ON_BEHALF_NOT_ALLOWED", // Código do erro
        "message": "Only agency accounts can act on behalf of managed accounts." // Descrição
    }
}
```

**Dúvidas?**

Em caso de dúvidas, entre em contato com nosso suporte.


# Webhook de status

O **Webhook de status** entrega, na sua URL, o que aconteceu com cada mensagem — em vez de você consultar. Vale para todo disparo da conta: broadcast, automação, pela API ou pelo painel, envio real ou de teste.

{% hint style="info" %}
A URL de base de todos os endpoints é: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

{% hint style="info" %}
O webhook **não substitui o polling como fonte da verdade**. O `status` do POST é o mesmo que `GET /sms/messages/{id}` devolve naquele instante; se os dois divergirem, a API é a fonte da verdade.
{% endhint %}

### São até dois eventos por mensagem

O primeiro evento sai no despacho e tem dois desfechos possíveis: ou a mensagem saiu, ou não saiu. O segundo sai quando a confirmação de entrega chega até nós — **quando e se** chegar.

| Evento                             | Quando                                                                                                                     | Garantido?                    |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `sent_to_carrier`                  | No instante em que a mensagem é despachada da nossa fila para envio                                                        | Sim, se a mensagem saiu       |
| `cancelled` ou `blocked_blacklist` | Quando a mensagem **não** sai: telefone na sua blacklist, telefone inválido ou crédito insuficiente. O crédito é estornado | Sim, **no lugar** do primeiro |
| `delivered`                        | Quando a confirmação de entrega no aparelho chega até nós                                                                  | **Não**                       |

Quando a mensagem não sai, não haverá segundo evento — não há confirmação de entrega a receber.

**Não há prazo e não há desfecho garantido.** Nem todo envio produz confirmação de entrega; quando não produz, a mensagem fica em `sent_to_carrier` e não muda mais. Não trate a ausência do `delivered` como falha: `sent_to_carrier` é um estado possivelmente final. Use o `delivered` como confirmação positiva. Em geral, retornos de **delivered** chegam em até **2 horas** após o envio. Em alguns casos de telefone desligado ou retentativas o retorno pode demorar mais.

Isso é a mesma regra da consulta por API, e é deliberado: **não inventamos desfecho**. Um `delivered` que você recebe é uma confirmação real que chegou até nós.

**O cancelamento que parte de você não gera evento.** `PUT /sms/broadcasts/{id}/cancel` é um pedido seu, e devolver por POST o que você acabou de pedir não acrescenta informação. Os eventos de cancelamento existem para o que você **não** pediu.

### Quando você não recebe nada

O webhook começa a valer no momento do **envio**, não no da mudança de status. Uma mensagem só entra na fila de eventos se, quando ela saiu, a sua conta já tinha URL cadastrada.

| Situação                                          | O que você recebe                                                                                                       |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Cadastrou a URL **depois** de disparar            | Nada daquela mensagem — nem o `sent_to_carrier`, nem o `delivered` que chegar depois                                    |
| Removeu a URL e cadastrou de novo                 | Nada do que foi disparado no intervalo                                                                                  |
| Mensagem anterior à ativação do recurso na conta  | Nada                                                                                                                    |
| Mensagem anterior a 02/09/2026 criada fora da API | Nada — antes dessa data só o envio criado pela API recebia o `id` público que identifica a mensagem. Não houve backfill |

Em todos os casos o `status` continua correto em `GET /sms/messages/{id}` — só o POST não sai.

### Endpoints

#### PUT /me/webhook

**Função:** cadastra ou substitui a URL que recebe os eventos e o segredo usado para assinar.

**Corpo de envio:**

```json
{
    "url": "https://seu-dominio.com/webhooks/smsfunnel", // Obrigatório. HTTPS. Recusamos URL que resolva para endereço privado, loopback ou metadados de nuvem.
    "secret": "sua-chave-secreta-aqui" // Opcional. Mínimo 16 caracteres. Se omitido, geramos um.
}
```

**Corpo de resposta — 200 OK:**

```json
{
    "url": "https://seu-dominio.com/webhooks/smsfunnel",
    "secret": "whsec_aB3dE6gH9jK2mN5pQ8sT1vW4xY7zA0bC", // Só aparece AQUI. Guarde agora.
    "enabled": true
}
```

**Guarde o `secret` nesta resposta.** Ela é a única em que ele aparece — o `GET` devolve apenas `has_secret`. Se perder, faça um novo `PUT`: o segredo anterior é substituído.

Com o header `X-On-Behalf-Of`, a configuração é gravada na conta filha, que é de quem são as mensagens.

#### GET /me/webhook

**Função:** mostra a configuração atual da conta. O segredo nunca é devolvido.

**Corpo de resposta — 200 OK:**

```json
{
    "url": "https://seu-dominio.com/webhooks/smsfunnel", // Nulo se não houver webhook cadastrado
    "has_secret": true, // Se existe segredo gravado
    "enabled": true // Se a conta está recebendo eventos
}
```

#### DELETE /me/webhook

**Função:** desliga o envio de eventos e apaga URL e segredo.

Responde **200 OK**. A partir daí nenhuma mensagem nova entra na fila de eventos — inclusive as disparadas antes de você cadastrar de novo.

### O evento

Um único tipo de evento (`message.status`) para os dois POSTs. O formato não muda; o que muda é o `status`. Você implementa um handler só.

```json
{
    "event": "message.status", // Sempre "message.status"
    "id": "0192a2c5-7d92-7c4a-9b1c-1a2b3c4d5e6f", // O mesmo id de GET /sms/messages/{id}
    "reference": "order-123", // Sua referência. NULO em automação — o campo só existe em broadcast, aqui e na API.
    "phone": "5511999999999", // Telefone de destino
    "status": "delivered", // sent_to_carrier | delivered | cancelled | blocked_blacklist
    "sent_at": "2026-08-03T14:25:13-03:00", // Instante do despacho. NULO quando a mensagem não saiu.
    "origin": { // De onde a mensagem saiu
        "type": "broadcast", // "broadcast" ou "automation"
        "id": "0192a2c4-7d92-7c4a-9b1c-1a2b3c4d5e6f", // Id do broadcast, ou da sequência em automação
        "name": "Black Friday 2026" // Nome do broadcast, ou da campanha em automação
    }
}
```

Os campos de mensagem são exatamente os de `GET /sms/messages/{id}`. Não há campo exclusivo do webhook além de `origin`.

#### O bloco origin

É o que distingue um disparo em massa de uma régua de automação sem você cruzar o `id` contra as suas tabelas. Em automação ele ganha um campo a mais:

```json
{
    "origin": {
        "type": "automation",
        "id": "0192d310-7d92-7c4a-9b1c-1a2b3c4d5e6f", // Id da SEQUÊNCIA — a mensagem específica da régua
        "name": "Recuperação de carrinho", // Nome da CAMPANHA
        "campaign_id": "0192d300-7d92-7c4a-9b1c-1a2b3c4d5e6f" // Só em automação
    }
}
```

Em automação o nome vem da campanha porque a sequência não tem nome próprio — ela é uma posição na régua. O `campaign_id` acompanha para você conseguir agrupar.

### Verificando a assinatura

Todo POST leva dois headers:

| Header                  | Conteúdo                                                               |
| ----------------------- | ---------------------------------------------------------------------- |
| `X-SmsFunnel-Signature` | `sha256=` seguido do HMAC-SHA256 do **corpo bruto**, com o seu segredo |
| `X-SmsFunnel-Event-Id`  | Identificador do evento, para idempotência                             |

Assine o corpo **exatamente como recebido**, antes de qualquer parse — reserializar o JSON muda os bytes e quebra a comparação.

```php
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $seuSegredo);

if (! hash_equals($expected, $request->header('X-SmsFunnel-Signature'))) {
    abort(401);
}
```

**Use o `X-SmsFunnel-Event-Id` para idempotência.** As retentativas de um mesmo evento repetem o id: se você já processou aquele id, descarte.

### Entrega e retentativas

| Aspecto     | Comportamento                                                                                |
| ----------- | -------------------------------------------------------------------------------------------- |
| Timeout     | 5 segundos por tentativa                                                                     |
| Sucesso     | Qualquer 2xx                                                                                 |
| Retentativa | 5xx, 408, 429 e falha de rede — até 5 tentativas, com espera de 10s, 30s, 1min, 5min e 15min |
| Desistência | Demais 4xx — tratamos como endereço ou contrato errado, e não insistimos                     |

Responda rápido: processe de forma assíncrona e devolva 200 imediatamente. Um endpoint lento gera retentativa mesmo tendo recebido o evento.


# Referência

Esta seção reúne os padrões comuns a todos os endpoints da API de parceiros: o formato de resposta de erros e os limites de requisição.

{% hint style="success" %}
**A URL de base de todos os endpoints é**: <https://web.smsfunnel.com.br/api/parceiros/v1>
{% endhint %}

***

### Envelope de Erro

Sempre que uma requisição falha, a resposta segue um formato padronizado, com a chave `error` contendo o código, a mensagem e, quando aplicável, os detalhes do erro.

```json5
{
    "error": {
        "code": "VALIDATION_ERROR", // Código do erro (identificador estável)
        "message": "The given data was invalid.", // Descrição legível do erro
        "details": { // Opcional: presente apenas em alguns erros (ex.: validação)
            "name": [
                "Field `name` is required."
            ]
        }
    }
}
```

{% hint style="info" %}
O campo `details` é **omitido** quando não há informações adicionais. Em erros de validação, ele traz um objeto onde cada chave é o campo inválido e o valor é a lista de mensagens correspondentes.
{% endhint %}

#### Códigos de Erro

| HTTP                        | `code`                         | Descrição                                                                                |
| --------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------- |
| `401 Unauthorized`          | `UNAUTHORIZED`                 | Não foi possível identificar o parceiro a partir da Chave de API.                        |
| `402 Payment Required`      | `INSUFFICIENT_CREDITS`         | Créditos de SMS insuficientes para concluir a operação.                                  |
| `404 Not Found`             | `NOT_FOUND`                    | O recurso solicitado não foi encontrado ou não pertence à sua conta.                     |
| `409 Conflict`              | `INVALID_STATUS_TRANSITION`    | A operação não é permitida no status atual do recurso (ex.: cancelar broadcast enviado). |
| `409 Conflict`              | `CONCURRENT_REQUEST`           | Já existe uma requisição em processamento com o mesmo `Idempotency-Key`.                 |
| `409 Conflict`              | `LIST_HAS_DEPENDENT_CAMPAIGNS` | Tentativa de remover uma lista que ainda possui campanhas vinculadas.                    |
| `422 Unprocessable Entity`  | `VALIDATION_ERROR`             | Algum campo do corpo da requisição é inválido.                                           |
| `429 Too Many Requests`     | `RATE_LIMIT_EXCEEDED`          | Limite de requisições por minuto excedido.                                               |
| `500 Internal Server Error` | `INTERNAL_ERROR`               | Ocorreu um erro interno ao processar a requisição.                                       |
| `502 Bad Gateway`           | `SHORTENER_UNAVAILABLE`        | Falha temporária ao gerar o link curto de "Meu Link". Tente novamente.                   |

{% hint style="warning" %}
Falhas de autenticação no nível de acesso retornam respostas mais simples, fora do envelope padrão: header ausente/malformado → `401` com `{"status":"Authorization header is missing or malformed"}`; Chave de API inválida, expirada ou sem permissão → `403` com o corpo `Forbidden`.
{% endhint %}

***

### Rate Limit

Todas as requisições estão sujeitas a um limite de **60 requisições por minuto**, contabilizado por **Chave de API** (não por IP).

Ao exceder o limite, a resposta será `429 Too Many Requests`:

```json5
{
    "error": {
        "code": "RATE_LIMIT_EXCEEDED",
        "message": "Too many requests. Retry after 30 seconds."
    }
}
```

#### Headers de controle

As respostas acompanham headers que informam o estado atual do seu limite:

| Header                  | Descrição                                                          |
| ----------------------- | ------------------------------------------------------------------ |
| `Retry-After`           | Segundos a aguardar antes de tentar novamente (presente no `429`). |
| `X-RateLimit-Limit`     | Total de requisições permitidas na janela (60).                    |
| `X-RateLimit-Remaining` | Quantidade de requisições ainda disponíveis na janela atual.       |

{% hint style="info" %}
**Boa prática:** ao receber um `429`, aguarde o tempo indicado em `Retry-After` antes de repetir a requisição. Para envios em massa, prefira agrupar destinatários em uma única chamada (até 5.000 mensagens por requisição) em vez de múltiplas chamadas pequenas.
{% endhint %}

#### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.


# Integração

Nesta seção, você aprenderá como integrar suas lojas no Magento com nossa plataforma utilizando o nosso módulo.

### Pré-Requisitos

Antes de iniciar a integração, certifique-se de que:

* Você possui uma conta ativa em nossa plataforma.
* Você possui uma integração "Magento" em nossa plataforma.
* Sua instalação do Magento está atualizada para uma versão compatível com o nosso plugin (Magento 2 ou superior).
* Você tem as credenciais de administrador para acessar o painel de controle do Magento.


# Instalação

Como instalar nosso módulo no Magento? Aprenda agora!

1. Execute o Comando Composer para Instalar o Módulo

```bash
composer require smsfunnel/module-smsfunnel
```

2. Atualize o Banco de Dados e o Cache

```bash
php bin/magento setup:upgrade
php bin/magento cache:flush
```

3. Habilite o módulo

```bash
php bin/magento module:enable Smsfunnel_Smsfunnel
```

4. Compile o Magento (se necessário)

```
php bin/magento setup:di:compile
```

5. Verifique se o módulo foi instalado com sucesso

```
php bin/magento module:status
```

Caso esteja corretamente instalado e habilitado, prossiga para a próxima seção para configurar.


# Configuração

Ok, já instalei o módulo... E agora? Aprenda agora à configurar em 4 passos simples!

1. No seu paínel administrativo do Magento, localize no menu a opção **SMSFUNNEL**.

<figure><img src="https://2334899636-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0zOT5nzTB4m2xwoPYBsk%2Fuploads%2FPvBj3WZrH8oBNN1sZ8vn%2Fimage.png?alt=media&amp;token=030504f0-b0d6-4182-899e-3db1b5f9a8a7" alt=""><figcaption><p>Logo da Opção</p></figcaption></figure>

2. Clique na opção e vá até **Notifications Configurations**.

<figure><img src="https://2334899636-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0zOT5nzTB4m2xwoPYBsk%2Fuploads%2FNkCsU7hpzR6NWrn9QNMA%2Fimage.png?alt=media&amp;token=5d195b81-fbb8-4c9c-b8bc-1e20d42c822f" alt=""><figcaption><p>Notifications Configurations</p></figcaption></figure>

3. Você irá encontrar um menu com diversas opções de configurações: `Active integration`, `Webhook integration`, `Number items post`, `Number of attempted`, `Garbage Collector Time`, `Clear postback items` e `Clear postback logs`. Recomendamos a configuração abaixo, para enviar seus postbacks de maneira segura e estável para nossa plataforma.

<figure><img src="https://2334899636-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0zOT5nzTB4m2xwoPYBsk%2Fuploads%2Fw174BugtQ4kbSKvYdQ5L%2Fimage.png?alt=media&amp;token=b229b816-0849-4e50-840d-04cddc5ecf95" alt="" width="563"><figcaption><p>Configurações Recomendadas</p></figcaption></figure>

4. Feito isto, agora basta salvar suas configurações e aguardar até que os postbacks cheguem em sua integração em nossa plataforma!

### Explicação dos campos

* `Active integration` : Status de ativação da sua integração no Magento.
  * &#x20;Caso seja **Yes**, você enviará os postbacks para a nossa plataforma.&#x20;
  * Caso seja **No**, os postbacks não serão enviados.
* `Webhook integration` : URL da sua integração na nossa plataforma, basta criar uma integração para plataforma "Magento" em nossa página de integrações e inserir o link gerado neste campo.
* `Number items post` : Número de postbacks que será enviado por requisição/minuto, recomendamos manter em um valor seguro (Entre 10 e 150).
* `Number of attempted` : Número de tentativas de envio, recomendamos deixar o valor 3. Serão quantas tentativas serão feitas em caso de falha na requisição.
* `Garbage Collector Time` : Minutagem para excluir arquivos temporários e derivados do módulo. Considere as limitações de recurso de sua máquina ao alterar esta configuração.
* `Clear postback items` : Dias para excluir postbacks armazenados, recomendamos 3 para garantia de envio, processamento correto e depuração em caso de falhas. Considere as limitações de recurso de sua máquina e a opção `Number items post` ao alterar esta configuração.
* `Clear postback logs` : Dias para excluir as logs de postbacks/requisições, recomendamos 7 para depuração em caso de falhas. Considere as limitações de armazenamento da sua máquina e a opção `Number items post` para alterar esta configuração.

Tudo pronto? Perfeito, agora é com você! Configure suas campanhas em nossa plataforma do seu jeitinho! 🤗

### Dúvidas?

Em caso de dúvidas, entre em contato com nosso suporte.


