# Autenticação (/docs/get-started/authentication)





Toda requisição à API do PEPINO ZAP é autenticada por uma **API key**, escopada a um **Projeto**. A key identifica quem está chamando e a qual Projeto pertence: todas as instâncias, conversas e mensagens criadas ou listadas com uma key pertencem ao Projeto dela. Não há login/senha nas chamadas de API — só a key, enviada em cada requisição.

A geração de keys é **self-service**: você cria e revoga quantas quiser pelo painel, sem depender da autorização de um operador. Esta página cobre o fluxo completo, ponta a ponta:

1. Criar a conta e confirmar o e-mail.
2. Escolher o **modo** (teste ou produção) e gerar a key.
3. Enviar a key nas requisições (header `Authorization`).
4. O caso especial do **SSE de QR Code** (fallback `?token=`).
5. Todos os **erros** que a autenticação pode retornar (`401`, `402`, `403`, `429`) e como resolver cada um.

<Callout type="info">
  Resumo em uma frase: o **host** decide o modo (`sandbox.*` = teste, `api.*` =
  produção) e a **key tem que casar** com ele (`pzk_test_` no sandbox,
  `pzk_live_` na produção). Key no host errado retorna `401`.
</Callout>

## Criando a conta e gerando keys [#criando-a-conta-e-gerando-keys]

A conta e as keys nascem no painel. O fluxo abaixo é feito **uma vez**; depois você só gera/revoga keys conforme precisar.

1. **Cadastre-se** (empresa, CNPJ, e-mail e senha) em `app.pepinozap.com.br`. O cadastro **exige confirmação de e-mail** — você recebe um link logo após o envio do formulário.
2. **Confirme o e-mail** pelo link enviado. O login **só é liberado depois da confirmação** — antes disso, tentar entrar não funciona.
3. **Faça login no painel** e vá em **Integrações → API Keys**. No topo de Integrações há um **toggle Teste / Produção** — escolha o modo desejado **antes** de gerar a key.
4. Clique em gerar, dê um **nome** à key (ele só serve para você identificá-la na lista — não aparece para ninguém) e, opcionalmente, defina uma validade.

A **key crua aparece uma única vez**, no momento da criação. Guarde-a em local seguro (um gerenciador de segredos, uma variável de ambiente do seu servidor). **Não há como recuperá-la depois** — o painel só guarda um hash e um prefixo de exibição. Se você perder a key, **revogue-a e crie outra**.

O **modo** da key é o do toggle no momento em que ela foi gerada e fica gravado nela para sempre:

* toggle em **Teste** → key começa com `pzk_test_`;
* toggle em **Produção** → key começa com `pzk_live_`.

<Callout type="warn">
  O **nome** da key é obrigatório. Gerar sem nome retorna `400` com a mensagem
  "nome obrigatório (identifica a key na listagem)". Escolha algo que diga onde a
  key é usada (ex.: `backend-producao`, `integracao-erp`) — isso facilita
  revogar a certa depois.
</Callout>

### Anatomia de uma API key [#anatomia-de-uma-api-key]

Uma key crua tem a forma `pzk_<modo>_<segredo>`, por exemplo:

```
pzk_test_k4m2qz7r3v8n6t1p9s5w0xab
└──┬───┘ └──────────┬───────────┘
 prefixo          segredo (aleatório)
```

* **Prefixo** (`pzk_test_` ou `pzk_live_`): indica o modo. É por ele que a API decide, logo de cara, se a key combina com o host.
* **Segredo**: parte aleatória de alta entropia. É o que você **não** deve expor. O painel mostra apenas um **prefixo de exibição** (o prefixo do modo + os 6 primeiros caracteres do segredo) para você reconhecer a key na lista sem revelá-la inteira.

## Modos: teste e produção [#modos-teste-e-produção]

Uma **única conta**, com dois modos — no mesmo espírito do test/live do Stripe. **Não há conta nem ambiente separado**: é a mesma conta, os mesmos logins. O que muda é o modo da requisição, e o modo é definido pelo **host** da API. A key tem que **casar** com o host, mas **não é ela que decide o modo**.

