# Visão geral (/docs/api)





Esta é a referência da API REST do PEPINO ZAP. Com ela o seu sistema conecta números de WhatsApp (instâncias) e envia mensagens de forma programática. Cada endpoint da referência traz exemplos de requisição em várias linguagens e um playground "Try it" para testar a chamada direto na página.

Esta página de visão geral reúne as **convenções comuns a toda a API** — as regras que valem para qualquer endpoint: como autenticar, em que ambiente você está (teste ou produção), como escrever o destinatário, como o sucesso e o erro são representados e por que o envio é assíncrono. Leia-a uma vez antes de mergulhar nos endpoints; ela evita a maioria dos tropeços de integração.

<Callout type="info">
  Se você ainda não gerou uma API key nem conectou um número, comece pelos
  guias passo a passo: [Autenticação](/docs/get-started/authentication) (criar a
  conta e gerar a key) e [Sua primeira instância](/docs/get-started/first-instance)
  (conectar um número via QR Code). Esta página é a referência das convenções;
  aqueles guias são o caminho guiado.
</Callout>

## Antes de começar: os três pré-requisitos [#antes-de-começar-os-três-pré-requisitos]

Para uma chamada de uso real (criar instância ou enviar mensagem) ser aceita, três coisas precisam estar certas ao mesmo tempo:

