# Catcher — WhatsApp API · manual completo > Provedora de Tecnologia oficial da Meta: a API oficial do WhatsApp (WhatsApp Business Platform / Cloud API) para desenvolvedores e agentes de IA, com conformidade e escala, templates aprovados, webhooks assinados e eventos em tempo real (SSE/WebSocket). Base da API: https://api.catcher.one/v1 — auth via header X-API-Key (token bza_...). Teste grátis por 30 dias. > > Este arquivo é a documentação inteira da API em um único texto (gerado do > mesmo manual que alimenta https://catcher.one/docs). Índice curto: https://catcher.one/llms.txt --- # Catcher API — Documentação > **Base URL:** `https://api.catcher.one` > **Versão:** v1 > **Formato:** JSON (`Content-Type: application/json`) --- > **📘 Documentação em reconstrução.** A referência completa da API oficial do > WhatsApp — a WhatsApp Business Platform (Meta Cloud API), na qual a Catcher > opera como Provedora de Tecnologia oficial da Meta — está sendo reescrita. > Enquanto isso, a autenticação da plataforma está documentada abaixo. Para > começar, crie sua conta no [Console](https://app.catcher.one). **Nesta página** 1. [Autenticação](#1-autenticação) — métodos (JWT e API key), papéis e rate limiting 2. [Auth (Registro e Login)](#2-auth-registro-e-login) — criar conta e obter sessão 3. [Tokens de API](#3-tokens-de-api) — criar e revogar chaves de API --- ## 1. Autenticação A API suporta dois métodos de autenticação: ### JWT (Bearer Token) Obtido via `/v1/auth/login`. Enviar no header: ``` Authorization: Bearer ``` - Algoritmo: HS256 - Expiracao padrão: 15 minutos - Claims: `company_id`, `user_id`, `role`, `is_superadmin` - **Revalidacao de empresa:** em cada request autenticada com JWT (exceto `is_superadmin`), o servidor consulta o master DB para garantir que a empresa existe e está `active`. Empresa inexistente → `403` com `company not found`; suspensa → `403` com `company suspended`; falha de banco nessa consulta → `503` com `service unavailable`. O mesmo critério de empresa ativa se aplica a **API key** (token inválido contínua `401`). ### API Key Obtida via `/v1/tokens` ou retornada no registro (`POST /v1/auth/register`). Enviar no header: ``` X-API-Key: bza_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` - Formato: `bza_` + 60 caracteres hex - Armazenada como hash SHA256 no banco (nunca em texto plano) - Pode ser revogada a qualquer momento > **⚠️ ATENÇÃO — NÃO CONFUNDIR OS HEADERS:** > > | Tipo de credencial | Header correto | Exemplo | > |---|---|---| > | JWT (sessão browser) | `Authorization: Bearer ` | `Authorization: Bearer eyJhbG...` | > | API Key (integração/SDK) | `X-API-Key: ` | `X-API-Key: bza_4615fb31...` | > > **Usar `Authorization: Bearer bza_...` retorna `401 TOKEN_INVALID`** — o middleware JWT tenta decodificar o `bza_` como JWT e falha. O token `bza_` DEVE ser enviado via `X-API-Key`. > > O token retornado no `POST /v1/auth/register` é um **API key** (`bza_...`), NÃO um JWT. Use `X-API-Key` para todas as chamadas subsequentes com esse token. ### Roles (Papeis) | Role | Descricao | |------|-----------| | `owner` | Dono da empresa. Acesso total: usuários, tokens, instâncias, mensagens | | `admin` | Administrador. Gerencia instâncias, webhooks, filas | | `agent` | Agente. Envia mensagens, acessa chats/contatos | | `superadmin` | Administrador da plataforma. Acesso a rotas `/v1/admin/*` | ### Acesso por Role | Recurso | owner | admin | agent | |---------|:-----:|:-----:|:-----:| | Tokens e Usuários | x | - | - | | Instâncias (CRUD) | x | x | - | | Mensagens | x | x | x | | Chats e Contatos | x | x | x | | Presença e Perfil | x | x | x | | Grupos | x | x | x | | Mídia | x | x | x | | Fila | x | x | - | | Webhooks | x | x | - | | Admin | superadmin only | - | - | ### Rate Limiting - Baseado em Redis, por empresa - Limite configuravel por plano (padrão: 60 req/min) - Rotas `/v1/admin/*` não aplicam throttling adicional por request; o acesso depende de JWT válido com `is_superadmin=true` - Headers de resposta: - `X-RateLimit-Limit` - Total permitido por minuto - `X-RateLimit-Remaining` - Restantes no periodo - `X-RateLimit-Reset` - Timestamp Unix do reset - `Retry-After` - Segundos para aguardar (quando 429) --- ## 2. Auth (Registro e Login) ### POST /v1/auth/register Cria uma nova empresa (tenant) com o usuário owner. **Auth:** Nenhuma **Request:** ```json { "company_name": "Minha Empresa", "owner_email": "dono@empresa.com", "owner_name": "Joao Silva", "password": "MinhaSenh@123" } ``` | Campo | Tipo | Obrigatório | Descricao | |-------|------|:-----------:|-----------| | `company_name` | string | sim | Nome da empresa | | `owner_email` | string | sim | Email do owner | | `owner_name` | string | sim | Nome do owner | | `password` | string | sim | Senha (mínimo 12 caracteres) | **Resposta 201:** ```json { "company_id": 1, "token": "bza_xxxx...xxxx", "api_token": "bza_xxxx...xxxx", "token_id": 1, "last4": "xxxx", "slug": "minha-empresa", "email_verification_required": true, "message": "company registered successfully" } ``` | Campo | Tipo | Descricao | |-------|------|-----------| | `company_id` | **integer** | ID numerico da empresa criada (ex: `1`, `38`, `42` — NÃO é string) | | `token` | string | API token bootstrap `bza_...` — mesmo valor que `api_token` | | `api_token` | string | Alias de `token` (mesmo valor, mantido por retrocompatibilidade) | | `token_id` | integer | ID interno do token no banco | | `last4` | string | Últimos 4 caracteres do token (para exibição segura) | | `slug` | string | Slug URL-safe derivado do `company_name` | | `email_verification_required` | boolean | `true` quando o owner deve confirmar o email com o código 6 dígitos enviado por Resend (sempre que o registro usou senha). Integrações que não renderizam UI podem ignorar — `POST /v1/instances` e `/v1/tokens` e `/v1/webhooks` retornam 403 `EMAIL_NOT_VERIFIED` até que `POST /v1/auth/verify-email` seja chamado. | | `message` | string | Mensagem de confirmação | > **⚠️ `company_id` é INTEGER, não string.** JSON retorna `"company_id": 38` (número sem aspas). Tipagens de DTO que usem `string` para este campo falharão silenciosamente no `json.Unmarshal` / `JSON.parse` com erro de tipo. Use `int`, `int64`, ou `number`. > O `token` retornado no registro e um **API token bootstrap** (`bza_...`), não um JWT de sessão do browser. Para chamadas subsequentes, envie via `X-API-Key: bza_...` (ver seção Autenticação acima). Para iniciar a sessão web, chame `POST /v1/auth/login`. **Erros:** | Status | Código | Body | Descricao | |--------|--------|------|-----------| | `400` | `VALIDATION_ERROR` | `{"error":"password must be at least 12 characters","error_code":"VALIDATION_ERROR"}` | Campos faltando ou senha curta | | `409` | `EMAIL_ALREADY_REGISTERED` | `{"error":"email already registered","error_code":"EMAIL_ALREADY_REGISTERED"}` | Email já usado por outra empresa | | `409` | `SLUG_TAKEN` | `{"error":"company name already taken","error_code":"SLUG_TAKEN"}` | Slug do `company_name` já existe | > **Integrações automatizadas** (como o Catcher) que provisionam multiplas empresas com o mesmo email humano devem usar plus-addressing (`user+suffix@domain.com`) ou emails únicos por tenant para evitar `EMAIL_ALREADY_REGISTERED`. --- ### POST /v1/auth/login Autêntica um usuário e retorna JWT. **Auth:** Nenhuma **Request:** ```json { "email": "dono@empresa.com", "password": "MinhaSenh@123" } ``` **Resposta 200:** ```json { "token": "eyJhbGciOiJIUzI1NiIs...", "company_id": 1, "user_id": 1, "role": "owner", "email": "dono@empresa.com", "is_superadmin": false } ``` **Cookies de sessão enviados no login bem-sucedido:** - `biazap_refresh_token`: `HttpOnly`, usado para rotação de sessão - `biazap_csrf_token`: usado no header `X-CSRF-Token` em requests mutaveis quando o cookie de refresh estiver presente **Protecoes adicionais:** - lockout por conta após 5 falhas em 15 minutos - JWT HS256 com `jti` e revogacao em logout - refresh token com rotação a cada uso **Erros:** - `401` - Credenciais inválidas - `403` - Empresa suspensa - `429` - Conta temporariamente bloqueada por excesso de tentativas --- ### GET /v1/auth/oauth/{provider}/start Inicia o login/cadastro social via OAuth 2.0. `provider` aceita `google` ou `github`. **Auth:** Nenhuma **Query params:** | Campo | Tipo | Obrigatório | Descricao | |-------|------|:-----------:|-----------| | `mode` | string | não | `login` ou `signup`. Default: `login` | **Resposta 302:** redireciona para o provedor com `state`, `redirect_uri`, `client_id`, `response_type=code` e escopos: - Google: `openid email profile` - GitHub: `read:user user:email` **Erros JSON:** - `400 OAUTH_PROVIDER_INVALID` - provider diferente de `google` ou `github` - `503 OAUTH_PROVIDER_DISABLED` - provider sem `OAUTH__ENABLED=true`, `CLIENT_ID` ou `CLIENT_SECRET` - `503 SERVICE_UNAVAILABLE` - Redis indisponível para armazenar o `state` Quando o request e uma navegação de browser (`Accept: text/html`), esses erros redirecionam para `/login?oauth_error=` em vez de devolver JSON. --- ### GET /v1/auth/oauth/{provider}/callback Callback configurado no Google/GitHub. O backend válida `state`, troca `code` por access token, busca o perfil e exige email verificado. **Auth:** Nenhuma **Fluxo:** - Identidade OAuth existente: emite a mesma sessão do `POST /v1/auth/login` e redireciona para `/oauth/callback?provider=...`. - Email verificado já cadastrado: vincula automaticamente o provider ao usuário existente, emite sessão e redireciona para `/oauth/callback?provider=...`. - Email novo: cria um token pendente curto no Redis e redireciona para `/oauth/complete?provider=...&token=...`. **Cookies de sessão:** quando o callback conclui login de usuário existente, envia `biazap_refresh_token` e `biazap_csrf_token` com as mesmas regras do login por senha. **Erros via redirect:** falhas redirecionam para `/login?oauth_error=&provider=`. | Código | Quando ocorre | |--------|---------------| | `OAUTH_STATE_INVALID` | `state` ausente, expirado ou de outro provider | | `OAUTH_EMAIL_UNVERIFIED` | Provider não retornou email verificado | | `OAUTH_UPSTREAM_FAILED` | Falha no token exchange ou profile fetch | | `OAUTH_PROVIDER_INVALID` | Provider desconhecido | | `OAUTH_PROVIDER_DISABLED` | Provider desabilitado ou sem credenciais | --- ### POST /v1/auth/oauth/complete-signup Finaliza cadastro social novo pedindo apenas o nome da empresa. Cria empresa, tenant DB, usuário owner e vinculo OAuth; em seguida emite sessão browser. **Auth:** Nenhuma **Request:** ```json { "token": "pending_oauth_token", "company_name": "Minha Empresa" } ``` **Resposta 200:** mesmo payload de `POST /v1/auth/login`, mais cookies `biazap_refresh_token` e `biazap_csrf_token`. **Erros:** - `400 OAUTH_PENDING_INVALID` - token pendente inválido ou expirado - `400 BAD_REQUEST` - `company_name` inválido - `409 EMAIL_ALREADY_REGISTERED` - email foi cadastrado antes da conclusao - `409 CONFLICT` - nome/slug da empresa já existe - `500 REGISTRATION_FAILED` - falha ao provisionar empresa, usuário, token ou tenant **Variaveis de ambiente OAuth:** | Variavel | Descricao | |----------|-----------| | `OAUTH_FRONTEND_REDIRECT_URL` | Base do Console para redirects (`https://app.catcher.one`) | | `OAUTH_GOOGLE_ENABLED` / `OAUTH_GITHUB_ENABLED` | Habilita explicitamente cada provider (`true`/`false`) | | `OAUTH_GOOGLE_CLIENT_ID` / `OAUTH_GOOGLE_CLIENT_SECRET` | Credenciais Google | | `OAUTH_GOOGLE_REDIRECT_URL` | Callback cadastrado no Google (`https://api.../v1/auth/oauth/google/callback`) | | `OAUTH_GITHUB_CLIENT_ID` / `OAUTH_GITHUB_CLIENT_SECRET` | Credenciais GitHub | | `OAUTH_GITHUB_REDIRECT_URL` | Callback cadastrado no GitHub (`https://api.../v1/auth/oauth/github/callback`) | `*_AUTH_URL`, `*_TOKEN_URL`, `*_USER_INFO_URL` e `OAUTH_GITHUB_EMAILS_URL` existem para testes/overrides; em produção os defaults oficiais são usados. --- ### POST /v1/auth/refresh Rotaciona o refresh token e devolve um novo access token JWT. **Auth:** Cookie `biazap_refresh_token` **Headers recomendados:** ```http X-CSRF-Token: ``` **Resposta 200:** mesmo payload de `POST /v1/auth/login`. **Erros:** - `401` - Refresh token ausente, inválido ou expirado - `403` - Falha de CSRF quando houver cookie de refresh --- ### POST /v1/auth/logout Revoga o JWT atual, inválida o refresh token e limpa os cookies de sessão. **Auth:** JWT em `Authorization: Bearer ` **Headers recomendados:** ```http X-CSRF-Token: ``` **Resposta 204:** Sem corpo. --- ### POST /v1/auth/verify-email Válida o código de 6 dígitos enviado por email no momento do registro (ou por resend). Ao sucesso, marca `users.email_verified_at = NOW()`, limpa a state de verificação e reemite a sessão (mesmos cookies de `POST /v1/auth/login`). A resposta é idêntica ao login, mais o campo `email_verified: true`. **Auth:** JWT em `Authorization: Bearer ` (+ CSRF header quando via browser) **Request:** ```json { "code": "428193" } ``` **Resposta 200:** Idêntico ao login. ```json { "token": "", "company_id": 42, "user_id": 118, "role": "owner", "email": "dono@empresa.com", "is_superadmin": false, "email_verified": true } ``` **Erros:** | Status | Código | Quando | |---|---|---| | `400` | `EMAIL_VERIFICATION_CODE_INVALID` | Código com formato errado, ou não bate com o persistido. Contador `email_verification_attempts` é incrementado. | | `400` | `EMAIL_VERIFICATION_CODE_EXPIRED` | Código expirou (TTL de 30 minutos). Peça `resend` e tente de novo. | | `429` | `EMAIL_VERIFICATION_MAX_ATTEMPTS` | 5 tentativas erradas consecutivas — a state é limpa, obrigando `resend`. | | `409` | `EMAIL_ALREADY_VERIFIED` | Usuário já verificado (ex: outro tab). Cliente pode seguir direto. | --- ### POST /v1/auth/verify-email/resend Gera um novo código (invalidando qualquer anterior) e dispara o email via Resend. Respeita cooldown de 60 segundos entre requisições. **Auth:** JWT em `Authorization: Bearer ` **Resposta 202:** ```json { "message": "verification code sent", "cooldown_secs": 60, "expires_in": 1800 } ``` **Erros:** | Status | Código | Quando | |---|---|---| | `429` | `EMAIL_VERIFICATION_RESEND_COOLDOWN` | Enviou faz menos de 60 segundos. `Retry-After` header diz quantos segundos faltam. | | `409` | `EMAIL_ALREADY_VERIFIED` | Nada a reenviar. | --- ### PATCH /v1/auth/me Edita o nome do usuário autenticado e/ou o nome da empresa. Ambos os campos são opcionais (sem nenhum = no-op 200). `company_name` só é honrado para `role=owner`. **Auth:** JWT em `Authorization: Bearer ` **Request:** ```json { "name": "Renata Schelbauer", "company_name": "Catcher LTDA" } ``` **Resposta 200:** ```json { "user": { "id": 12, "email": "renata@catcher.one", "name": "Renata Schelbauer", "role": "owner" }, "company": { "id": 9, "name": "Catcher LTDA", "slug": "catcher-original-slug" } } ``` > **Slug não muda.** Mesmo se você renomear a empresa, o `slug` permanece o original (compatibilidade com integrações externas). **Erros:** | Status | Código | Quando | |---|---|---| | `400` | `BAD_REQUEST` | Campo vazio (após trim) ou maior que 255 caracteres | | `403` | `OWNER_ONLY` | Usuário não-owner tentou setar `company_name` | --- ### PATCH /v1/auth/me/password Troca a senha do usuário autenticado. Exige a senha atual + nova com mínimo 8 caracteres. Em sucesso, **revoga todas as sessões ativas** (refresh tokens) e bumpa `password_changed_at` (que inválida o JWT atual via claim `pwd_changed_at`). O JWT atual contínua válido até expirar (~15min); no próximo refresh o usuário é forçado a re-logar. **Auth:** JWT em `Authorization: Bearer ` **Request:** ```json { "current_password": "MinhaSenhaAtual", "new_password": "MinhaNovaSenha" } ``` **Resposta 200:** ```json { "message": "password updated", "requires_relogin": true } ``` **Erros:** | Status | Código | Quando | |---|---|---| | `400` | `PASSWORD_TOO_SHORT` | `new_password` com menos de 8 caracteres | | `400` | `MISSING_FIELD` | `current_password` vazio | | `401` | `INVALID_CURRENT_PASSWORD` | Senha atual incorreta | --- ### POST /v1/auth/me/email/start Solicita troca de email. Exige o **novo email** + a **senha atual**. Envia código de 6 dígitos para o **novo email** + notificação de aviso para o **email antigo**. Token persiste em Redis por 30min; uma segunda chamada antes da confirmação **sobrescreve** a primeira (last-write-wins). **Auth:** JWT em `Authorization: Bearer ` **Request:** ```json { "new_email": "renata.new@example.com", "current_password": "MinhaSenhaAtual" } ``` **Resposta 202:** ```json { "message": "verification code sent to the new email", "expires_in": 1800 } ``` **Erros:** | Status | Código | Quando | |---|---|---| | `400` | `BAD_REQUEST` | Email inválido ou igual ao email atual | | `400` | `MISSING_FIELD` | `new_email` ou `current_password` vazio | | `401` | `INVALID_CURRENT_PASSWORD` | Senha atual incorreta | | `409` | `EMAIL_ALREADY_TAKEN` | `new_email` já está em uso por outra conta | --- ### POST /v1/auth/me/email/confirm Confirma o código de 6 dígitos recebido no novo email. Em sucesso, troca `users.email` no DB e **revoga todos os refresh tokens**. Cliente deve descartar a sessão e redirecionar para `/login`. **Auth:** JWT em `Authorization: Bearer ` **Request:** ```json { "code": "123456" } ``` **Resposta 200:** ```json { "message": "email updated — please log in again", "requires_relogin": true } ``` **Erros:** | Status | Código | Quando | |---|---|---| | `400` | `EMAIL_CHANGE_CODE_INVALID` | Código não bate. Token contínua válido até 5 tentativas. | | `404` | `EMAIL_CHANGE_NOT_FOUND` | Sem `start` prévio ou TTL expirou | | `429` | `EMAIL_CHANGE_LOCKED` | 5 tentativas erradas; token foi destruído. Reinicie com `start` | --- ## 3. Tokens de API ### POST /v1/tokens Cria um novo token de API. **Auth:** Owner **Request:** ```json { "label": "producao", "role": "agent" } ``` | Campo | Tipo | Obrigatório | Descricao | |-------|------|:-----------:|-----------| | `label` | string | sim | Nome identificador do token | | `role` | string | não | Role do token (`owner`, `admin`, `agent`). Padrão: `agent` | **Resposta 201:** ```json { "token_id": 2, "token": "bza_xxxx...xxxx", "label": "producao", "role": "agent", "last4": "xxxx", "created_at": "2026-03-07T10:00:00Z" } ``` > **Importante:** O token so e exibido uma vez. Salve-o em local seguro. --- ### GET /v1/tokens Lista todos os tokens da empresa. **Auth:** Owner **Resposta 200:** ```json [ { "id": 1, "label": "producao", "last4": "xxxx", "role": "agent", "created_at": "2026-03-07T10:00:00Z" } ] ``` --- ### DELETE /v1/tokens/{tokenId} Revoga um token (não pode ser desfeito). **Auth:** Owner **Resposta 200:** ```json { "message": "token revoked", "token_id": 2 } ``` ---