| Modo         | Base URL da API                    | Prefixo da key | Cobrança                |
| ------------ | ---------------------------------- | -------------- | ----------------------- |
| **Teste**    | `https://sandbox.pepinozap.com.br` | `pzk_test_`    | grátis, com teto diário |
| **Produção** | `https://api.pepinozap.com.br`     | `pzk_live_`    | exige plano ativo       |

### O host decide o modo; a key tem que casar [#o-host-decide-o-modo-a-key-tem-que-casar]

O modo é resolvido pelo **hostname** da URL que você chama:

* host que &#x2A;*começa com `sandbox`** (`sandbox.pepinozap.com.br`) → **modo teste**;
* qualquer outro host (`api.pepinozap.com.br`) → **modo produção**.

Depois de resolver o modo pelo host, a API **confere o prefixo da key**: ela precisa ser do mesmo modo. As combinações válidas e inválidas:

| Host                       | Key            | Resultado                  |
| -------------------------- | -------------- | -------------------------- |
| `sandbox.pepinozap.com.br` | `pzk_test_...` | OK (modo teste)            |
| `api.pepinozap.com.br`     | `pzk_live_...` | OK (modo produção)         |
| `sandbox.pepinozap.com.br` | `pzk_live_...` | `401` — key do modo errado |
| `api.pepinozap.com.br`     | `pzk_test_...` | `401` — key do modo errado |

Quando o prefixo não casa com o host, a resposta é `401 UNAUTHORIZED&#x60; com a mensagem &#x2A;*"chave de API inválida para este ambiente/modo"**. Esse erro é, na prática, o engano mais comum: usar uma key de teste apontando para `api.*`, ou vice-versa. Antes de investigar qualquer outra coisa, confirme que **o prefixo da key bate com o host**.

### Isolamento de dados por modo [#isolamento-de-dados-por-modo]

Os dados são **isolados por modo**. Instâncias, conversas e webhooks criados em **Teste** nunca aparecem em **Produção**, e vice-versa. Consequências práticas:

* Uma instância criada no sandbox **não existe** em produção (consultá-la lá dá `404`).
* Listar keys/instâncias retorna **apenas** as do modo da requisição.
* No painel, o **toggle filtra** o que você vê em Integrações. O CRM (Inbox, Funil) é sempre Produção.

<Callout type="info">
  Se um `GET` por id retornar `404` ("instância não encontrada") mesmo você
  "tendo certeza" de que ela existe, verifique se não está chamando o **host do
  modo errado**. Recurso de teste só aparece no `sandbox.*`; recurso de produção
  só no `api.*`.
</Callout>

### Teste é grátis; produção exige plano [#teste-é-grátis-produção-exige-plano]

O **modo teste é grátis e self-service**: criar instâncias e enviar mensagens **não exigem plano ativo** — você valida a integração inteira sem assinar nada. Para que o teste grátis não vire "produção de graça", ele tem um **teto diário** de **100 mensagens por dia, por Projeto** (enviadas + recebidas; o dia fecha à meia-noite no fuso de São Paulo). Ao ultrapassar o teto, os envios retornam `429 RATE_LIMITED`.

A **produção** exige uma **assinatura ativa no Projeto** para as ações de uso real (conectar número, enviar mensagem) — é o `402` detalhado [mais abaixo](#plano-ativo-erro-402). Resumindo: teste à vontade no sandbox e só pague ao ir para produção.

## Gerando keys pela API (self-service) [#gerando-keys-pela-api-self-service]

Além do botão no painel, você pode gerar, listar e revogar keys **pela API**, sob o grupo `/v1/keys`. Isso é útil para automatizar a rotação de chaves.

<Callout type="warn">
  Gerenciar keys exige **login de usuário** (a sessão do painel, um token de
  usuário) — **uma API key não pode gerar outras**. Se você chamar `/v1/keys`
  autenticando com uma `pzk_...`, recebe `403` com "gestão de chaves requer login
  de usuário". Isso é proposital: limita o estrago caso uma key vaze (ela não
  consegue se "auto-multiplicar"). Os exemplos desta seção assumem o token de
  usuário do painel no `Authorization`.
</Callout>

### Obtendo o token de usuário — `POST /v1/auth/login` [#obtendo-o-token-de-usuário--post-v1authlogin]

No **navegador**, a sessão do painel vive em cookies `httpOnly` (o token não é exposto ao JavaScript). Para automatizar a rotação de keys **do seu backend**, chame o login **server-to-server** pedindo o token no corpo com o header `X-Token-Response: body`:

```bash
curl -X POST https://api.pepinozap.com.br/v1/auth/login \
  -H "Content-Type: application/json" \
  -H "X-Token-Response: body" \
  -d '{ "email": "voce@empresa.com", "password": "sua-senha" }'
