# Integração do Atendro com o AtendroZAP Contrato revisado em **17/09/2026**, a partir do código deste repositório. O serviço continua na fase `laboratory`; funcionalidades implementadas e testadas localmente não equivalem a liberação para migrar clientes. Leia junto com o [OpenAPI](openapi.json), o [catálogo de endpoints](API_ENDPOINTS.md) e os [gates de piloto](PILOT_GATES.md). O [llms.txt](../llms.txt) é o índice para a IA que implementará o consumidor. ## URL e cadastro do servidor | Campo no Atendro | Valor/contrato | |---|---| | Nome de exibição sugerido | AtendroZAP — servidor 1 | | URL base da API | `https://1.atendro.cloud` | | Token do servidor | Valor de `ATENDROZAP_ADMIN_TOKEN` configurado **nesse servidor AtendroZAP**, guardado como segredo no backend | | Header administrativo | `admintoken: ` | | Tipo de provedor proposto | `atendrozap`; precisa ser implementado/aceito no consumidor antes do cadastro | | Console de administração | `https://wpp.atendro.cloud` — não usar como URL base da API | | Documentação central | `https://wpp.atendro.cloud/docs` — pública, atende todos os servidores | | Contrato e índice para IA | `https://wpp.atendro.cloud/openapi.json` e `https://wpp.atendro.cloud/llms.txt` | | Base local padrão | `http://127.0.0.1:8091` | O console centraliza a administração e a documentação. Cada servidor mantém sua URL de API e seu token: `https://1.atendro.cloud` é o servidor 1, não o endereço da central de documentação. Selecione o servidor correto antes de chamar a API. Gravar a URL sem `/instance`, `/v1`, query, fragmento ou barra final. Os caminhos do OpenAPI são acrescentados diretamente a ela. Em 17/09/2026, a leitura pública de `https://1.atendro.cloud/health/ready` respondeu `200 {"status":"ready"}`. Isso confirma disponibilidade HTTP/banco naquele momento; não verifica o token, a revisão instalada, o pareamento nem todas as operações do servidor. **O valor real do token não pertence a esta documentação, ao OpenAPI ou ao llms.txt.** O responsável o informa no cadastro privado do Atendro. Ele não é a senha do console, a chave do banco, o segredo do webhook ou um token da UAZAPI. Esta preparação não cadastra nem ativa o servidor no produto. O consumidor ainda tem bloqueios documentados para hosts diferentes de `uazapi.com`. Antes de habilitar o cadastro, adaptar o guard de URL, a restrição no banco e o proxy de mídia para o host explicitamente autorizado. Não basta trocar URL/token de um servidor UAZAPI existente. Vincular cada instância ao servidor, empresa e provedor corretos; não resolver autorização só pelo nome. ## Dois tokens, duas responsabilidades | Credencial | Origem | Onde usar | |---|---|---| | Token administrativo | Configuração `ATENDROZAP_ADMIN_TOKEN` no AtendroZAP | Capacidades, criação/listagem, administração por ID e restart do worker | | Token de instância | `instance.token` de `POST /instance/init` ou `POST /v1/instances/{id}/token` | Conexão/status, mensagens, grupos, proxy e webhook dessa instância | | Segredo de webhook | Gerado/configurado para o receptor e enviado como `secret` no registro | Verificar HMAC dos eventos; não autentica chamadas REST | | Token do link de mídia | Embutido no `fileURL` assinado | Acessar apenas aquele arquivo até `expiresAt` | Enviar exatamente um header do tipo exigido. `Authorization: Bearer`, query string e `instancekey` não substituem `admintoken`/`token`. O token admin não autentica rotas de instância. Header ausente, errado ou duplicado retorna `401 {"error":"unauthorized"}`. O backend do Atendro guarda os segredos; o navegador pede ações ao backend. `GET /instance/all` não recupera tokens e devolve `qrcode` vazio. Em perda do token, um administrador pode reemitir por ID. `graceSeconds` permite de 0 a 3600 segundos de convivência com o anterior; zero revoga imediatamente. Não reemitir token como parte de um simples teste de cadastro. ## Validar o cadastro sem iniciar sessão O botão “Testar servidor” do Atendro deve fazer apenas: 1. `GET /health/ready`, sem segredo. Esperar `200` e `status: ready`. 2. `GET /v1/capabilities`, com `admintoken`. Esperar `200`, `service: AtendroZAP` e ler o mapa `capabilities`. Um `401` aponta para credencial/header; um `200` de health não prova que a credencial funciona. 3. Opcionalmente `GET /instance/all`, com `admintoken`, para validar a listagem. Verificar a forma de array sem imprimir identidades ou guardar o corpo em log. Não usar connect, restart, disconnect, rotação de token, alteração de proxy, envio de mensagem ou chamada como teste de credencial. Não ativar instâncias existentes automaticamente após o teste. `max_instances=0` significa ilimitado no consumidor legado; `is_active=false` não desfaz a troca de provedor. Exemplo de contrato HTTP, com **marcador de segredo, não uma credencial**: ```http GET /v1/capabilities HTTP/1.1 Host: 1.atendro.cloud admintoken: ``` ## Criar e conectar uma instância O fluxo abaixo descreve a implementação. Executar pareamento real somente com autorização explícita e o telefone de laboratório do responsável. 1. Criar com `POST /instance/init`, header `admintoken`, JSON e uma `Idempotency-Key` nova para aquela criação: ```json {"name":"atendro-integracao-teste","systemName":"Atendro","companyId":"tenant-de-teste"} ``` Retorna **200**, não 201: `{response, connected:false, loggedIn:false, instance:{id, token, name, systemName, companyId, status, ...}}`. Criar não conecta. `name` é obrigatório; os três campos aceitam até 128 bytes UTF-8, sem controle nem espaços nas extremidades. 2. Persistir `instance.id`, token e vínculo com empresa/servidor em uma operação segura no backend. O token aparece na criação e na reemissão; o replay idempotente do init também pode repetir a resposta durante 15 minutos. 3. Se houver proxy autorizado, configurar `POST /instance/proxy` antes de conectar. `mode: internal` significa conexão direta; não existe pool gerenciado. 4. Configurar o webhook global com `POST /v1/webhook` antes do pareamento para receber a conexão. As novas instâncias herdam o destino; consultar `GET /webhook`. Use `POST /webhook` apenas para uma exceção por instância. 5. Executar `POST /instance/connect`, com header `token` e `{}` para QR. Para código de pareamento, enviar `{"phone":""}` substituindo o marcador por 8–15 dígitos. O marcador não é um telefone válido. 6. Mostrar `instance.qrcode` como imagem PNG em data URI ou `instance.paircode` apenas ao usuário autorizado. Não registrar esses campos em logs/analytics. 7. Consultar `GET /instance/status` enquanto conecta, com intervalo e prazo limitado no consumidor. Considerar a sessão pronta quando `connected` e `loggedIn` forem verdadeiros. `status` usa `connecting`, `connected` e `disconnected`. QR/código expiram; usar sempre a resposta atual. `POST /instance/restart` preserva a credencial. `POST /instance/disconnect` desvincula e limpa a credencial; `unlinked` informa se houve confirmação remota. `DELETE /instance` exclui. As rotas `/v1/instances/{id}` e suas ações permitem administração com `admintoken` sem precisar recuperar o token da instância. ## Chamadas de voz pelo WhatsApp O módulo de ligações usa uma instância própria, criada com `kind: calls`. Exigir `capabilities.calls=true` e `instance_kinds` contendo `calls` antes de oferecer discagem. A instância precisa estar conectada e seu token deve ficar no backend. Instâncias `whatsapp` continuam recebendo `501 calls_not_supported` em `/call/make`. `POST /call/make {number}` inicia uma chamada e retorna `201` com `callId`. As rotas `/call/answer`, `/call/reject`, `/call/hangup`, `/call/active` e `/call/{id}` atendem, recusam, encerram e consultam o estado. A ponte `GET /call/{id}/audio` faz upgrade WebSocket e transporta PCM s16le mono a 16 kHz nos dois sentidos. Apenas essa ponte aceita `?token=` como alternativa ao header; o console faz proxy e mantém a credencial fora do navegador. No painel, crie uma instância de tipo **Ligação**, pareie pelo QR e use **Ligar** numa conversa ou **Discar** na instância de ligação. A instância de voz deve pertencer ao mesmo servidor e ter token conhecido pelo painel. O evento `call` pode ser inscrito no webhook global ou próprio. O protocolo foi homologado na bancada para saída curta com áudio nos dois sentidos; latência medida, chamada longa, recebimento e defeito upstream #25 continuam pendentes. Consulte o [guia de ligações](/docs/guias/chamadas-de-voz). ## Mensagens, consultas e erros O [catálogo](API_ENDPOINTS.md) cobre todos os métodos/caminhos da API pública. O [OpenAPI](openapi.json) detalha corpos, headers, parâmetros e respostas. - Envios retornam `messageid`, `messageId` e `id` com o ID do WhatsApp. Persistir o ID antes de reconciliar ACK/eco; eventos podem chegar antes da resposta REST. A edição retorna o ID do original e um `editId` separado. - `number` aceita telefone com DDI ou JID de contato, grupo ou LID. Não fabricar número de telefone quando só existir `@lid`. - Usar `POST /message/find` para mensagens legadas/paginação. Histórico entra por padrão e não deve disparar IA/automação como mensagem nova. `GET /v1/messages` tem formato interno e não usa offset. - `timestamp` é Unix em segundos. `messageTimestamp` legado é milissegundos. Em `/chat/find`, **ambos** `wa_lastMsgTimestamp` e `wa_lastMessageTime` são segundos. Datas textuais como `expiresAt` usam RFC 3339. - Em `/send/media`, arquivos por URL exigem que o host do storage esteja em `ATENDROZAP_MEDIA_URL_ALLOWLIST`. Limite de envio: 32 MiB decodificados. PTT exige OGG/Opus; não há transcodificação. Arquivos de outros formatos saem como áudio comum com `ptt:false` e `note`. - `/message/download` pode acessar o WhatsApp e solicitar reenvio ao celular. Consumir a URL assinada inteira e observar `expiresAt`; não remontar o link. - `POST /chat/read` com `read:false` não marca como não lido: devolve `marked:0` e uma nota. `linkPreview` em texto é aceito e ignorado. | HTTP / `error` | Tratamento pelo Atendro | |---|---| | 400 | Corrigir entrada, opções ou allowlist; não repetir cegamente | | 401 `unauthorized` | Corrigir tipo/valor do header; não mostrar o segredo | | 403 em grupos | Conta sem associação/permissão de administrador | | 404 | Recurso inexistente; não presumir desconexão | | 405 | Método incorreto; consultar header `Allow` | | 409 `whatsapp_disconnected` | Mostrar estado de desconexão; reconectar só no fluxo autorizado | | 409 `session_owned_elsewhere` | Sessão pertence a outro worker; não criar outra instância | | 409 `media_reupload_pending` | Aguardar reenvio do arquivo ao servidor e tentar download depois | | 409 `idempotency_in_progress` | Aguardar, repetir a mesma requisição e chave | | 422 `idempotency_mismatch` | Corrigir reuso de chave com método, caminho ou corpo diferentes | | 422 `send_failed` / `number_not_on_whatsapp` | Falha definitiva daquele envio; inspecionar código sem vazar conteúdo | | 429 | Respeitar `Retry-After`; nada enviado nos casos `retry_later`/`too_many_requests` | | 500 `whatsapp_reachout_timelock` | Bloqueio 463 do WhatsApp; ler `error_key` e `details.reachout_timelock.until` quando presentes | | 501 `calls_not_supported` | Desabilitar ligação para o provedor; não retentar | | 502 `storage_unavailable` | Indisponibilidade do banco; respeitar `Retry-After` | | 503 em health | Banco não pronto; 503 também pode indicar recurso desabilitado em outras rotas | | 504 `send_ambiguous` / `action_ambiguous` | Pode ter sido aceito; reconciliar ID/status/eventos antes de qualquer reenvio | Usar `X-Request-Id` para correlação. As respostas JSON usam `Cache-Control: no-store`. O limite geral de corpo é 64 KiB; `/send/media` aceita 48 MiB de JSON e `/group/updateImage` 12 MiB. Limites de bytes não são limites de caracteres Unicode. O orçamento de envio do handler é 44 s; manter timeout do cliente compatível (o consumidor atual usa 45 s). ### Idempotência Aplicada ao init (15 min), a todos os `/send/*`, forward, react, delete, edit (POST/PUT) e mutações de grupo (24 h). As operações correspondentes estão marcadas no OpenAPI. Não é uma garantia geral para qualquer POST. Repetir a **mesma chave, método, caminho e os mesmos bytes do corpo**. Mudar espaços/ordem do JSON também muda o hash. Guardar a serialização do envio e usar chave nova por operação de negócio. Escopos: admin/init; por instância, send/action/group. Não reutilizar uma chave entre rotas do mesmo escopo. Respostas transitórias 409, 429 e 5xx (exceto 504) liberam a chave. Resultados concluídos e 504 ficam guardados. `Idempotency-Replayed:true` informa replay. Um 504 repetido não autoriza novo envio com outra chave. ## Webhook para integrar ao Atendro Registro recomendado, substituindo a URL e o segredo no backend privado: ```json { "action": "add", "enabled": true, "url": "https://receptor.example/webhooks/atendrozap", "secret": "", "events": ["connection", "messages", "messages_update", "messages_edit"], "excludeMessages": ["wasSentByApi"], "addUrlEvents": false, "addUrlTypesMessages": false } ``` O serviço entrega `connection`, `messages`, `messages_update`, `messages_edit`, `groups`, `limits` e `undecryptable`. `qrcode`, `call`, `chats` e `history` são aceitos mas ignorados. QR é obtido por status/connect. O cadastro global fica em **Servidor → Configurações de webhook** no console, ou `GET/POST /v1/webhook` com `admintoken`. O Atendro terá um novo receptor específico deste projeto; não reutilizar o webhook do provedor anterior. O [contrato do receptor AtendroZAP](ATENDRO_WEBHOOK.md) descreve herança, exceções, autenticação e o exemplo de validação HMAC. O corpo entregue tem a projeção legada (`EventType`, `instanceName`, `chat`, `message` ou campos específicos do evento). O token de instância não é enviado. Os [contratos de cada evento](EVENTS.md) detalham reações e edições, que não devem virar novas mensagens para a IA. O receptor precisa: 1. Resolver o cadastro por servidor e `X-AtendroZAP-Instance-Id`, vinculados à empresa. Nomes/owner do payload não concedem autorização. 2. Ler o corpo bruto, antes de parsear JSON. Verificar `X-AtendroZAP-Signature: sha256=` com HMAC-SHA256 do segredo sobre `X-AtendroZAP-Timestamp + "." + corpo_bruto`; comparação em tempo constante. 3. Recusar timestamps fora da janela adotada (recomendação do contrato: 5 min). 4. Deduplicar por servidor/instância/`X-AtendroZAP-Event-Id`; processamento deve ser idempotente. `X-AtendroZAP-Event-Seq` orienta ordenação. 5. Preservar avanço de ACKs; não regredir Read para Sent. Diferenciar mensagens manuais do celular, ecos API e histórico importado. 6. Só responder 2xx após aceitação durável. Há entrega pelo menos uma vez, retry e dead-letter; `POST /v1/events/{id}/replay` pode repetir efeitos. A adaptação e a verificação de assinatura no consumidor permanecem gates de integração. Esta documentação não confirma alterações no projeto Atendro. ## O que foi validado e o que falta O catálogo relaciona cada operação aos testes de contrato existentes. Os testes HTTP usam `httptest`, store em memória e sessões falsas; não conectam WhatsApp. O teste de documentação verifica cobertura de rotas e os tipos de autenticação. Os testes de persistência precisam de PostgreSQL descartável via `ATENDROZAP_TEST_DATABASE_URL`; truncam tabelas e nunca devem usar o banco de lab. O relatório desta revisão fica em [API_VALIDATION.md](API_VALIDATION.md). | Família | Evidência atual | Limite da evidência | |---|---|---| | Instâncias | Contratos locais e homologações anteriores em DECISIONS/PILOT_GATES | Este trabalho não fez novo pareamento ou ciclo destrutivo real | | Mensagens/mídia/menus/webhook | Testes e registros de laboratório anteriores | Restam cenários de terceiros e validação ponta a ponta do consumidor | | Grupos e importação de histórico | Testes locais | Homologação real pendente | | Proxy e takeover | Testes locais e verificações parciais anteriores | Sessão real por proxy/takeover depende de autorização e pareamento | | Ligações | Contrato negativo 501 e capacidade false | Funcionalidade não implementada | | Cadastro no Atendro | Contrato de integração documentado | Adaptar produto e validar com credencial privada; não cadastrado nesta revisão | Antes de um piloto, cumprir os [gates](PILOT_GATES.md): allowlists, assinatura, vínculos, reconciliação, bloqueio de eventos tardios e autorização do cutover. ## Página pública ou llms.txt? Use os artefatos em conjunto: - **Guia renderizado como página** para quem integra e opera. - **OpenAPI** para contratos de requisição/resposta e ferramentas de API. - **llms.txt** como índice curto apontando para Markdown e OpenAPI. - **llms-full.txt** com guias, endpoints e modelos completos em texto. A referência tem páginas individuais para as 56 operações, agrupadas por recurso, com autenticação, campos, exemplos cURL/JavaScript e respostas por status. Cada guia, endpoint e modelo possui uma versão Markdown no endereço da página com o sufixo `.md`. O índice e o conteúdo completo usam a mesma fonte do site. O nome proposto é `llms.txt`, com **s**. Ele orienta a leitura da IA; não substitui o contrato nem executa testes. Referências: [proposta llms.txt](https://llmstxt.org/) e [especificação OpenAPI](https://spec.openapis.org/oas/v3.1.0.html). O console distribui a página pública em `https://wpp.atendro.cloud/docs`, o contrato em `https://wpp.atendro.cloud/openapi.json` e o índice em `https://wpp.atendro.cloud/llms.txt`, sem autenticação. O conteúdo completo está em `https://wpp.atendro.cloud/llms-full.txt`. Os documentos vinculados são embutidos no binário por uma lista explícita; a página funciona sem consultar os servidores de WhatsApp e não lê configurações privadas nem credenciais. Os antigos caminhos de documentação na API redirecionam para a central.