Seções

Pools de disparo (HTTP)

Endpoints /v1/pools — agrupar números e enviar pelo número LRU saudável do grupo.

Um pool é um agrupamento de números do tenant. Em vez de escolher um número para cada envio, você envia para o pool e o broker escolhe, a cada mensagem, o número LRU (o que está há mais tempo sem disparar) entre os que estão saudáveis — espalhando o volume no tempo em vez de martelar sempre o mesmo número. Os endpoints estão sob /v1/pools e exigem o mesmo header Authorization: Bearer das demais rotas.

métodopatho que faz
GET/v1/poolslista os pools do tenant
POST/v1/poolscria um pool
PATCH/v1/pools/:idrenomeia o pool
DELETE/v1/pools/:idapaga o pool
GET/v1/pools/:id/channelslista os números vinculados, com estado e último disparo
POST/v1/pools/:id/channelsvincula um número ao pool
DELETE/v1/pools/:id/channels/:channelIddesvincula um número do pool
POST/v1/pools/:ref/messagesenvia uma mensagem pelo número LRU saudável do pool
POST/v1/pools/:ref/messages/batchenvia uma lista de envios — cada um pode sair por um número diferente

Em todas as rotas de envio, :ref aceita o id do pool (pl_…) ou o slug — o broker resolve os dois, sempre dentro do tenant da API key. O slug é gerado a partir do nome na criação e fica fixo mesmo que o pool seja renomeado depois; o nome cru não resolve.

Criar e gerenciar o catálogo

O corpo carrega só name (1 a 40 caracteres). Nomes são únicos por tenant, case-insensitive: criar (ou renomear para) um nome já usado por outro pool do mesmo tenant responde 409 pool_name_taken.

bash
curl -X POST https://api.grwthy.com/v1/pools \
  -H "Authorization: Bearer $WABROKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Marketing" }'

Resposta 201

json
{
  "channelCount": 0,
  "id": "pl_4c8f2a6b0d1e3579",
  "name": "Marketing",
  "slug": "marketing"
}

GET /v1/pools lista o catálogo, ordenado por nome, com channelCount — a contagem de números vinculados a cada pool.

Resposta 200

json
[
  {
    "channelCount": 1,
    "id": "pl_4c8f2a6b0d1e3579",
    "name": "Marketing",
    "slug": "marketing"
  }
]
  • PATCH /v1/pools/:id aceita só name e devolve { id, name } — não recalcula channelCount; liste de novo se precisar dele atualizado.
  • DELETE /v1/pools/:id responde 204. Apaga o pool e os vínculos com os números — os números em si não são removidos nem desconectados.
  • Pool de outro tenant (ou inexistente): 404 resource_not_found, nunca 403.

Vincular números ao pool

POST /v1/pools/:id/channels vincula um número existente pelo channelId (idempotente — vincular de novo não duplica nem falha). Um número pode pertencer a mais de um pool.

bash
curl -X POST https://api.grwthy.com/v1/pools/pl_9f2c.../channels \
  -H "Authorization: Bearer $WABROKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channelId": "ch_9f2c8a1b0d3e4f50" }'

GET /v1/pools/:id/channels lista os números do pool, ordenados pelo mesmo critério LRU do envio (menos usado recentemente primeiro) — a mesma ordem em que o broker os escolheria para o próximo disparo.

Resposta 200

json
[
  {
    "id": "ch_9f2c8a1b0d3e4f50",
    "label": "+55 11 90000-0001",
    "lastDispatchAt": null,
    "number": null,
    "state": "connected"
  }
]
  • lastDispatchAt é null enquanto o número nunca disparou pelo pool — esses vêm primeiro (nunca-usado antes de já-usado).
  • DELETE /v1/pools/:id/channels/:channelId desvincula (204) sem afetar o número.
  • Pool ou número de outro tenant (ou inexistente): 404 (resource_not_found ou channel_not_found), nunca 403.

Enviar pelo pool (LRU)

POST /v1/pools/:ref/messages tem o mesmo corpo do envio por número (to, content, idempotencyKey obrigatória, opcionalmente replyToProviderId/ttlSeconds) — veja Mensagens (HTTP) para os tipos de content. A única diferença é quem escolhe o número: aqui é o broker, não você. No exemplo abaixo, marketing é o slug gerado para o pool "Marketing" criado acima — não o nome.

bash
curl -X POST https://api.grwthy.com/v1/pools/marketing/messages \
  -H "Authorization: Bearer $WABROKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5511988887777",
    "content": { "kind": "text", "text": "Promoção só hoje!" },
    "idempotencyKey": "promo-4821-envio"
  }'

Resposta 202

json
{
  "idempotencyKey": "promo-4821-envio",
  "idempotent": false,
  "messageId": "msg_7b3e1d9a2c4f6081",
  "state": "queued",
  "traceId": "tr_2f8b6d0a4c1e3597"
}

O content aceita os mesmos kind do envio por número — inclusive interactive e reaction:

bash
curl -X POST https://api.grwthy.com/v1/pools/marketing/messages \
  -H "Authorization: Bearer $WABROKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5511988887777",
    "content": { "kind": "interactive", "interactive": {
      "type": "list", "body": "Escolha um horário disponível", "buttonLabel": "Ver horários",
      "sections": [
        { "title": "Manhã", "rows": [
          { "id": "h9", "title": "09:00" },
          { "id": "h10", "title": "10:00", "description": "Último horário da manhã" }
        ] }
      ]
    } },
    "idempotencyKey": "promo-4821-lista"
  }'