```

Resposta `200 OK` com `accessToken` (curto) e `refreshToken`. Use o `accessToken` como `Bearer <TOKEN_DE_USUARIO>` nos exemplos abaixo. **Sem** esse header, o login responde só com a sessão em cookie (o fluxo do painel) — nunca chame o login com credencial de usuário a partir do navegador de uma página pública.

### Criar uma key — `POST /v1/keys` [#criar-uma-key--post-v1keys]

Corpo da requisição:

| Campo           | Tipo   | Obrigatório | Descrição                                                                                  |
| --------------- | ------ | ----------- | ------------------------------------------------------------------------------------------ |
| `name`          | string | sim         | Identifica a key na listagem. Não pode ser vazio.                                          |
| `expiresInDays` | número | não         | Validade em dias a partir de agora (inteiro `>= 1`). Sem este campo, a key **não expira**. |

O **modo** da key gerada (e portanto o prefixo `pzk_test_`/`pzk_live_`) segue o modo da requisição — no painel/API de usuário, o toggle Teste/Produção.

```bash
curl -X POST https://api.pepinozap.com.br/v1/keys \
  -H "Authorization: Bearer <TOKEN_DE_USUARIO>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "backend-producao", "expiresInDays": 90 }'
```

Resposta `201 Created&#x60; — repare no campo &#x2A;*`key`** com a key crua (aparece **só aqui, uma vez**):

```json
{
  "id": "key_abc123",
  "name": "backend-producao",
  "keyPrefix": "pzk_live_k4m2qz",
  "status": "active",
  "lastUsedAt": null,
  "expiresAt": "2026-09-03T12:00:00Z",
  "revokedAt": null,
  "createdAt": "2026-06-05T12:00:00Z",
  "key": "pzk_live_k4m2qz7r3v8n6t1p9s5w0xab"
}
```

* `key` — a key crua completa. **Guarde-a agora**; ela não volta a aparecer.
* `keyPrefix` — o prefixo de exibição (modo + 6 caracteres), seguro de logar/mostrar.
* `status` — `active`, `revoked` ou `expired`, derivado de `revokedAt`/`expiresAt`.
* `expiresAt` — `null` se você não enviou `expiresInDays`.