1. **API key válida** no header `Authorization` (veja [Autenticação](#autenticacao)). Sem ela: `401`.
2. **Host correto para o modo da key** — o host `sandbox.*` é Teste e exige `pzk_test_`; o host `api.*` é Produção e exige `pzk_live_`. Key e host têm que casar. Se não casarem: `401`.
3. **Plano ativo** no Projeto (só em Produção) — criar instâncias e enviar mensagens exigem assinatura ativa. Sem ela: `402`. No modo Teste é grátis (com teto diário).

Leitura (listar/detalhar), pareamento por QR Code e "marcar como lida" só exigem os pré-requisitos 1 e 2 — **não** dependem de plano.

## Base URL e o prefixo `/v1` [#base-url-e-o-prefixo-v1]

Todas as rotas vivem sob o prefixo `/v1`. A URL completa de um endpoint é sempre `<BASE_URL>/v1/<caminho>`. A base URL define em qual **modo** (Teste ou Produção) você está operando:

| Modo                | Base URL                           | Prefixo da key | Cobrança                                          |
| ------------------- | ---------------------------------- | -------------- | ------------------------------------------------- |
| **Teste** (sandbox) | `https://sandbox.pepinozap.com.br` | `pzk_test_`    | grátis, com teto de 100 mensagens/dia por Projeto |
| **Produção** (live) | `https://api.pepinozap.com.br`     | `pzk_live_`    | exige plano ativo                                 |

É o **host que decide o modo**: qualquer host que comece com `sandbox` é Teste; os demais (`api.*`) são Produção. A API key **não** decide o modo — ela apenas precisa ser coerente com o host. Por isso uma `pzk_test_` chamando `api.*` (ou uma `pzk_live_` chamando `sandbox.*`) é rejeitada com `401`, mesmo sendo uma key real e válida.

```bash
# Teste (sandbox) — key pzk_test_
curl https://sandbox.pepinozap.com.br/v1/instances \
  -H "Authorization: Bearer pzk_test_xxxxxxxxxxxxxxxx"

# Produção (live) — key pzk_live_
curl https://api.pepinozap.com.br/v1/instances \
  -H "Authorization: Bearer pzk_live_xxxxxxxxxxxxxxxx"
```

<Callout type="info">
  Os dois modos são **100% isolados**: instâncias, conversas e webhooks criados
  em Teste nunca aparecem em Produção e vice-versa. Inclusive um `id` de
  instância de um modo, usado no outro modo, retorna `404` (não vaza entre
  ambientes). Valide a integração inteira em `sandbox.*` sem custo e, quando
  estiver pronto, troque a base URL para `api.*` e a key para `pzk_live_` — o
  resto do código permanece igual.
</Callout>

## Autenticação [#autenticação]

Toda chamada envia a API key no header `Authorization` com o esquema `Bearer`:

```
Authorization: Bearer <API_KEY>
```

A key é **escopada a um Projeto**: todas as instâncias e mensagens criadas ou listadas com ela pertencem a esse Projeto. A geração é self-service no painel (Integrações → API Keys), e a key crua aparece **uma única vez** na criação. Detalhes de cadastro, do toggle Teste/Produção e de revogação estão em [Autenticação](/docs/get-started/authentication).

**Exceção — SSE de QR Code.** O único endpoint que **não** usa o header é o stream de QR Code via Server-Sent Events (`GET /v1/instances/{id}/qr`), porque o `EventSource` do navegador não envia headers customizados. Nele a key vai pela query string, `?token=<API_KEY>`. Recomenda-se emitir um token efêmero (`POST /v1/instances/{id}/qr-token`, válido \~2 min) para não expor a key crua na URL. Todos os demais endpoints — inclusive o **polling** de QR (`/qr/poll`) — usam o header normalmente. Veja [Autenticação → Fallback `?token=`](/docs/get-started/authentication#fallback-token-apenas-para-sse) e [Sua primeira instância → Parear via QR Code](/docs/get-started/first-instance#parear-via-qr-code).

## Formato do campo `to` [#formato-do-campo-to]

Todos os endpoints de mensagem identificam o destinatário pelo campo `to`. Ele aceita **três formatos**, todos equivalentes — use o que for mais conveniente:

| Formato                        | Exemplo                        | Observação                       |
| ------------------------------ | ------------------------------ | -------------------------------- |
| Apenas dígitos (E.164 sem `+`) | `5511999999999`                | Forma mais simples; recomendada. |
| E.164 com `+`                  | `+5511999999999`               | O `+` é aceito.                  |
| JID completo                   | `5511999999999@s.whatsapp.net` | Forma "nativa" do WhatsApp.      |

O número deve incluir **código do país** e **DDD**, sem espaços, traços ou parênteses. Para o Brasil, o código do país é `55`.

<Callout type="warn">
  A API **não verifica**, no momento do envio, se o número existe no WhatsApp —
  a validação só confirma que o campo `to` não está vazio. Um `to` com número
  inexistente é aceito e enfileirado (resposta `202`), mas a mensagem
  simplesmente não é entregue, e **não** há evento de falha por número inválido.
  Garanta que o `to` está correto antes de enviar. Para envio a **grupos**, use
  o JID de grupo recebido nos webhooks; este guia trata de conversas individuais
  (1:1).
</Callout>

## Convenções de requisição e resposta [#convenções-de-requisição-e-resposta]

* **Método e corpo.** Endpoints de escrita usam `POST`/`PATCH`/`DELETE`; os de leitura usam `GET`. Requisições com corpo JSON exigem o header `Content-Type: application/json` (a exceção é o upload de mídia por arquivo, que é `multipart/form-data`).
* **Formato.** Pedidos e respostas são JSON (`UTF-8`), salvo o SSE de QR (`text/event-stream`) e o upload de mídia (multipart).
* **Códigos de sucesso por tipo de operação:**

| Operação                               | Sucesso          | Corpo da resposta                            |
| -------------------------------------- | ---------------- | -------------------------------------------- |
| Criar instância (`POST /v1/instances`) | `201 Created`    | A instância criada (objeto completo).        |
| Detalhar / listar / atualizar settings | `200 OK`         | A instância (ou a lista).                    |
| Remover instância (`DELETE`)           | `204 No Content` | Sem corpo.                                   |
| Enviar qualquer tipo de mensagem       | `202 Accepted`   | Confirmação de enfileiramento (veja abaixo). |

* **Confirmação de envio.** Os endpoints de envio respondem com um objeto curto que confirma o **enfileiramento**, não a entrega:

```json
{
  "queued": true,
  "to": "5511999999999"
}
```

Endpoints de mídia e sticker incluem também o campo `type` (ex.: `"type": "image"`). O `to` ecoado é exatamente o que você enviou.

## Envio assíncrono (202) e idempotência [#envio-assíncrono-202-e-idempotência]

O envio de mensagens é **assíncrono**. A resposta `202 Accepted` confirma apenas que a mensagem foi **enfileirada** — não que foi entregue nem lida. O processamento real (envio ao WhatsApp) ocorre logo em seguida, em segundo plano. A confirmação de **entrega e leitura** chega depois pelo evento `message.status` no [webhook](/docs/webhooks).

<Callout type="warn">
  **A instância precisa estar `CONNECTED` para a mensagem sair.** Enviar para
  uma instância que não está conectada (por exemplo, `DISCONNECTED` ou aguardando
  QR) **também** retorna `202` — mas a mensagem fica em espera e **não** é
  entregue, sem gerar evento de falha. Confirme `status == CONNECTED` (via
  `GET /v1/instances/{id}` ou `GET /v1/instances/{id}/qr/poll` com
  `connected == true`) **antes** de enviar. Instâncias em estado terminal
  (`BANNED`/`ERROR`) são bloqueadas já no envio, com `409` (veja a tabela de
  erros).
</Callout>

**Idempotência.** Os endpoints de envio **não são idempotentes**: cada chamada enfileira um novo envio. Se a sua requisição falhar com erro de rede ou timeout **depois** de a API ter aceitado o job, repeti-la pode resultar em mensagem duplicada. Para reenviar com segurança, controle a deduplicação no seu lado (por exemplo, só repita quando tiver certeza de que a chamada anterior **não** retornou `202`). Já os erros `402` (plano/créditos), `401`, `403`, `404`, `409` e `429` são respostas de negócio/validação — **não** os repita em loop: corrija a causa (assinar, recarregar, conectar a instância, corrigir a key) e tente de novo.

## Envelope de erro [#envelope-de-erro]

Toda resposta de erro segue um envelope único e estável:

```json
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "campos to e body são obrigatórios"
  }
}
```

O campo `code` identifica a **categoria** de forma estável — programe o seu tratamento de erros em cima dele. O campo `message` é apenas legível por humanos (em português) e pode mudar; **não** faça parsing nem `switch` sobre a `message`, com uma exceção prática anotada na tabela (o `402` de plano vs. créditos).

| Status | `code`             | Significado                                                                                                                                                                                          |
| ------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `BAD_REQUEST`      | Corpo JSON inválido ou campo obrigatório faltando/ inválido (ex.: `to` vazio, `type` de mídia inválido).                                                                                             |
| `401`  | `UNAUTHORIZED`     | API key ausente, inválida, **ou** do modo errado para o host (`pzk_live_` no sandbox, ou vice-versa).                                                                                                |
| `402`  | `PAYMENT_REQUIRED` | Sem plano ativo **ou** sem créditos da API (ver [abaixo](#pre-requisito-plano-ativo-402)). Distinga os dois casos pela `message`. **Não** repita a chamada — exige ação humana (assinar/recarregar). |
| `403`  | `FORBIDDEN`        | Projeto inativo ou sem permissão.                                                                                                                                                                    |
| `404`  | `NOT_FOUND`        | Recurso não encontrado no Projeto da key — inclui o caso de a instância existir, mas pertencer a **outro modo** (teste/produção) ou a outro Projeto.                                                 |
| `409`  | `CONFLICT`         | Ao **criar** instância: cota de números do plano atingida. Ao **enviar**: instância não-operacional (`BANNED` ou `ERROR`).                                                                           |
| `429`  | `RATE_LIMITED`     | **Apenas no sandbox:** teto diário do Projeto atingido (100 mensagens/dia por Projeto, enviadas + recebidas; o contador zera à meia-noite no horário de São Paulo). Em produção não ocorre.          |
| `5xx`  | `INTERNAL_ERROR`   | Falha inesperada do servidor. Em geral é transitória — pode ser repetida com recuo (backoff).                                                                                                        |

<Callout type="info">
  O `404` é deliberadamente "cego" ao motivo: tentar acessar uma instância de
  outro modo (ex.: um `id` de produção usando a base URL de sandbox) ou de outro
  Projeto retorna `404`, e não um `403`. Isso evita vazar a existência de
  recursos entre Projetos e entre ambientes. Se um `id` que você "tem certeza"
  que existe dá `404`, confira primeiro se a base URL e o prefixo da key batem
  com o modo em que a instância foi criada.
</Callout>

## Pré-requisito: plano ativo (402) [#pré-requisito-plano-ativo-402]

Além da API key, o Projeto precisa de uma **assinatura ativa** para **criar instâncias** e **enviar mensagens** em produção. Sem plano ativo, esses endpoints retornam `402` (`PAYMENT_REQUIRED`):

```json
{
  "error": {
    "code": "PAYMENT_REQUIRED",
    "message": "É necessário um plano ativo para usar este recurso. Assine em \"Meu plano\"."
  }
}
```

Endpoints que **exigem** plano ativo (em produção):

* `POST /v1/instances` — criar instância
* `POST /v1/instances/{id}/messages` e todos os demais envios (`/media`, `/media-upload`, `/location`, `/contact`, `/sticker`, `/reaction`, `/poll`)

Endpoints de **leitura** (listar/detalhar instâncias), o **pareamento por QR Code** (SSE, polling e emissão de token) e o **marcar como lida** (`/messages/read`) continuam liberados mesmo sem plano.

**Modo Teste é grátis.** Em `sandbox.*` a assinatura **não** é exigida — você só esbarra no teto diário (`429`). Use o sandbox para validar a integração inteira sem custo.

**Créditos da API (também `402`).** Projetos no modo **API** consomem créditos pré-pagos a cada mensagem **enviada pela API key** (envios pelo painel/inbox **não** consomem). Quando o saldo zera, os endpoints de envio retornam o **mesmo** `402` `PAYMENT_REQUIRED`, mas com a `message` indicando créditos insuficientes (ex.: `"créditos da API insuficientes — recarregue em Meu plano"`). É por isso que, para o `402`, a `message` é o único sinal que distingue os dois casos: **falta de plano** versus **falta de créditos**.

A resolução de ambos (assinar ou recarregar) é em **Meu plano**, no painel — é um gate de negócio que exige ação humana, **não** repita a chamada. Projetos de cortesia (isentos, marcados pelo time PEPINO) nunca recebem este erro. Detalhes em [Autenticação → Plano ativo](/docs/get-started/authentication#plano-ativo-erro-402).

## Como navegar a referência [#como-navegar-a-referência]

Os endpoints abaixo agrupam-se em quatro áreas. Cada link leva à página do endpoint, com todos os campos, exemplos por linguagem e o playground "Try it".

### Instâncias [#instâncias]

Conectar e gerenciar os números de WhatsApp do Projeto.

| Operação                       | Método e caminho                    | Referência                                                            |
| ------------------------------ | ----------------------------------- | --------------------------------------------------------------------- |
| Listar instâncias              | `GET /v1/instances`                 | [listInstances](/docs/api/instancias/listInstances)                   |
| Criar instância                | `POST /v1/instances`                | [createInstance](/docs/api/instancias/createInstance)                 |
| Detalhar instância             | `GET /v1/instances/{id}`            | [getInstance](/docs/api/instancias/getInstance)                       |
| Atualizar configurações        | `PATCH /v1/instances/{id}/settings` | [updateInstanceSettings](/docs/api/instancias/updateInstanceSettings) |
| Remover instância              | `DELETE /v1/instances/{id}`         | [deleteInstance](/docs/api/instancias/deleteInstance)                 |
| Parear via QR Code (SSE)       | `GET /v1/instances/{id}/qr`         | [streamInstanceQR](/docs/api/instancias/streamInstanceQR)             |
| Parear via QR Code (polling)   | `GET /v1/instances/{id}/qr/poll`    | [pollInstanceQR](/docs/api/instancias/pollInstanceQR)                 |
| Token efêmero para o SSE de QR | `POST /v1/instances/{id}/qr-token`  | [createQrToken](/docs/api/instancias/createQrToken)                   |

O ciclo de vida da instância e o pareamento estão detalhados em [Sua primeira instância](/docs/get-started/first-instance).

### Mensagens [#mensagens]

Todos os envios são por `POST /v1/instances/{id}/messages/...`, respondem `202` e são assíncronos.

| Tipo                      | Caminho                          | Referência                                                     |
| ------------------------- | -------------------------------- | -------------------------------------------------------------- |
| Texto                     | `POST .../messages`              | [sendTextMessage](/docs/api/mensagens/sendTextMessage)         |
| Mídia (por URL)           | `POST .../messages/media`        | [sendMediaMessage](/docs/api/mensagens/sendMediaMessage)       |
| Mídia (upload de arquivo) | `POST .../messages/media-upload` | [sendMediaUpload](/docs/api/mensagens/sendMediaUpload)         |
| Localização               | `POST .../messages/location`     | [sendLocationMessage](/docs/api/mensagens/sendLocationMessage) |
| Contato                   | `POST .../messages/contact`      | [sendContactMessage](/docs/api/mensagens/sendContactMessage)   |
| Sticker                   | `POST .../messages/sticker`      | [sendStickerMessage](/docs/api/mensagens/sendStickerMessage)   |
| Reação                    | `POST .../messages/reaction`     | [sendReactionMessage](/docs/api/mensagens/sendReactionMessage) |
| Enquete                   | `POST .../messages/poll`         | [sendPollMessage](/docs/api/mensagens/sendPollMessage)         |
| Marcar como lida          | `POST .../messages/read`         | [markMessagesRead](/docs/api/mensagens/markMessagesRead)       |

Notas úteis: em **mídia**, o campo `type` aceita `image`, `video`, `audio` ou `document`, e um áudio com `ptt: true` vira nota de voz; em **reação**, `emoji` vazio remove a reação; em **enquete**, `options` deve ter de 2 a 12 alternativas. Os campos exatos de cada tipo estão na página da referência correspondente.

### Webhooks [#webhooks]

O integrador **recebe** eventos (mensagem recebida, status de envio e status da instância) em uma URL própria, cadastrada **via API** (self-service):

| Operação          | Método e caminho           | Referência                                        |
| ----------------- | -------------------------- | ------------------------------------------------- |
| Cadastrar webhook | `POST /v1/webhooks`        | [createWebhook](/docs/api/webhooks/createWebhook) |
| Listar webhooks   | `GET /v1/webhooks`         | [listWebhooks](/docs/api/webhooks/listWebhooks)   |
| Remover webhook   | `DELETE /v1/webhooks/{id}` | [deleteWebhook](/docs/api/webhooks/deleteWebhook) |

O formato do payload, a assinatura HMAC e a lista de eventos estão em [Webhooks](/docs/webhooks). Para receber eventos em tempo real por conexão persistente em vez de HTTP, veja também o [WebSocket](/docs/webhooks/websocket).

### Inbox (CRM) [#inbox-crm]

Consultar as conversas e mensagens armazenadas pela plataforma e gerir o atendimento (status, atribuição):

| Operação                        | Método e caminho                            | Referência                                                       |
| ------------------------------- | ------------------------------------------- | ---------------------------------------------------------------- |
| Listar conversas                | `GET /v1/inbox/conversations`               | [listInboxConversations](/docs/api/inbox/listInboxConversations) |
| Detalhar conversa               | `GET /v1/inbox/conversations/{id}`          | [getInboxConversation](/docs/api/inbox/getInboxConversation)     |
| Listar mensagens da conversa    | `GET /v1/inbox/conversations/{id}/messages` | [listInboxMessages](/docs/api/inbox/listInboxMessages)           |
| Mudar status (encerrar/reabrir) | `POST /v1/inbox/conversations/{id}/status`  | [setConversationStatus](/docs/api/inbox/setConversationStatus)   |
| Atribuir a um atendente         | `POST /v1/inbox/conversations/{id}/assign`  | [assignConversation](/docs/api/inbox/assignConversation)         |

### Gestão (CRM) [#gestão-crm]

As ferramentas de gestão do atendimento. O modelo de dados e o passo a passo de cada uma estão no guia [CRM e gestão](/docs/crm):

| Área           | O que faz                                      | Referência                                                                                 |
| -------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Relatórios     | Métricas agregadas do atendimento              | [getReportsSummary](/docs/api/relatorios/getReportsSummary) · [guia](/docs/crm/relatorios) |
| Etiquetas      | Classificar e filtrar conversas                | [listLabels](/docs/api/etiquetas/listLabels) · [guia](/docs/crm/etiquetas-encerramento)    |
| Justificativas | Motivos de encerramento                        | [listCloseReasons](/docs/api/justificativas/listCloseReasons)                              |
| Fluxos         | Construtor de chatbot por número               | [listFlows](/docs/api/fluxos/listFlows) · [guia](/docs/crm/fluxos)                         |
| Funil          | Funil cujas etapas são regras sobre a conversa | [listFunnels](/docs/api/funil/listFunnels) · [guia](/docs/crm/funil)                       |

## Próximos passos [#próximos-passos]

* [Autenticação](/docs/get-started/authentication) — gerar a API key e entender os modos teste/produção.
* [Sua primeira instância](/docs/get-started/first-instance) — conectar um número via QR Code.
* [Enviar uma mensagem de texto](/docs/api/mensagens/sendTextMessage) — o primeiro envio após o pareamento.
* [Webhooks](/docs/webhooks) — receber eventos de mensagens e de status da instância.
* [CRM e gestão](/docs/crm) — relatórios, etiquetas, encerramento, equipe e o construtor de chatbot.
