---
name: 4send-api
description: >-
  Contexto da API do 4send (WhatsApp + e-mail): base URL, autenticação por API
  key, envio de mensagens, contatos, tags, campos personalizados, fluxos,
  idempotência e webhooks assinados. Use ao construir, integrar ou depurar
  qualquer coisa que chame a API do 4send (arquivos com "4send", "integração"
  ou "webhook").
---

# API do 4send

Você está ajudando a integrar a **API do 4send** — WhatsApp + e-mail transacional,
contatos e fluxos (jornadas automáticas).

## Essencial

- **Base URL:** `https://api.4send.me/v1`
- **Auth:** cabeçalho `Authorization: Bearer <api_key>`
- **Chaves:** `4s_test_...` = sandbox (NÃO envia de verdade, ideal pra desenvolver);
  `4s_live_...` = produção (envia pra valer). Crie em Integrações → Tokens de API.
- **⚠️ CHAME SEMPRE DO SERVIDOR (backend). NUNCA do navegador.** A API key é um
  segredo. Em código que roda no navegador (React, Vue, Next client component,
  HTML, extensão, app web) a key fica **visível para qualquer visitante** da
  página, que passaria a enviar mensagens e ler os contatos da conta.
  - Chamada com `4s_live_` **vinda de um navegador é recusada com `403`** — a
    API detecta pelo cabeçalho `Origin`. Não tente contornar: o bloqueio existe
    para proteger a key de quem te contratou.
  - `4s_test_` **funciona no navegador** (é sandbox, não dispara nada). Use pra
    prototipar a tela; mova a chamada pro backend antes de usar `4s_live_`.
  - **Está montando um conector/integração dentro de um produto?** Guarde a key
    no seu backend e faça a chamada de lá. Nunca entregue a key ao frontend,
    nem a repasse para o navegador do seu cliente.
  - Sem backend? Use uma camada de servidor (rota de API do seu framework,
    função serverless, n8n/Make) como intermediária.
- **Formato:** JSON. Datas em ISO 8601. Ids têm prefixo por recurso
  (`msg_`, `ct_`, `flow_`, `run_`, `tag_`, `field_`).
- **Envio é assíncrono:** o POST responde `202` com `status: "queued"`; o
  resultado final chega por **webhook** ou consultando `GET /messages/{id}`.
- **Acesso depende do plano** da conta (a key só vale se o plano libera a API).

## Endpoints

### Enviar mensagem — `POST /messages`  (escopo `messages:send`)
```json
{
  "channel": "whatsapp",
  "to": { "phone": "+5551999999999" },
  "whatsapp": { "body": "Olá {{nome}}! Seu pedido saiu 🚚" },
  "variables": { "nome": "João" }
}
```
E-mail: `"channel": "email"`, `to.email`, e `email: { subject, html, reply_to?, preview_text? }`.
Destinatário: `to.phone`, `to.email` **ou** `to.contact_id`. Mídia no WhatsApp:
`whatsapp: { media_url, media_type: image|video|audio|document }`.
Resposta `202`:
```json
{ "id": "msg_663f...", "status": "queued", "channel": "whatsapp",
  "to": "+5551999999999", "provider_message_id": null, "created_at": "..." }
```

### Status da mensagem — `GET /messages/{id}`  (escopo `messages:read`)
Status: `queued → sent → delivered → read` (ou `failed`).

### Criar/atualizar contato — `POST /contacts`  (escopo `contacts:write`)
```json
{ "phone": "+5551999999999", "name": "João",
  "tags": ["tag_663f..."], "fields": { "plano": "gold" } }
```
Deduplica por telefone; na falta, por e-mail. Campos omitidos NÃO são apagados.
Também: `GET /contacts/{id}` e `GET /contacts?phone=&email=` (escopo `contacts:read`).