<Callout type="info">
  Criar uma key em **produção** exige **plano ativo** (gerar key = habilitar uso
  via integração). Sem assinatura, `POST /v1/keys` retorna `402`
  (`PAYMENT_REQUIRED`). Em **teste**, gerar keys é livre. Ver
  [Plano ativo (erro 402)](#plano-ativo-erro-402).
</Callout>

### Listar suas keys — `GET /v1/keys` [#listar-suas-keys--get-v1keys]

Retorna **apenas as keys do modo atual** (o toggle Teste/Produção filtra). A key crua **não** é incluída — só metadados.

```bash
curl https://api.pepinozap.com.br/v1/keys \
  -H "Authorization: Bearer <TOKEN_DE_USUARIO>"
```

Resposta `200 OK`:

```json
{
  "items": [
    {
      "id": "key_abc123",
      "name": "backend-producao",
      "keyPrefix": "pzk_live_k4m2qz",
      "status": "active",
      "lastUsedAt": "2026-06-05T13:40:00Z",
      "expiresAt": "2026-09-03T12:00:00Z",
      "revokedAt": null,
      "createdAt": "2026-06-05T12:00:00Z"
    }
  ]
}
```

O campo `lastUsedAt` ajuda a identificar keys ociosas (candidatas a revogar). Ele é atualizado de forma assíncrona a cada uso, então pode levar alguns instantes para refletir a última chamada.

### Revogar uma key — `DELETE /v1/keys/{id}` [#revogar-uma-key--delete-v1keysid]

Revoga **imediatamente** a key indicada (use o `id`, não o `keyPrefix`). A partir daí, qualquer requisição com aquela key retorna `401`.

```bash
curl -X DELETE https://api.pepinozap.com.br/v1/keys/key_abc123 \
  -H "Authorization: Bearer <TOKEN_DE_USUARIO>"
```

Sucesso: `204 No Content` (sem corpo). Se o `id` não existir, não for do seu Projeto, ou for de outro modo, a resposta é `404` ("chave não encontrada").

<Callout type="warn">
  A revogação é **definitiva e não tem desfazer**. Não dá para "reativar" uma key
  revogada — gere uma nova. Por isso, ao trocar uma chave em produção, **gere a
  nova e atualize sua aplicação primeiro**, e só então revogue a antiga, evitando
  uma janela sem key válida.
</Callout>

## Como enviar a API key [#como-enviar-a-api-key]

Em **todas** as chamadas REST, envie a key no header `Authorization` com o esquema `Bearer`:

```bash
curl https://sandbox.pepinozap.com.br/v1/instances \
  -H "Authorization: Bearer pzk_test_xxxxxxxxxxxxxxxx"
```

Exemplo com `fetch` (JavaScript):

```javascript
const res = await fetch('https://sandbox.pepinozap.com.br/v1/instances', {
  headers: {
    Authorization: 'Bearer pzk_test_xxxxxxxxxxxxxxxx',
  },
});
```

Pontos de atenção ao montar o header:

* O esquema &#x2A;*`Bearer ` (com um espaço)** é obrigatório. Mandar a key "pelada", sem `Bearer`, é tratado como ausência de Authorization → `401`.
* A key **não** vai na URL nem no corpo — só no header (exceção: o SSE de QR, a seguir).
* Use sempre **HTTPS**. A key é um segredo de acesso total ao Projeto.

## Fallback `?token=` (apenas para SSE) [#fallback-token-apenas-para-sse]

O endpoint de QR Code via stream (`GET /v1/instances/{id}/qr`) usa **Server-Sent Events**. O `EventSource` do navegador **não envia headers customizados**, então não há como mandar o `Authorization` nesse caso. Para esse endpoint — e **somente para ele** — a API aceita a credencial na **query string**, no parâmetro `?token=`.

Há duas formas de preencher o `?token=`:

1. **A própria key crua** (mais simples). Funciona, mas coloca a key na URL:

   ```bash
   curl -N "https://sandbox.pepinozap.com.br/v1/instances/{id}/qr?token=pzk_test_xxxxxxxxxxxxxxxx"
   ```

2. **Um token efêmero de SSE** (recomendado para o navegador). Gere com `POST /v1/instances/{id}/qr-token` e use o token retornado no `?token=`. Esse token é &#x2A;*curto (validade \~2 min)** e **escopado a uma única instância** (só vale na rota de QR daquela instância), limitando o impacto se vazar em um log ou no `Referer`.

```javascript
const id = 'inst_...';
const token = 'pzk_test_xxxxxxxxxxxxxxxx'; // ou o token efêmero de qr-token

const source = new EventSource(
  `https://sandbox.pepinozap.com.br/v1/instances/${id}/qr?token=${token}`,
);
```

<Callout type="warn">
  Utilize `?token=` **exclusivamente** no endpoint SSE de QR. Nas demais
  chamadas, use sempre o header `Authorization` — a query string aparece em logs
  de servidor, proxies e no `Referer`, então expor a key crua ali é arriscado.
  Para o QR no navegador, prefira o **token efêmero** de `qr-token` justamente por
  isso. O passo a passo do pareamento está em
  [Sua primeira instância](/docs/get-started/first-instance#parear-via-qr-code).
</Callout>

## Erros esperados [#erros-esperados]

Todos os erros da API seguem o **mesmo envelope**:

```json
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "explicação legível do que aconteceu"
  }
}
```

* `code` — rótulo estável, derivado do status HTTP. Use-o para tratar o erro no seu código (é mais confiável que comparar a `message`).
* `message` — texto legível, em português, que diz **o que** deu errado.

Os status que a autenticação pode retornar:

| Status | `code`             | Quando ocorre                                                                                                                |
| ------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `UNAUTHORIZED`     | Key ausente, malformada, inválida, revogada, expirada, **ou do modo errado** para o host.                                    |
| `402`  | `PAYMENT_REQUIRED` | O Projeto não tem plano ativo (ou créditos da API zerados). Ver [Plano ativo](#plano-ativo-erro-402).                        |
| `403`  | `FORBIDDEN`        | Projeto **inativo** (key válida, mas o Projeto não está `ACTIVE`); ou gestão de keys tentada sem login de usuário.           |
| `429`  | `RATE_LIMITED`     | **Apenas no modo teste:** teto diário do Projeto atingido (100 mensagens/dia, enviadas + recebidas). Em produção não ocorre. |

### Detalhando o `401 UNAUTHORIZED` [#detalhando-o-401-unauthorized]

O `401` cobre várias causas. A `message` distingue cada uma — vale tratá-las separadamente ao depurar:

| `message`                                       | Causa                                                                                         | Como resolver                                                                                                  |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `Authorization Bearer ou ?token= ausente`       | Nenhuma credencial foi enviada (faltou o header, ou veio sem `Bearer`).                       | Envie `Authorization: Bearer <key>`.                                                                           |
| `chave de API inválida para este ambiente/modo` | O prefixo da key **não bate com o host** (`pzk_live_` no sandbox ou `pzk_test_` na produção). | Use a key do modo correto para o host (ver [O host decide o modo](#o-host-decide-o-modo-a-key-tem-que-casar)). |
| `token ou chave de API inválido`                | A key não corresponde a nenhuma key existente (digitada errada, truncada, ou inexistente).    | Confira se copiou a key inteira. Se perdeu a key, gere outra.                                                  |
| `chave de API revogada`                         | A key foi revogada (no painel ou via `DELETE /v1/keys/{id}`).                                 | Gere uma key nova.                                                                                             |
| `chave de API expirada`                         | A key passou da validade definida em `expiresInDays`.                                         | Gere uma key nova (sem `expiresInDays`, ou com prazo maior).                                                   |

Exemplo de resposta `401`:

```json
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "chave de API inválida para este ambiente/modo"
  }
}
```

<Callout type="info">
  Há um `401` específico do **painel** (login de usuário), não da API key: o
  `code` `SESSION_SUPERSEDED`, emitido quando a mesma conta é aberta em outro
  dispositivo (a sessão única invalida a anterior). **API keys não têm sessão** e
  nunca recebem esse erro — ele só aparece em chamadas autenticadas com o token de
  usuário do painel. Ao recebê-lo, faça login de novo.
</Callout>

### O `403 FORBIDDEN` [#o-403-forbidden]

O `403` significa que a credencial **é válida**, mas a ação não é permitida no estado atual:

* **Projeto inativo** — a key é reconhecida, mas o Projeto dela não está com status `ACTIVE` (suspenso, por exemplo). `message`: "projeto não está ativo". A regularização é feita pelo time PEPINO; entre em contato.
* **Gestão de keys sem login de usuário** — você tentou `/v1/keys` autenticando com uma API key em vez do token de usuário. `message`: "gestão de chaves requer login de usuário". Use a sessão do painel.

Exemplo de resposta `403`:

```json
{
  "error": {
    "code": "FORBIDDEN",
    "message": "projeto não está ativo"
  }
}
```

## Plano ativo (erro 402) [#plano-ativo-erro-402]

Ter uma API key válida **não basta** para operar em produção. As ações de **uso real** — conectar um número e enviar mensagens — exigem uma **assinatura ativa no Projeto** (modelo "pague antes, use depois"). Como a key é escopada a um Projeto, a regra vale igualmente para o painel e para a integração via API. **No modo teste, esse erro nunca ocorre** (teste é grátis).

Só a assinatura com status &#x2A;*`active`** libera. Qualquer outro estado (sem assinatura, `pending`, `paused`, `past_due`, `canceled`) resulta em `402`.

Endpoints que retornam `402` sem plano ativo (em produção):

* `POST /v1/instances` — criar instância;
* `POST /v1/instances/{id}/messages` e os demais envios sob `/v1/instances/{id}/messages/` (`/media`, `/media-upload`, `/location`, `/contact`, `/sticker`, `/reaction`, `/poll`);
* `POST /v1/keys` — gerar API key (habilitar uso via integração).

Endpoints de **leitura** (listar/detalhar instâncias e keys), o **pareamento por QR Code** e **marcar como lida** continuam liberados mesmo sem plano ativo — você consegue inspecionar o estado, mas não produzir tráfego.

Exemplo de resposta `402`:

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

<Callout type="info">
  **Como resolver:** assine um plano em **Meu plano**, no painel. Assim que a
  assinatura fica `active`, as chamadas voltam a funcionar **sem alterar uma
  linha de código** — não é preciso gerar nova key. Projetos de cortesia,
  marcados como isentos pelo time PEPINO, nunca recebem este erro.
</Callout>

### Créditos da API insuficientes [#créditos-da-api-insuficientes]

Além da falta de plano, o `402` também aparece por **créditos zerados**. Projetos no modo **API** consomem **créditos pré-pagos** a cada mensagem **enviada pela API** (envios feitos pelo painel **não** consomem créditos). Quando o saldo zera, os endpoints de **envio** passam a retornar `402` com `code` `PAYMENT_REQUIRED`, e a `message` indica créditos insuficientes.

A resolução é **recarregar créditos** em **Meu plano**. &#x2A;*Distinga os dois casos `402` pela `message`:** falta de plano ativo (texto sobre assinar) versus falta de créditos (texto sobre recarregar) — a ação a tomar é diferente em cada um.

### Add-on de campanhas (disparo em massa) [#add-on-de-campanhas-disparo-em-massa]

As rotas de **campanhas** (`/v1/campaigns`, criação/disparo) têm uma exigência extra: além do plano ativo, o Projeto precisa do &#x2A;*add-on "Disparo em massa"**. Sem ele, criar/disparar campanha retorna `402` com a mensagem orientando a ativar o add-on em **Meu plano**. Leitura de campanhas é livre, e no **modo teste** o add-on não é exigido. (Detalhes na documentação de campanhas.)

## Boas práticas de segurança [#boas-práticas-de-segurança]

* **Trate a key como senha.** Quem tiver a key tem acesso total ao Projeto. Não a coloque em repositórios, front-end, prints ou tickets de suporte.
* **Use variáveis de ambiente / cofre de segredos** no servidor — nunca hardcode a key no código.
* **Uma key por integração.** Dê nomes claros (ex.: `backend-prod`, `worker-fila`). Assim, se uma vazar, você revoga só ela sem derrubar as outras.
* **Defina validade** (`expiresInDays`) em keys de vida curta (scripts, testes pontuais) para que expirem sozinhas.
* **Rotacione com janela de transição:** gere a nova key, atualize a aplicação, confirme que funciona e só então revogue a antiga.
* **Comece pelo modo teste.** Valide a integração inteira no `sandbox.*` com uma `pzk_test_` antes de gerar a `pzk_live_` e ligar a produção.

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

* [Quick start](/docs/get-started/quick-start) — a primeira chamada de ponta a ponta.
* [Criar a primeira instância](/docs/get-started/first-instance) — criar, parear por QR Code e configurar uma instância.