curl -X POST https://api.grwthy.com/v1/pools/marketing/messages \
  -H "Authorization: Bearer $WABROKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5511988887777",
    "content": { "kind": "reaction", "targetMessageId": "msg_9f2c8a1b0d3e4f50", "emoji": "👍" },
    "idempotencyKey": "promo-4821-reacao"
  }'

A reação segue o mesmo contrato agnóstico de /v1/channels/:ref/messages — veja Mensagens (HTTP) para o detalhe (targetMessageId, alias deprecated, e invalid_recipient quando o alvo não resolve).

A escolha é atômica com a admissão: o broker olha o número com last_dispatch_at mais antigo (nunca-usado primeiro) entre os saudáveis — connected, sem estar limitado nem restrito (quarentena é aplicada em seguida, na admissão em si) — e carimba o disparo antes de devolver a resposta. Dois envios concorrentes ao mesmo pool nunca colidem no mesmo número. A partir daí, o envio herda os mesmos gates de fila, cota e compliance do envio por número — só a escolha do número muda.

Pool sem número disponível

Se nenhum número do pool está saudável no momento (todos desconectados, restritos, limitados ou em quarentena), o envio falha com 409 no_available_channel — nada é admitido, e é seguro tentar de novo mais tarde ou por outro pool/número.

Enviar em lote pelo pool

POST /v1/pools/:ref/messages/batch aceita o mesmo corpo do lote por número — { "messages": [...] }, cada item idêntico ao envio unitário (to, content, idempotencyKey obrigatória), máximo de 2000 itens num corpo de até 512KB. A resposta, porém, segue o contrato antigo e síncrono, diferente do lote por número — veja Mensagens (HTTP) para o contrato assíncrono do lote por número (202 accepted + webhook). Aqui cada item já volta resolvido na própria posição: entrou (messageId) ou falhou na admissão (error), sem passar por um estado intermediário — o lote continua não atômico nesse sentido. A diferença de roteamento: cada item escolhe seu próprio número LRU, de forma independente. Um broadcast de 200 mensagens não sai todo pelo mesmo número: ele se espalha pelos números saudáveis do pool, reduzindo o risco de um único número levar toda a rajada (bom para evitar restrição/banimento).

bash
curl -X POST https://api.grwthy.com/v1/pools/marketing/messages/batch \
  -H "Authorization: Bearer $WABROKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      { "to": "5511900000001", "idempotencyKey": "promo-a",
        "content": { "kind": "text", "text": "Promoção só hoje!" } },
      { "to": "5511900000002", "idempotencyKey": "promo-b",
        "content": { "kind": "text", "text": "Promoção só hoje!" } }
    ]
  }'

Cada item traz seu messageId na posição em que entrou, ou um error se falhou na admissão — inclusive { "code": "no_available_channel" } se o pool ficou sem número saudável no meio do lote; os itens seguintes ainda tentam normalmente.

Template com params no content, e lote heterogêneo

Igual ao lote por número: quando um item tem content.kind === "template", os parâmetros de renderização (bodyParams, headerText, headerMediaUrl, buttonParams) vão dentro do content daquele item — não há mais personalização "por fora" nem herança entre itens. E como cada item carrega o seu próprio content completo, nada impede misturar kind diferentes no mesmo lote de pool, cada um saindo pelo número que o LRU escolher para ele:

bash
curl -X POST https://api.grwthy.com/v1/pools/marketing/messages/batch \
  -H "Authorization: Bearer $WABROKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      { "to": "5511900000001", "idempotencyKey": "pedido-a",
        "content": { "kind": "template", "name": "confirmacao_pedido", "language": "pt_BR",
                      "bodyParams": ["Ana", "1234"] } },
      { "to": "5511900000002", "idempotencyKey": "pedido-b",
        "content": { "kind": "template", "name": "confirmacao_pedido", "language": "pt_BR",
                      "bodyParams": ["Bruno", "5678"] } },
      { "to": "5511900000003", "idempotencyKey": "pedido-c",
        "content": { "kind": "media", "media": "image", "url": "https://cdn.exemplo.com/nf.png" } }
    ]
  }'

Corpo estrito: campo desconhecido é rejeitado

O corpo (envelope e cada item de messages) é validado estrito: uma propriedade fora do contrato responde 422 em vez de ser ignorada em silêncio.

Validação de forma é atômica: 1 item inválido reprova o lote inteiro

A forma de cada item (parse do envio e os parâmetros de template efetivos do seu content) é checada antes de admitir qualquer um, mesmo aqui onde a escolha do número é por LRU e por item. O primeiro item com forma inválida reprova a requisição inteira com 422 e nada é enviado. Só a admissão em runtime (fila, cota, canal disponível) continua não-atômica — essa sim, por-posição, como no restante desta seção.

Equivalência no SDK

client.pools.list()/create()/rename()/delete()/channels()/addChannel()/removeChannel() cobrem o catálogo e o vínculo (C#: client.Pools.ListAsync()/CreateAsync()/RenameAsync()/DeleteAsync()/ChannelsAsync()/AddChannelAsync()/RemoveChannelAsync()); o envio é client.messages.sendToPool(poolRef, input) e client.messages.sendBatchToPool(poolRef, input) — mesma assinatura de send/sendBatch, trocando o número pelo poolRef do pool (C#: client.Messages.SendToPoolAsync(poolRef, input) e client.Messages.SendBatchToPoolAsync(poolRef, input)).