### Colocar contato num fluxo — `POST /flows/{flow_id}/enroll`  (escopo `flows:enroll`)
```json
{ "to": { "phone": "+5551999999999" }, "variables": { "nome": "João" } }
```
Use `GET /flows` (escopo `flows:read`) pra descobrir o `flow_id`. O fluxo precisa
estar `active` (senão `409`).

### Tirar contato do fluxo — `POST /flows/{flow_id}/unenroll`  (escopo `flows:enroll`)
```json
{ "to": { "phone": "+5551999999999" }, "reason": "comprou" }
```
Interrompe a jornada: o contato para de receber o que faltava. **`flow_id` aceita
`all`** pra tirar de TODAS as jornadas de uma vez — é o que você quer no caso
clássico ("comprou → para tudo", "cancelou", "pediu pra parar"), sem listar
fluxo por fluxo. Resposta: `{ "unenrolled": 1, "runs": [...] }`.

Idempotente: quem não está em jornada nenhuma devolve `unenrolled: 0` e `200`,
não erro — então dá pra chamar sempre, sem checar antes. O `reason` vai no
relatório e no webhook `fluxo.parado`, e é o que separa "comprou" de "desistiu".

### Tags — `POST /tags` `{ "name": "cliente-vip", "color": "#FF4E33" }` · `GET /tags`
Escopos `tags:write` / `tags:read`. Use o `id` retornado no campo `tags` de um contato.

### Campos personalizados — `POST /custom-fields` `{ "name": "Plano", "key": "plano", "type": "text" }` · `GET /custom-fields`
Escopos `fields:write` / `fields:read`. A `key` vira a variável `{{key}}` nas mensagens.

### Consumo — `GET /usage`
`{ "period": "2026-08", "whatsapp": 1234, "email": 567, "total": 1801, "limit": null }`

## Idempotência (não duplicar envio)

Em POSTs de envio (`/messages`) e matrícula (`/flows/{id}/enroll`), mande o
cabeçalho `Idempotency-Key: <uuid>`. Convenção recomendada: `<evento>/<id>`
(ex.: `pedido-enviado/12345`). Retenção 24h.
- mesma key + mesmo corpo → devolve a resposta guardada (não reenvia).
- mesma key + corpo diferente → `422`.

## Erros

Sempre no formato:
```json
{ "error": { "type": "invalid_request", "message": "...", "param": "to.phone",
             "doc_url": "https://docs.4send.me" } }
```
`type`: `authentication` (401) · `forbidden` (403) · `invalid_request` (400/422) ·
`not_found` (404) · `conflict` (409) · `rate_limit` (429) · `quota_exceeded` (403) · `api_error` (500).

## Limite de requisições

~120 req/min por chave. Ao exceder → `429` com cabeçalhos `RateLimit-Limit`,
`RateLimit-Remaining`, `RateLimit-Reset` e `Retry-After`.

## Webhooks (o aviso de volta)

O 4send faz POST no endpoint que você cadastrar (Integrações → Webhooks) quando
algo acontece. Eventos: `mensagem.enviada`, `mensagem.entregue`, `mensagem.lida`,
`mensagem.falhou`, `fluxo.concluido`, `fluxo.parado`, `contato.criado`,
`contato.atualizado`. Cada entrega vai assinada em `X-4send-Signature: t=<ts>,v1=<hmac>`.
Verifique com o secret do webhook — detalhes e código em **webhooks.md**.

## Regras de ouro

- **A chamada sai do SERVIDOR, nunca do navegador.** Se o código que você está
  escrevendo roda no navegador do usuário final, a integração está no lugar
  errado — mova pro backend. `4s_live_` no navegador é recusado com `403`.
- Em produção, comece testando com uma chave `4s_test_` (não gasta cota, não dispara).
- Sempre mande `Idempotency-Key` nos envios.
- Sempre verifique a assinatura do webhook contra o **corpo cru** (raw body).
- Consulte **openapi.yaml** (spec completa) e **examples.md** (curl/Node/Python).
