This is the abridged developer documentation for Zoppy Connect # Ligue a Zoppy a qualquer sistema da loja > Integrações que uma IA escreve, ensaia e liga pela conversa, na nuvem ou no computador da loja, sempre com uma pessoa no controle. O **Zoppy Connect** liga a Zoppy a qualquer outro sistema que a loja usa: um ERP, um PDV, uma plataforma na nuvem ou um sistema instalado no computador da loja, sem API na internet. A pessoa da loja descreve o que quer numa conversa; a IA pesquisa a documentação do sistema, escreve a integração, testa contra o sistema de verdade, mostra o que aconteceria e só então liga. Cada integração é um **conector**: um arquivo de código e um **manifesto** que diz no que ele pode mexer. Ninguém escreve código à mão. O conector roda no horário que o manifesto define (a cada 5 a 1.440 minutos), sempre isolado e passando pelo gateway do Connect, que só deixa sair o que o manifesto declara. ## Por onde começar [Seção intitulada “Por onde começar”](#por-onde-começar) [Para a loja](/store/getting-started/) [Sem código: liberar o acesso, construir uma integração conversando, ensaiar, publicar, confirmar escritas e acompanhar as execuções.](/store/getting-started/) [Abrir ](/store/getting-started/)[Implantação e suporte](/support/local-agent/) [Instalar e parear o agente local na máquina da loja, liberar o que ele alcança e diagnosticar o agente fora do ar.](/support/local-agent/) [Abrir ](/support/local-agent/)[Como funciona](/how-it-works/) [A arquitetura e o modelo de segurança: gateway, isolamento do código, segredos, agente local e separação entre lojas.](/how-it-works/) [Abrir ](/how-it-works/)[Referência técnica](/reference/how-connectors-work/) [Como um conector funciona por dentro: o SDK (ctx), o manifesto, a API de Parceiros da Zoppy, os erros e os limites.](/reference/how-connectors-work/) [Abrir ](/reference/how-connectors-work/)[API do Connect](/reference/connect-api/) [As rotas da API do Connect, geradas do OpenAPI, com parâmetros, respostas e exemplos.](/reference/connect-api/) [Abrir ](/reference/connect-api/)[CLI e MCP](/reference/cli/) [Escrever, ensaiar e publicar conectores do editor, ou consultar e operar o Connect por uma IA.](/reference/cli/) [Abrir ](/reference/cli/) ## O que dá para fazer [Seção intitulada “O que dá para fazer”](#o-que-dá-para-fazer) * **Sistema na nuvem → Zoppy**: trazer clientes, pedidos e produtos de um ERP ou plataforma com API. * **Zoppy → sistema na nuvem**: mandar pedidos ou clientes da Zoppy para outro sistema. * **Sistema local → Zoppy**: ler o banco de dados do ERP instalado na loja (SQL Server, Oracle, Firebird, MySQL ou Postgres) ou uma API interna, pelo **agente local**, sem abrir porta nenhuma. O sistema local é só leitura. ## Regras que valem sempre [Seção intitulada “Regras que valem sempre”](#regras-que-valem-sempre) 1. **Nada roda sem ensaio.** O ensaio lê de verdade, mas não grava nada: as escritas viram “a requisição que eu mandaria”. A exceção são as consultas por `POST`, `PUT` ou `PATCH` que o manifesto declara como leitura (`readRoutes`, aprovadas junto com o host): essas saem de verdade, e o resultado do ensaio lista cada uma para conferir. Publicar exige um ensaio completo limpo e o sim de uma pessoa. 2. **A primeira escrita em cada sistema espera confirmação.** Uma pessoa vê a requisição exata e confirma; depois disso, o conector escreve normalmente. 3. **Nunca apaga nada na Zoppy**, e o que vai para outro sistema não tem desfazer. 4. **Segredos nunca passam pelo chat.** Senhas e tokens são preenchidos num formulário à parte e o código nunca vê o valor. 5. **Registro recusado não para a integração.** Ele vai para a quarentena com o motivo; dá para corrigir e reprocessar só ele. ## Para uma IA [Seção intitulada “Para uma IA”](#para-uma-ia) A documentação inteira está em texto puro, para ler de uma vez: [`/llms.txt`](/llms.txt) (o índice), [`/llms-full.txt`](/llms-full.txt) (tudo) e [`/llms-small.txt`](/llms-small.txt) (versão enxuta). # Como funciona > A arquitetura do Zoppy Connect e o modelo de segurança — gateway, isolamento do código, segredos, agente local e separação entre lojas. O Zoppy Connect é um produto separado da Zoppy: tem a própria API, o próprio banco e a própria infraestrutura. Ele fala com a Zoppy **só pela API de Parceiros** (`partners.zoppy.com.br`), como faria com qualquer outro sistema, e nunca acessa o banco, as filas ou o cache da Zoppy. ```plaintext Loja (tela do Connect) Nuvem do Connect Outros sistemas ┌──────────────────┐ ┌────────────────────────────────┐ │ Copiloto e │────►│ API ─── IA de construção │ │ conversa de │ │ │ │ │ construção │ │ ▼ │ ┌──────────────────────┐ └──────────────────┘ │ Worker ── fatias ──► Runner │ │ partners.zoppy.com.br│ │ (sem rede) │ ├──────────────────────┤ │ │ │ │ ERP / plataforma │ │ ▼ │ ───► │ na nuvem (https) │ │ Gateway ────┼──────┤ │ │ ▲ │ └──────────────────────┘ └───────────────────────┼────────┘ túnel de saída (443) ┌───────────────────────┴────────┐ │ Agente local (rede da loja) │──► banco do ERP (só leitura) └────────────────────────────────┘──► API interna (GET/HEAD) ``` ## O conector [Seção intitulada “O conector”](#o-conector) Um conector é um arquivo TypeScript (`export default async function run(ctx)`) e um manifesto JSON. A IA de construção escreve os dois; não existe editor manual. Cada mudança vira uma **versão** nova, com diff e restauração. A versão é compilada num arquivo só, e a compilação recusa qualquer import que não seja do próprio Node ou de tipos. O manifesto declara o horário (`triggers`), os domínios que o código chama (`hosts`), os métodos e caminhos liberados em cada um (`routes`), os agentes locais e o que cada um alcança (`agents`), os nomes dos segredos (`secrets`) e se o conector escreve em outro sistema (`externalWrites`). Detalhes em [Manifesto](/reference/manifest/). ## Execução isolada [Seção intitulada “Execução isolada”](#execução-isolada) O código roda num processo novo, numa função isolada **sem acesso à rede**, sem as variáveis de ambiente da plataforma e com teto de memória e de tempo. Uma execução é dividida em **fatias** de até 10 minutos: quando a fatia está perto do fim, o código guarda o cursor (`ctx.state`) e pede a próxima. Uma fatia que estoura o tempo é encerrada à força. Os números estão em [Limites](/reference/limits/). ## O gateway [Seção intitulada “O gateway”](#o-gateway) Toda chamada do código sai pelo gateway, autenticada por um token assinado para aquela execução e fatia. O gateway: * só deixa passar os hosts do manifesto, por `https`, e, quando o host tem `routes`, só os métodos e caminhos delas; * bloqueia endereços internos, link-local e de metadados **depois** de resolver o DNS, para que um domínio não aponte para dentro da rede; * troca as referências `ctx.secret('NOME')` pelo valor do segredo na saída, de forma que o código nunca vê o valor; * separa leitura (`ctx.http.request`) de escrita (`ctx.http.write`): no ensaio as escritas são só registradas, e em produção a primeira escrita em cada host espera uma pessoa confirmar; * injeta o acesso da loja à Zoppy só em chamadas para `partners.zoppy.com.br`, recusa `DELETE` nele e troca esse host lógico pelo endereço da API de Parceiros do ambiente; * registra cada chamada como um passo da execução, com dados pessoais mascarados. ## Segredos [Seção intitulada “Segredos”](#segredos) Os segredos (tokens, senhas, usuário e senha do banco local) são preenchidos pela pessoa num formulário fora do chat, guardados cifrados (AES-256-GCM) e são **só de escrita**: a API nunca devolve o valor. Um segredo colado por engano no chat é mascarado antes de chegar ao modelo e vira um pedido de acesso. O acesso à Zoppy (`ZOPPY_TOKEN`) é da loja, gerado pelo Connect na primeira construção, e não pode ser declarado nem lido por um conector. ## Agente local [Seção intitulada “Agente local”](#agente-local) Para um sistema dentro da rede da loja, o time da Zoppy instala o **agente local** numa máquina sempre ligada. O agente abre uma conexão **de saída** (WebSocket na porta 443) até o gateway; a loja não abre porta nem precisa de VPN. Antes de qualquer acesso, o agente confere a **lista local**, que fica só naquela máquina e que a nuvem não consegue mudar. No banco, só `SELECT`, por um usuário só leitura e dentro de transação só leitura ou sempre desfeita; nas APIs internas, só `GET` e `HEAD`. Usuário e senha do banco ficam cifrados na nuvem e descem pelo túnel a cada consulta, sem nunca ir para o disco da máquina nem para logs. Veja [Agente local: instalação](/support/local-agent/). ## Separação entre lojas [Seção intitulada “Separação entre lojas”](#separação-entre-lojas) Toda consulta é filtrada pela loja de quem está logado: uma loja não enxerga conectores, versões, execuções, segredos, agentes nem quarentena de outra. O código de um conector nunca é compartilhado entre lojas. O que o Connect aprende sobre um sistema (notas curtas, sem código, link, segredo nem dado pessoal) só passa a ajudar outras lojas depois de um ensaio limpo do conector que o registrou. ## Quem acessa [Seção intitulada “Quem acessa”](#quem-acessa) O Connect abre com o mesmo login da Zoppy. Só usuários **MASTER** ou **ADMIN** de lojas com o Connect liberado pelo time da Zoppy e com plano pago entram; tokens de propósito restrito (integrações externas, MCP) são recusados. Desligar a liberação barra a entrada, mas não pausa os conectores já ligados. O time da plataforma tem uma **pausa de emergência** por conector, por loja ou para o Connect inteiro. # CLI do Connect > Baixar, editar, enviar, ensaiar e publicar conectores do próprio editor, com os tipos do SDK: instalação, login e o ciclo completo. Gerada da ajuda e das opções do pacote `@zoppy-crm/connect-cli` (Zoppy-crm/zoppy-connect\@f1ae3a72be71). A CLI é para quem tem TI própria e quer escrever e ajustar conectores no próprio editor, com versionamento, em vez de só pela [conversa de construção](/store/build-by-chat/). Ela segue as mesmas regras da tela: login com a conta da Zoppy, só MASTER ou ADMIN, e nada roda sem ensaio limpo e sem publicar com prévia e confirmação. ## Instalar [Seção intitulada “Instalar”](#instalar) O pacote fica no GitHub Packages da organização `zoppy-crm` e é privado: sua conta do GitHub precisa de acesso de leitura a ele. Precisa do Node 22 ou mais novo. 1. Crie no GitHub um token clássico (personal access token classic) com o escopo `read:packages`. O GitHub Packages não aceita tokens fine-grained. 2. Aponte o escopo `@zoppy-crm` para o registro e entre com o token como senha: ```sh npm config set @zoppy-crm:registry https://npm.pkg.github.com npm login --scope=@zoppy-crm --auth-type=legacy --registry=https://npm.pkg.github.com ``` Ou, direto no `~/.npmrc`: ```ini @zoppy-crm:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken= ``` 3. Instale e confira: ```sh npm install -g @zoppy-crm/connect-cli zoppy-connect --version ``` ## Entrar [Seção intitulada “Entrar”](#entrar) ```sh zoppy-connect login ``` O `login` abre a Zoppy no navegador. Você entra com a sua conta (com a verificação em duas etapas, se estiver ligada), confere na tela de consentimento a permissão que a CLI pede (`connect:build`: “Construir, ensaiar e editar o código dos conectores pela CLI do Connect”) e autoriza. O navegador volta para um endereço local (`127.0.0.1`) que a CLI abre só durante o login. Se o navegador não abrir, use `zoppy-connect login --no-browser` e abra o endereço mostrado num navegador da mesma máquina: o retorno vai para `127.0.0.1`. Só entram usuários MASTER ou ADMIN de uma loja com o Connect liberado e com o termo do Connect aceito. Ao terminar, a CLI mostra a loja e o papel com que entrou. O acesso fica num arquivo legível só pelo seu usuário, e se renova sozinho: | Sistema | Arquivo | | ------- | -------------------------------------------------------------------------------- | | Linux | `~/.config/zoppy-connect/credentials.json` (ou `$XDG_CONFIG_HOME/zoppy-connect`) | | macOS | `~/Library/Application Support/zoppy-connect/credentials.json` | | Windows | `%APPDATA%\zoppy-connect\credentials.json` | `ZOPPY_CONNECT_CONFIG_DIR` troca a pasta. `zoppy-connect logout` revoga o acesso na Zoppy e apaga o arquivo. Para usar a staging, entre com `--api-url https://api-connect-staging.zoppy.com.br`. ## O ciclo de um conector [Seção intitulada “O ciclo de um conector”](#o-ciclo-de-um-conector) O conector é criado na tela do Connect (ou pela conversa); a CLI trabalha sobre um conector que já existe, pelo id dele. ### 1. Baixar: `init` [Seção intitulada “1. Baixar: init”](#1-baixar-init) ```sh zoppy-connect init cd npm install ``` A pasta recebe `connector.ts`, `manifest.json` (com `$schema`, para o editor validar), `.zoppy-connect.json` (o conector e a versão de onde a pasta partiu), `package.json`, `tsconfig.json` e `.npmrc`. O `init` também lista os segredos que ainda estão sem valor. ### 2. Editar com os tipos do SDK [Seção intitulada “2. Editar com os tipos do SDK”](#2-editar-com-os-tipos-do-sdk) O `npm install` da pasta instala o próprio `@zoppy-crm/connect-cli`, que traz os tipos do [SDK](/reference/sdk/) e o schema do [manifesto](/reference/manifest/). O editor completa o `ctx` e aponta erros no manifesto: ```ts import type { ConnectorContext, ConnectorResult } from '@zoppy-crm/connect-cli/sdk'; export default async function run(ctx: ConnectorContext): Promise { return { done: true }; } ``` O `import type` some na compilação: o conector continua sem dependências. ### 3. Enviar: `push` [Seção intitulada “3. Enviar: push”](#3-enviar-push) ```sh zoppy-connect push -m "o que mudou" ``` Cada `push` cria uma versão nova, com origem `cli`, por cima da versão de onde a pasta partiu. Se outra versão entrou depois (pela tela, pela conversa ou por outra pasta), o `push` é **recusado e nada é gravado**: baixe a mais nova em outra pasta (`init --dir `), junte as suas mudanças e envie de novo. Depois do envio, a CLI avisa os hosts novos esperando aprovação e os segredos sem valor. ### 4. Ensaiar: `rehearse` [Seção intitulada “4. Ensaiar: rehearse”](#4-ensaiar-rehearse) ```sh zoppy-connect rehearse --sample zoppy-connect rehearse --full ``` O [ensaio](/store/dry-run/) roda a versão mais nova de verdade, lendo dos sistemas, sem escrever em nenhum. A CLI espera o fim e mostra: * os contadores por objeto e o erro com a linha do código, se houver; * as **requisições planejadas**: as escritas (`ctx.http.write`) que o ensaio registrou e não mandou, com método, endereço e corpo; * as chamadas feitas como leitura com método de escrita (`writesAsReads`): `POST`, `PUT`, `PATCH` ou `DELETE` por `ctx.http.request`, para você conferir que não são escritas; * os **problemas que impedem publicar**, como `connect.write_without_lookup` (um `POST` que cria registros sem consultar antes se eles já existem; veja [Como um conector funciona](/reference/how-connectors-work/)). A amostra roda poucas fatias. Publicar exige um ensaio **completo** (`--full`) limpo da versão. Hosts novos e segredos sem valor fazem o ensaio ser recusado: aprove e preencha na tela e ensaie de novo. ### 5. Publicar: `publish` [Seção intitulada “5. Publicar: publish”](#5-publicar-publish) ```sh zoppy-connect publish ``` A CLI mostra a prévia do que muda e pede o seu sim (`s`). Sem a confirmação, nada é publicado. `--yes` confirma sem perguntar, depois de mostrar a prévia. Um conector já ligado passa a rodar a versão nova no cron; um que não está ligado continua parado até ser ligado na tela ou pelo [MCP](/reference/mcp/). ### 6. Acompanhar: `runs` [Seção intitulada “6. Acompanhar: runs”](#6-acompanhar-runs) ```sh zoppy-connect runs zoppy-connect runs ``` Lista as execuções (gatilho, versão, situação) ou mostra uma inteira: contadores, erro, requisições planejadas, passos, quarentena e escritas enviadas. Veja [Execuções e quarentena](/store/runs-and-quarantine/). ## O que nunca passa pela CLI [Seção intitulada “O que nunca passa pela CLI”](#o-que-nunca-passa-pela-cli) * **Segredos.** Os valores são preenchidos só no formulário da tela do conector. A CLI só diz quais faltam. * **Criar conectores.** O conector nasce na tela ou na conversa; a CLI trabalha sobre o id dele. * **Aprovar hosts.** Um host novo no manifesto espera aprovação na tela antes do ensaio. * **Ligar, pausar, a primeira escrita e a quarentena.** Ficam na tela, ou no [MCP](/reference/mcp/), sempre com prévia e confirmação. ## Comandos [Seção intitulada “Comandos”](#comandos) ```text zoppy-connect: conectores do Zoppy Connect no seu editor Uso: zoppy-connect [opções] login [--api-url ] [--issuer ] [--no-browser] entra com a sua conta da Zoppy (navegador, OAuth com PKCE) logout revoga o acesso e apaga desta máquina init [--dir ] [--force] baixa o código e o manifesto da versão mais nova push [-m, --message ] [--dir ] envia a pasta como versão nova (recusa se outra entrou antes) rehearse [--sample | --full] [--timeout ] ensaia a versão mais nova e mostra as requisições planejadas publish [-y, --yes] mostra a prévia e publica depois da confirmação runs [] [--limit ] lista as execuções ou mostra uma --connector usa outro conector no lugar do da pasta; --dir aponta a pasta. Segredos nunca passam pela CLI: preencha na tela do conector no Zoppy Connect. ``` ### `login` [Seção intitulada “login”](#login) Abre a Zoppy no navegador para você entrar e autorizar a CLI (OAuth com PKCE, retorno em `127.0.0.1`). Confere a loja e o papel e guarda o acesso nesta máquina. ```sh zoppy-connect login [--api-url ] [--issuer ] [--no-browser] ``` | Opção | Tipo | Descrição | | -------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------- | | `--api-url` | texto | Endereço da API do Connect. Padrão: produção, ou `ZOPPY_CONNECT_API_URL`. Precisa ser https (http só na própria máquina). | | `--issuer` | texto | Endereço do login, quando é diferente da API. Padrão: o da API, ou `ZOPPY_CONNECT_ISSUER_URL`. | | `--no-browser` | bool | Não abre o navegador: mostra o endereço para você abrir num navegador desta mesma máquina, que recebe o retorno em `127.0.0.1`. | ### `logout` [Seção intitulada “logout”](#logout) Revoga o acesso na Zoppy e apaga o arquivo de acesso desta máquina. ```sh zoppy-connect logout ``` ### `init` [Seção intitulada “init”](#init) Baixa o código e o manifesto da versão mais nova do conector numa pasta nova, com os arquivos para o editor. Sem versão ainda, começa de um modelo. ```sh zoppy-connect init [--dir ] [--force] ``` | Opção | Tipo | Descrição | | --------- | ----- | -------------------------------------------------------------------------- | | `--dir` | texto | Pasta onde o conector é baixado. Padrão: uma pasta com o nome do conector. | | `--force` | bool | Sobrescreve uma pasta que já tem um conector. | ### `push` [Seção intitulada “push”](#push) Envia o `connector.ts` e o `manifest.json` da pasta como uma versão nova (origem `cli`). Recusa sem gravar nada se outra versão entrou depois da que a pasta baixou. ```sh zoppy-connect push [-m, --message ] [--dir ] ``` | Opção | Tipo | Descrição | | --------------- | ----- | ----------------------------------------------------------------- | | `--dir` | texto | Pasta do conector. Padrão: a pasta atual. | | `-m, --message` | texto | Resumo do que mudou, que aparece no histórico de versões da tela. | ### `rehearse` [Seção intitulada “rehearse”](#rehearse) Ensaia a versão mais nova e espera o fim: contadores, erro com a linha do código, requisições planejadas, chamadas de leitura com método de escrita e problemas que impedem publicar. ```sh zoppy-connect rehearse [--sample | --full] [--timeout ] ``` | Opção | Tipo | Descrição | | ------------- | ----- | ---------------------------------------------------------------------------------------------------------------- | | `--dir` | texto | Pasta do conector. Padrão: a pasta atual. | | `--connector` | texto | Id de outro conector, no lugar do que a pasta aponta. | | `--sample` | bool | Ensaio amostra, poucas fatias (o padrão). | | `--full` | bool | Ensaio completo: lê a origem inteira. É o que libera publicar. | | `--timeout` | texto | Quantos minutos esperar o fim do ensaio (padrão 30). Depois disso o ensaio continua e você acompanha com `runs`. | ### `publish` [Seção intitulada “publish”](#publish) Mostra a prévia do que muda e publica depois do seu sim. Publicar exige um ensaio completo limpo da versão. ```sh zoppy-connect publish [-y, --yes] ``` | Opção | Tipo | Descrição | | ------------- | ----- | ------------------------------------------------------------------ | | `--dir` | texto | Pasta do conector. Padrão: a pasta atual. | | `--connector` | texto | Id de outro conector, no lugar do que a pasta aponta. | | `-y, --yes` | bool | Confirma sem perguntar, depois de mostrar a prévia (para scripts). | ### `runs` [Seção intitulada “runs”](#runs) Lista as execuções do conector, das mais novas para as mais antigas, ou mostra uma inteira quando recebe o id. ```sh zoppy-connect runs [] [--limit ] ``` | Opção | Tipo | Descrição | | ------------- | ----- | ----------------------------------------------------- | | `--dir` | texto | Pasta do conector. Padrão: a pasta atual. | | `--connector` | texto | Id de outro conector, no lugar do que a pasta aponta. | | `--limit` | texto | Quantas execuções listar (padrão 20). | ## Códigos de saída [Seção intitulada “Códigos de saída”](#códigos-de-saída) | Código | Quando | | ------ | -------------------------------------------------------------------------- | | 0 | Deu certo. | | 1 | Falha: rede, ensaio que não passou, publicação recusada ou não confirmada. | | 2 | Uso errado: opção, argumento ou manifesto inválido. | | 3 | Sem login, ou acesso inválido, expirado ou revogado: rode `login`. | | 4 | O papel, a loja ou o acesso não liberam a operação. | | 5 | Conector ou execução não encontrados. | | 6 | Conflito: outra versão entrou antes, ou já há uma execução rodando. | # API do Connect > A API HTTP que a tela do Connect usa — endereço, autenticação, erros e o fluxo de prévia e confirmação. Tudo o que a tela do Connect faz passa por esta API: conectores e versões, segredos, hosts, ensaios, publicar, ligar e pausar, execuções, quarentena, escritas guardadas, agentes locais, a conversa de construção e o Copiloto. A referência de cada rota, gerada do OpenAPI da API, está em [API do Connect (rotas)](/reference/api/). Para integrar de fora, use a CLI ou o MCP Esta API serve a tela do Connect, a [CLI](/reference/cli/) e o [MCP](/reference/mcp/). Não há chave de API para terceiros, e as rotas podem mudar sem aviso. Para trabalhar com conectores de fora da tela, use a [CLI](/reference/cli/) (escrever, ensaiar e publicar) ou o [MCP](/reference/mcp/) (consultar e operar por uma IA). ## Endereço e autenticação [Seção intitulada “Endereço e autenticação”](#endereço-e-autenticação) ```plaintext Produção: https://api-connect.zoppy.com.br Staging: https://api-connect-staging.zoppy.com.br Header: Authorization: Bearer ``` O token é o da sessão da Zoppy de quem está logado: a API confere a assinatura, a validade, a loja, o papel (só MASTER ou ADMIN) e se o Connect está liberado para a loja. `GET /me` diz quem está usando e se o acesso está liberado. ## Erros [Seção intitulada “Erros”](#erros) Todo erro vem como JSON (`application/json`) no formato problem+json: `{ type, title, status, detail, requestId, meta? }`. O `type` é o código estável (ex.: `connect.not_rehearsed`); a mensagem é em português. Veja o [catálogo de erros](/reference/errors/). ## Prévia e confirmação [Seção intitulada “Prévia e confirmação”](#prévia-e-confirmação) Toda ação que muda o que roda ou o que sai para outro sistema tem dois passos: uma rota `.../preview`, que mostra o que muda e devolve um **token de confirmação**, e a rota da ação, que recebe esse token. | Ação | Prévia | Confirmação | | ------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------- | | Publicar | `POST /connectors/{id}/publish/preview` | `POST /connectors/{id}/publish` | | Ligar | `POST /connectors/{id}/enable/preview` | `POST /connectors/{id}/enable` | | Pausar | `POST /connectors/{id}/pause/preview` | `POST /connectors/{id}/pause` | | Aprovar hosts | `POST /connectors/{id}/hosts/preview` | `POST /connectors/{id}/hosts/approve` | | Confirmar a 1ª escrita | `POST /connectors/{id}/external-writes/{heldWriteId}/preview` | `POST /connectors/{id}/external-writes/{heldWriteId}/confirm` | | Reprocessar a quarentena | `POST /connectors/{id}/quarantine/reprocess/preview` | `POST /connectors/{id}/quarantine/reprocess` | | Religar após revogação | `POST /connect/zoppy-token` | `POST /connect/zoppy-token/reenable` | O token vale 10 minutos, uma vez só, preso à pessoa, à loja, ao conector, à mudança e ao estado. Se algo mudar entre a prévia e a confirmação, a confirmação é recusada (`connect.confirmation_stale` ou `connect.confirmation_refused`) e é preciso gerar a prévia de novo. ## Versões e conflito [Seção intitulada “Versões e conflito”](#versões-e-conflito) Escrever uma versão (pela IA) ou restaurar uma antiga informa a versão em que a mudança se baseou. Se outra versão entrou depois dela, a API recusa com `409 connect.version_conflict`. ## Manifesto como JSON Schema [Seção intitulada “Manifesto como JSON Schema”](#manifesto-como-json-schema) `GET /connect/manifest.schema.json` devolve o schema do manifesto (draft-07), o mesmo que gera a página [Manifesto](/reference/manifest/). O retrato usado por este site também está publicado aqui: [`/openapi.json`](/openapi.json) e [`/manifest.schema.json`](/manifest.schema.json). # Catálogo de erros > Todos os códigos de erro do Zoppy Connect, com o status HTTP, a mensagem que a pessoa vê e o que fazer. Gerada dos catálogos de mensagens da API (Zoppy-crm/zoppy-connect\@f1ae3a72be71). Todo erro da API vem como JSON (`application/json`) no formato problem+json: ```json { "type": "connect.route_not_allowed", "title": "Essa rota não está liberada no manifesto do conector.", "status": 403, "detail": "Essa rota não está liberada no manifesto do conector.", "requestId": "01JC5R6Z8T2Q9M3V7X1B4N0K2P", "meta": { "host": "partners.zoppy.com.br", "method": "DELETE" } } ``` O `type` é o código estável, em inglês; a mensagem é em português e é a que a tela mostra. Os `{marcadores}` são preenchidos com o `meta`. Passe o `requestId` ao suporte. No código do conector, uma recusa do gateway chega como `ConnectGatewayError` com o mesmo `code` ([SDK](/reference/sdk/)). Status `—` quando o código não sai como resposta HTTP própria (aparece no passo da execução ou varia). ## Acesso ao Connect [Seção intitulada “Acesso ao Connect”](#acesso-ao-connect) | Código | Status | Mensagem | O que fazer | | ---------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `connect.access_token_not_allowed` | 403 | Esta operação só pode ser feita na tela do Zoppy Connect, não pela CLI. | — | | `connect.feature_disabled` | 403 | O Zoppy Connect não está liberado para esta loja. | Peça ao time da Zoppy para liberar o Connect na loja (feature ligada no app admin). | | `connect.feature_unverifiable` | 403 | Não conseguimos confirmar que o Zoppy Connect continua liberado para esta loja. Abra a tela do Zoppy Connect uma vez ou reconecte o acesso à Zoppy e tente de novo. | — | | `connect.features_unavailable` | 503 | Não conseguimos confirmar o acesso ao Zoppy Connect agora. Tente novamente em instantes. | — | | `connect.forbidden_role` | 403 | Só administradores da loja podem usar o Zoppy Connect. | Entre com um usuário MASTER ou ADMIN da loja. | | `connect.insufficient_scope` | 403 | Este acesso não libera esta operação no Zoppy Connect. Entre de novo e autorize o acesso pedido. | — | | `connect.missing_company` | 403 | Escolha uma loja na Zoppy antes de abrir o Zoppy Connect. | — | | `connect.restricted_token` | 403 | Este acesso não abre o Zoppy Connect. Entre na Zoppy com seu usuário. | — | | `connect.terms_master_only` | 403 | Só um usuário MASTER da loja pode aceitar os termos do Zoppy Connect. | — | | `connect.terms_not_accepted` | 403 | Um MASTER da loja precisa aceitar os termos do Zoppy Connect antes do primeiro uso. | — | | `connect.unauthorized` | 401 | Sua sessão expirou. Entre de novo na Zoppy. | — | ## Conectores, versões, segredos e acesso à Zoppy [Seção intitulada “Conectores, versões, segredos e acesso à Zoppy”](#conectores-versões-segredos-e-acesso-à-zoppy) | Código | Status | Mensagem | O que fazer | | -------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `connect.agent_not_available` | 422 | Estes agentes locais não existem nesta loja ou foram revogados: {list} | O manifesto aponta para um agente revogado ou de outra loja. Pareie um agente e peça à IA para atualizar o `agentId`. | | `connect.confirmation_refused` | 409 | A confirmação expirou ou não vale mais para este conector. Gere a prévia de novo. | — | | `connect.confirmation_stale` | 409 | O conector mudou depois da prévia. Gere a prévia de novo e confirme. | Algo mudou depois da prévia: abra a prévia de novo e confirme. | | `connect.connector_not_found` | 404 | Não encontramos esse conector. | — | | `connect.connector_transition_refused` | 409 | Não dá para fazer isso com o conector no estado em que ele está agora. | — | | `connect.host_not_declared` | 422 | Estes hosts não estão no manifesto do conector: {list} | — | | `connect.invalid_code` | 422 | O código do conector não compila: {detail} | — | | `connect.invalid_manifest` | 422 | O manifesto do conector tem problemas: {detail} | — | | `connect.not_rehearsed` | 409 | Ensaie esta versão lendo a origem inteira antes de publicar. | Rode um ensaio completo limpo da versão em edição e publique de novo. | | `connect.secret_missing` | 409 | Falta preencher um segredo do conector. | — | | `connect.secret_not_declared` | 422 | O manifesto do conector não pede esse segredo. | — | | `connect.system_logo_not_found` | 404 | Esse sistema ainda não tem logo. | — | | `connect.system_logo_refused` | 422 | O logo precisa ser um PNG com fundo transparente, de 128 pixels ou mais de lado e até 512 KB. | — | | `connect.system_not_found` | 404 | Não encontramos esse sistema no catálogo. | — | | `connect.version_conflict` | 409 | O conector mudou enquanto você editava. Abra a versão mais nova e tente de novo. | Outra versão foi guardada enquanto você editava: abra a mais nova e repita a mudança em cima dela. | | `connect.version_not_found` | 404 | Não encontramos essa versão do conector. | — | | `connect.write_without_lookup` | 409 | O último ensaio desta versão criou registros sem antes consultar se eles já existiam ({list}). Toda escrita precisa ser idempotente: corrija o conector para buscar pela chave natural antes de criar e ensaie de novo. | Peça à IA para buscar o registro pela chave natural antes de criar (ou atualizar pelo id) e ensaie de novo. | | `connect.zoppy_free_tier` | 403 | O Zoppy Connect precisa de um plano pago da Zoppy para acessar os dados da loja. | A loja precisa de um plano pago da Zoppy. | | `connect.zoppy_token_inactive` | 409 | O acesso do Zoppy Connect à Zoppy foi desativado. Gere um novo acesso para religar os conectores. | Um administrador abre o Connect: ele gera um acesso novo e oferece religar os conectores. | | `connect.zoppy_token_missing` | 409 | O acesso à Zoppy ainda não foi gerado para esta loja. | — | | `connect.zoppy_token_outside_zoppy` | 403 | O acesso à Zoppy só pode ser usado em chamadas para a Zoppy. | — | | `connect.zoppy_token_unavailable` | 503 | Não conseguimos gerar o acesso à Zoppy agora. Tente novamente em instantes. | — | ## Execuções, ensaios e quarentena [Seção intitulada “Execuções, ensaios e quarentena”](#execuções-ensaios-e-quarentena) | Código | Status | Mensagem | O que fazer | | -------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `connect.access_incomplete` | 409 | Falta preencher o acesso do conector antes de ensaiar: {list} | Preencha na tela os segredos listados (o formulário de acesso fica fora do chat) e ensaie de novo. | | `connect.connector_not_enabled` | 409 | Ligue o conector antes de rodar ou reprocessar. | — | | `connect.connector_without_version` | 409 | Este conector ainda não tem uma versão para rodar. | — | | `connect.hosts_awaiting_approval` | 409 | Aprove estes hosts antes de ensaiar: {list} | Uma pessoa da loja aprova os hosts novos na tela do conector (prévia e confirmação) antes do ensaio. | | `connect.quarantine_empty` | 422 | Não há registros abertos na quarentena para reprocessar. | — | | `connect.quarantine_item_not_found` | 404 | Este registro não está mais na quarentena. | — | | `connect.quarantine_record_not_object` | 422 | Só dá para corrigir campos de um registro que tem campos. | — | | `connect.run_in_progress` | 409 | Este conector já tem uma execução em andamento. Espere ela terminar. | Espere a execução em andamento terminar. | | `connect.run_not_found` | 404 | Não encontramos esta execução. | — | | `connect.writes_awaiting_confirmation` | 409 | Este conector tem uma escrita guardada esperando confirmação. Confirme ou descarte a escrita antes de rodar de novo. | — | ## Gateway (chamadas do conector) [Seção intitulada “Gateway (chamadas do conector)”](#gateway-chamadas-do-conector) | Código | Status | Mensagem | O que fazer | | ------------------------------------- | ------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `connect.blocked_address` | 403 | Esse endereço aponta para uma rede interna e foi bloqueado. | O host resolveu para um IP interno. Um sistema dentro da rede da loja só é alcançado pelo agente local. | | `connect.emergency_pause_active` | 409 | Já existe uma pausa de emergência ativa nesse escopo. | — | | `connect.emergency_pause_not_found` | 404 | Essa pausa de emergência não existe. | — | | `connect.external_writes_not_allowed` | 403 | Esse conector não pode escrever no outro sistema. | O conector tentou escrever sem `externalWrites: true` no manifesto. Peça à IA para revisar. | | `connect.gateway_not_wired` | 503 | Erro inesperado. Tente novamente em instantes. | — | | `connect.host_not_allowed` | 403 | Esse endereço não está no manifesto do conector. | O código chamou um host fora de `hosts`. Peça à IA para corrigir o manifesto; o host novo precisa de aprovação. | | `connect.host_not_approved` | 403 | Esse endereço ainda não foi aprovado por uma pessoa da loja. | Aprove o host na tela do conector. | | `connect.invalid_run_token` | 401 | Execução sem credencial válida. | — | | `connect.invalid_target` | 422 | Esse endereço não pode ser chamado pelo conector. | — | | `connect.operator_unauthorized` | 401 | Acesso restrito ao time da plataforma. | — | | `connect.paused` | 423 | O Zoppy Connect está pausado para este conector. | O conector (ou a loja) está pausado. Veja o motivo na tela do conector. | | `connect.response_too_large` | 502 | A resposta do outro sistema passou do limite da execução. | Peça páginas menores ao outro sistema (a IA ajusta o código). | | `connect.route_not_allowed` | 403 | Essa rota não está liberada no manifesto do conector. | Método ou caminho fora de `routes` (ou um `DELETE` na Zoppy, sempre recusado). Peça à IA para ajustar o código ou o manifesto. | | `connect.run_not_running` | 409 | Essa execução já terminou. | — | | `connect.runner_failed` | 502 | A execução do conector falhou antes de terminar. Tente novamente em instantes. | — | | `connect.secret_reserved` | 403 | Esse acesso é reservado e não pode ser usado pelo código do conector. | — | | `connect.upstream_failed` | 502 | O outro sistema não respondeu. | O outro sistema não respondeu a tempo. Confira se ele está no ar; a próxima execução retoma do cursor. | ## Agente local [Seção intitulada “Agente local”](#agente-local) | Código | Status | Mensagem | O que fazer | | ------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `connect.agent_busy` | — | O agente local está ocupado com outras chamadas. Tente de novo em instantes. | — | | `connect.agent_connection_lost` | — | A conexão com o agente local caiu com a chamada em andamento: ela pode ter chegado ao sistema ({detail}). Confira lá antes de repetir. | — | | `connect.agent_contract_unsupported` | 426 | Essa versão do agente não fala com a Zoppy. Atualize o agente para a versão mais nova. | Atualize o agente: `zoppy-agent update`. | | `connect.agent_cut_unauthorized` | 401 | Pedido de corte do agente sem autorização válida. | — | | `connect.agent_error` | — | O agente local falhou: {detail}. | — | | `connect.agent_invalid_target` | 400 | Endereço inválido para o agente local: {reason}. | — | | `connect.agent_method_not_allowed` | 405 | Pelo agente local só GET e HEAD são permitidos (veio {method}). | — | | `connect.agent_not_allowed` | — | O agente local recusou: {detail}. Libere o endereço na lista do agente, na máquina onde ele roda. | Na máquina do agente, libere o banco ou a API na lista local (`zoppy-agent allow add`) e confira com `zoppy-agent allow check`. | | `connect.agent_not_found` | 404 | Esse agente local não existe na sua loja. | — | | `connect.agent_not_in_manifest` | 400 | O endereço {target} não está em nenhum agente do manifesto. | — | | `connect.agent_offline` | 503 | O agente local não está conectado: a máquina está desligada, sem internet, ou o serviço do agente parou. | Siga o [diagnóstico de agente fora do ar](/support/local-agent/#diagn%C3%B3stico-agente-fora-do-ar) da página do agente local. | | `connect.agent_pairing_bad_request` | 400 | O pedido de pareamento do agente veio inválido: {reason}. | — | | `connect.agent_pairing_code_invalid` | 400 | Código de pareamento inválido, já usado ou vencido. Gere outro na tela do agente local. | Gere um código novo na tela (vale 30 minutos e uma vez só). | | `connect.agent_pairing_rate_limited` | 429 | Tentativas demais de parear. Espere {retryAfterSeconds} segundos e tente de novo. | — | | `connect.agent_protocol_error` | — | O agente local respondeu fora do contrato: {detail}. | — | | `connect.agent_response_too_large` | 502 | A resposta do agente local passou do limite de {maxBytes} bytes. | — | | `connect.agent_revoked` | 403 | O agente local foi revogado. Instale e pareie um agente novo e aponte o conector para ele. | Instale e pareie um agente novo e aponte o conector para ele. | | `connect.agent_secrets_unavailable` | 503 | Não conseguimos ler os segredos do conector para falar com o banco. Tente de novo em instantes. | — | | `connect.agent_sql_auth_failed` | — | O banco recusou o usuário ou a senha: {detail}. | Confira o usuário e a senha do banco nos segredos do conector (o usuário só leitura criado pelo DBA). | | `connect.agent_sql_failed` | — | O banco devolveu erro na consulta: {detail}. | — | | `connect.agent_sql_not_read_only` | 400 | A consulta não é só leitura: {detail}. | A consulta precisa ser um único `SELECT` (ou `WITH ... SELECT`). Peça à IA para corrigir. | | `connect.agent_target_unreachable` | — | O agente não conseguiu chegar ao sistema na rede da loja: {detail}. | A máquina do agente não chega ao banco ou à API: confira host, porta, firewall interno e certificado. | | `connect.agent_timeout` | — | O agente local não respondeu a tempo: {detail}. | — | | `connect.agent_unsupported` | — | Essa versão do agente local não sabe fazer isso: {detail}. Atualize o agente. | Atualize o agente: `zoppy-agent update`. | ## Escritas guardadas [Seção intitulada “Escritas guardadas”](#escritas-guardadas) | Código | Status | Mensagem | O que fazer | | ----------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `connect.held_write_confirmation_refused` | 409 | A confirmação expirou ou a escrita mudou desde a prévia. Abra a prévia de novo. | — | | `connect.held_write_connector_paused` | 409 | O conector está pausado, então a escrita não saiu. Ela ficou confirmada e sai sozinha quando o conector for ligado de novo. | — | | `connect.held_write_decided` | 409 | Essa escrita já foi decidida: ela já saiu, está saindo ou foi trocada por uma mais nova. | — | | `connect.held_write_not_found` | 404 | Não encontramos essa escrita guardada neste conector. | — | | `connect.held_write_outdated` | 409 | A versão publicada não escreve mais nesse sistema. A próxima execução guarda uma nova escrita para confirmar. | A próxima execução guarda uma nova primeira escrita para confirmar. | | `connect.held_write_relay_failed` | 502 | A escrita não saiu agora e continua confirmada. Ela sai sozinha em instantes, ou confirme de novo. | — | | `connect.held_write_send_uncertain` | 502 | A escrita falhou no meio do caminho e pode ter chegado ao outro sistema. Ela não será repetida: confira no outro sistema. | A escrita pode ter chegado. Confira no outro sistema antes de qualquer nova tentativa; ela não é repetida. | ## IA de construção [Seção intitulada “IA de construção”](#ia-de-construção) | Código | Status | Mensagem | O que fazer | | -------------------------------- | ------ | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `connect.ai_month_cap_reached` | 429 | A IA do Connect chegou ao limite de uso da loja neste mês. Ela volta quando o limite renovar. | O teto mensal de IA da loja foi atingido. Copiloto e construção voltam quando o mês virar; conectores ligados continuam rodando. | | `connect.build_turn_in_progress` | 409 | A IA ainda está respondendo a mensagem anterior. Espere ela terminar. | Espere a IA terminar a resposta anterior. | | `connect.build_unavailable` | 503 | A IA de construção ainda não está disponível neste ambiente. | — | | `connect.fix_run_not_found` | 404 | Não encontramos esta execução neste conector. | — | | `connect.run_still_running` | 409 | Esta execução ainda está rodando. Peça a correção quando ela terminar. | Peça a correção quando a execução terminar. | ## Copiloto [Seção intitulada “Copiloto”](#copiloto) | Código | Status | Mensagem | O que fazer | | --------------------------- | ------ | ----------------------------------------------------------------- | ----------- | | `connect.handoff_not_found` | 404 | Esta proposta não está mais disponível. Peça de novo ao Copiloto. | — | ## Documentação [Seção intitulada “Documentação”](#documentação) | Código | Status | Mensagem | O que fazer | | ----------------------- | ------ | -------------------------------------------------- | ----------- | | `connect.doc_not_found` | 404 | Essa página da documentação do Connect não existe. | — | ## MCP [Seção intitulada “MCP”](#mcp) | Código | Status | Mensagem | O que fazer | | ---------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `connect.mcp_confirmation_invalid` | 409 | Essa confirmação não vale mais: ela expirou, já foi usada ou é de outra pessoa. Peça a prévia de novo e confirme com o token novo. | Chame preview_operation de novo e confirme com o token novo, dentro do prazo e uma vez só. | | `connect.mcp_run_not_found` | 404 | Não encontramos essa execução neste conector. | — | | `connect.mcp_run_now_not_enabled` | 409 | Ligue o conector antes de mandar rodar agora. | — | | `connect.mcp_scope_missing` | 403 | Este aplicativo não tem a permissão {scope}. A pessoa precisa conectar de novo marcando essa permissão. | Desconecte o aplicativo e conecte de novo marcando a permissão que falta na tela de consentimento. | ## Login de aplicativos (CLI e MCP) [Seção intitulada “Login de aplicativos (CLI e MCP)”](#login-de-aplicativos-cli-e-mcp) | Código | Status | Mensagem | O que fazer | | ---------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `connect.oauth_insufficient_scope` | 403 | Este aplicativo não tem a permissão necessária ({required}). Conecte de novo marcando essa permissão. | Conecte de novo marcando a permissão pedida na tela de consentimento. | | `connect.oauth_invalid_token` | 401 | O acesso deste aplicativo expirou ou foi revogado. Conecte de novo. | Na CLI, rode zoppy-connect login; no cliente MCP, conecte o servidor de novo. | | `connect.oauth_no_scope` | 422 | Escolha ao menos uma permissão para conectar o aplicativo. | — | | `connect.oauth_request_not_found` | 404 | Esse pedido de conexão não existe mais ou já foi decidido. Peça a conexão de novo no aplicativo. | — | ## Gerais [Seção intitulada “Gerais”](#gerais) | Código | Status | Mensagem | O que fazer | | ------------------------------- | ------ | --------------------------------------------------------------------------------------------------------- | ----------- | | `core.missing_context` | 500 | Erro inesperado. Tente novamente em instantes. | — | | `core.missing_tenant_context` | 500 | Erro inesperado. Tente novamente em instantes. | — | | `core.search_timeout` | 503 | A busca demorou demais. Use palavras mais específicas e tente de novo. | — | | `core.too_many_attempts` | 429 | Muitas tentativas. Aguarde alguns minutos e tente novamente. | — | | `internal_error` | — | Erro inesperado. Se persistir, envie o código abaixo para o suporte. | — | | `llm.budget_exceeded` | 429 | O assistente atingiu o limite de uso deste mês. Ele volta a responder no mês que vem. | — | | `llm.invalid_request` | 422 | O assistente não está configurado corretamente. Avise o suporte com o código abaixo. | — | | `llm.task_budget_exceeded` | 429 | Esta leitura ficou grande demais para o limite da tarefa. Avise o suporte com o código abaixo. | — | | `llm.unavailable` | 503 | O assistente está fora do ar agora. Tente novamente em instantes. | — | | `llm.unknown_task` | 500 | Erro inesperado. Tente novamente em instantes. | — | | `mail.delivery_failed` | — | Não conseguimos enviar o e-mail agora. Tente novamente em instantes. | — | | `mail.unknown_template` | — | Erro inesperado. Tente novamente em instantes. | — | | `prompt.task_not_registered` | — | Erro inesperado. Tente novamente em instantes. | — | | `storage.artifact_expired` | 410 | Este arquivo passou do prazo de retenção e não está mais disponível. | — | | `storage.file_not_found` | 404 | Arquivo não encontrado. | — | | `storage.invalid_key` | — | Nome de arquivo inválido. | — | | `storage.operation_failed` | 502 | Não conseguimos acessar o arquivo agora. Tente novamente em instantes. | — | | `validation_failed` | — | Confira os campos destacados e tente novamente. | — | | `webhook_ingress.route_retired` | — | Esta rota de webhook só existe no ambiente local. Em nuvem, o webhook entra pelo endpoint do API Gateway. | — | # Como um conector funciona > O modelo técnico de um conector — arquivo e manifesto, fatias e cursor, leitura e escrita, quarentena, contagens e banco local. Esta parte é para quem quer entender, revisar ou depurar o que a IA escreve. Conectores são escritos pela IA de construção ou, por quem tem TI própria, no próprio editor com a [CLI](/reference/cli/). ## Arquivo e manifesto [Seção intitulada “Arquivo e manifesto”](#arquivo-e-manifesto) Um conector é: * **um arquivo TypeScript** que exporta como `default` uma função que recebe o [`ctx`](/reference/sdk/); * **um [manifesto](/reference/manifest/)** JSON que declara o cron, os hosts, as rotas, os agentes, os segredos e se o conector escreve fora. O código é compilado num arquivo só. Imports só do Node ou de tipos; não há pacotes de terceiros, rede, sistema de arquivos útil nem variáveis de ambiente. Tudo o que sai do código passa pelo `ctx`. ## Fatias e cursor [Seção intitulada “Fatias e cursor”](#fatias-e-cursor) Uma execução roda em **fatias**. Em cada fatia a função é chamada de novo, do começo, com o mesmo `ctx.state`. O padrão é sempre o mesmo: 1. Ler o cursor de `ctx.state` (a data da última alteração lida, a última chave). 2. Ler uma página da origem a partir do cursor. 3. Processar e gravar o novo cursor com `ctx.state.set`. 4. Se `ctx.shouldYield()` for `true`, devolver `{ done: false }` para continuar na próxima fatia; quando não houver mais nada, devolver `{ done: true }` (ou nada). O `ctx.state` sobrevive entre execuções: a próxima execução começa de onde a última que terminou bem (sucesso ou parcial) parou. Uma execução que falha, ou uma execução real com escrita que ficou guardada esperando confirmação, não avança o cursor, então nada se perde. Zere no estado o que só serve a uma execução (o número da página, por exemplo). ## Ler e escrever [Seção intitulada “Ler e escrever”](#ler-e-escrever) | Chamada | Uso | No ensaio | Em produção | | -------------------------- | -------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------ | | `ctx.http.request` | Ler, com qualquer método (um `POST` que só consulta vai aqui). | De verdade | De verdade | | `ctx.http.write` | Criar, alterar ou apagar algo em outro sistema. | Registrada, não sai (`planned`) | Exige `externalWrites`; a primeira em cada host espera confirmação | | `client.read.` (SOAP) | Operação SOAP que só consulta. | De verdade | De verdade | | `client.write.` (SOAP) | Operação SOAP que muda algo. | Registrada, não sai | Como `http.write` | | `ctx.db.query` | Um `SELECT` num banco da rede da loja, pelo agente. | De verdade | De verdade | Uma escrita feita por `ctx.http.request` passaria no ensaio como leitura: por isso o ensaio lista todo `POST`, `PUT`, `PATCH` e `DELETE` feito por `request` (`writesAsReads`) para uma pessoa conferir. Toda escrita na Zoppy usa `write`. Quando `planned` vem `true`, a escrita não chegou ao outro sistema (ensaio, ou a primeira escrita esperando confirmação). O código deve seguir normalmente, sem tratar como erro. ## Toda escrita é idempotente [Seção intitulada “Toda escrita é idempotente”](#toda-escrita-é-idempotente) O gateway não deduplica: quem garante que nada se repete é o conector. Toda escrita, na Zoppy ou em outro sistema, é: * um **upsert pela chave natural** (id externo, documento, e-mail ou telefone), com `PUT` ou `PATCH` no id do registro; ou * uma criação (`POST`) feita **depois de consultar, na mesma execução**, se o registro já existe: um `GET` pela chave, ou a rota de leitura declarada em `readRoutes`, na mesma coleção. Criar sem essa consulta é proibido, porque o cursor é relido: depois que uma escrita guardada é confirmada, depois de uma queda e no reprocesso da quarentena, o mesmo registro passa de novo pelo conector. O ensaio confere a regra. Um `POST` que é escrita (fora de `readRoutes`) num caminho sem nenhuma leitura antes, na mesma execução, no mesmo host e na mesma coleção (o caminho sem os ids do fim: `/customers/123` e `/customers` são a mesma coleção), volta como problema `connect.write_without_lookup`. Esse ensaio não conta como limpo e a publicação é recusada com `409 connect.write_without_lookup` até um ensaio sem ele. `PUT` e `PATCH` contam como upsert e não são cobrados. ## Registro recusado: quarentena [Seção intitulada “Registro recusado: quarentena”](#registro-recusado-quarentena) Um registro que o outro lado recusa com 4xx, ou que não dá para mapear, vai para `ctx.quarantine(registro, motivo)` e o conector segue com o próximo. Falha geral (o sistema fora do ar, credencial errada) é erro: deixe a exceção subir para a execução falhar e a próxima retomar do cursor. No reprocesso, a execução recebe os registros em `ctx.input.quarantined` (`[{ id, record, reason }]`) em vez de ler a origem; o que ela não mandar de volta para a quarentena sai como resolvido. ## Contagens [Seção intitulada “Contagens”](#contagens) `ctx.count(objeto, tipo, quantidade)` alimenta o resumo da execução: `read`, `created`, `updated`, `skipped` e `failed`, por objeto (`clientes`, `pedidos`, `produtos`). Uma contagem `failed` deixa a execução `partial`. ## Banco de dados local [Seção intitulada “Banco de dados local”](#banco-de-dados-local) * Uma consulta por chamada, só `SELECT` (ou `WITH ... SELECT`), recusada no gateway e de novo no agente se mudar algo. * Paginação pela chave: `ORDER BY chave` e `WHERE chave > :ultima`, com a última chave em `ctx.state`, e `maxRows` até o limite do gateway. `truncated: true` quer dizer que há mais linhas. * Marcadores de parâmetro de cada motor: SQL Server `@p1`, Oracle `:1`, Firebird e MySQL `?`, Postgres `$1`. * Decimais chegam como texto exato e datas como texto ISO. * No reconhecimento, a IA descobre tabelas e colunas pelas visões de catálogo de cada banco e olha os dados só em agregado. ## Segredos [Seção intitulada “Segredos”](#segredos) Declare o nome em `secrets` no manifesto e use `ctx.secret('NOME')` onde o valor deve ir (um cabeçalho, um parâmetro, o `soapHeader`). A chamada devolve uma referência que o gateway troca pelo valor na saída: o valor nunca está no código, nos logs nem na conversa. ## Erros que o código vê [Seção intitulada “Erros que o código vê”](#erros-que-o-código-vê) Uma recusa do gateway lança `ConnectGatewayError` com `status` e `code` (`connect.route_not_allowed`, `connect.agent_offline`…). Um limite da execução lança `ConnectLimitError`; mesmo capturado, a execução termina com falha por aquele motivo. Veja o [catálogo de erros](/reference/errors/) e os [limites](/reference/limits/). # Limites e estados > Os limites de cada fatia, execução, chamada, agente e da IA, e os estados de conectores e execuções. Gerada da configuração padrão da API e dos valores de produção do CDK (Zoppy-crm/zoppy-connect\@f1ae3a72be71). ## Limites [Seção intitulada “Limites”](#limites) | Onde | Limite | Valor | | ------------ | --------------------------------------------------------------------------------- | --------------- | | Fatia | Tempo máximo de uma fatia | 10 min | | Fatia | `ctx.shouldYield()` passa a responder `true` depois de | 9 min | | Fatia | Memória | 256 MB | | Fatia | Chamadas (HTTP, SOAP e banco) por fatia | 5000 | | Fatia | Tamanho de uma resposta | 10 MB | | Fatia | Logs por fatia | 256 KB | | Fatia | Registros em quarentena por fatia | 500 | | Execução | Fatias por execução | 48 | | Execução | Fatias do ensaio amostra | 1 | | Execução | Pausa automática depois de execuções reais seguidas com erro | 3 | | Gateway | Tempo de uma chamada a outro sistema | 1 min | | Manifesto | Intervalo do cron | de 5 a 1440 min | | Agente local | Espera por um agente fora do ar antes de falhar | 15 s | | Agente local | Aviso aos administradores com o agente sem sinal há | 5 min | | Agente local | Batimento do túnel | 20 s | | Agente local | Linhas por consulta (`maxRows`) | 5000 | | Agente local | Tempo de uma consulta SQL | 2 min | | Agente local | Tempo de uma chamada HTTP interna | 1 min | | IA | Teto mensal por loja (copiloto e construção somados) | R$ 100 | | IA | Teto por conversa de construção | R$ 50 | | Confirmação | Validade da prévia (publicar, ligar, pausar, aprovar hosts, escritas, reprocesso) | 10 min | ## Estados do conector [Seção intitulada “Estados do conector”](#estados-do-conector) | Valor | Significado | | --------- | ------------------------------------------------------------------- | | `draft` | Rascunho: ainda não roda sozinho. Pode ser ensaiado e publicado. | | `enabled` | Ligado: roda no cron com a versão publicada. | | `paused` | Pausado, com um motivo (abaixo). Não roda até alguém ligar de novo. | ## Motivos de pausa [Seção intitulada “Motivos de pausa”](#motivos-de-pausa) | Valor | Significado | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `manual` | Uma pessoa pausou, com prévia e confirmação. | | `failures` | Pausa automática depois de execuções reais seguidas com erro. Os administradores recebem um aviso. | | `emergency` | Pausa de emergência do time da Zoppy (do conector, da loja ou da plataforma). Só o time libera. | | `zoppy_token_revoked` | O acesso do Connect à Zoppy foi desativado. Todos os conectores da loja pausam; um administrador gera um acesso novo e religa. | ## O que iniciou a execução [Seção intitulada “O que iniciou a execução”](#o-que-iniciou-a-execução) | Valor | Significado | | ----------- | -------------------------------------------------------- | | `cron` | O agendamento do manifesto. | | `manual` | Uma pessoa clicou em rodar agora, com o conector ligado. | | `rehearsal` | Um ensaio da versão em edição. | | `reprocess` | O reprocesso de registros da quarentena. | ## Modos de execução [Seção intitulada “Modos de execução”](#modos-de-execução) | Valor | Significado | | --------- | -------------------------------------------------------------------------------- | | `dry_run` | Ensaio: leituras de verdade, escritas só registradas como planejadas. | | `live` | Produção: escritas saem de verdade (a primeira em cada host espera confirmação). | ## Tipos de ensaio [Seção intitulada “Tipos de ensaio”](#tipos-de-ensaio) | Valor | Significado | | -------- | -------------------------------------------------------------------------------------------- | | `sample` | Amostra: roda só a primeira fatia, para ver rápido se o conector funciona. | | `full` | Completo: lê a origem inteira, em fatias. Publicar exige um ensaio completo limpo da versão. | ## Estados da execução [Seção intitulada “Estados da execução”](#estados-da-execução) | Valor | Significado | | ----------- | ---------------------------------------------------------------------------------- | | `running` | Rodando. | | `succeeded` | Terminou bem. | | `partial` | Terminou, mas parte dos registros foi para a quarentena ou foi contada como falha. | | `failed` | Falhou; o motivo está em `failureReason`. | | `timed_out` | Estourou o tempo de uma fatia. | ## Motivos de falha da execução [Seção intitulada “Motivos de falha da execução”](#motivos-de-falha-da-execução) | Valor | Significado | | ---------------- | ---------------------------------------------------------------------------------------- | | `error` | O código lançou um erro. O detalhe mostra a mensagem e a linha do TypeScript. | | `memory` | Passou da memória de uma fatia. | | `time` | Passou do tempo de uma fatia sem devolver `{ done: false }`. | | `slices` | Passou do número máximo de fatias de uma execução. | | `calls` | Passou do número de chamadas por fatia. | | `response_size` | Uma resposta passou do tamanho máximo. | | `log_size` | Os logs da fatia passaram do tamanho máximo. | | `quarantine` | Registros demais na quarentena numa fatia. | | `crash` | O processo da fatia morreu ou o trabalho se perdeu. A próxima execução retoma do cursor. | | `invalid_bundle` | O código compilado da versão não pôde ser carregado. | | `paused` | O conector foi pausado com a execução em andamento. | | `removed` | O conector foi removido com a execução em andamento. | ## Contagens (`ctx.count`) [Seção intitulada “Contagens (ctx.count)”](#contagens-ctxcount) | Valor | Significado | | --------- | ------------------------------------------------------------ | | `read` | Lidos na origem. | | `created` | Criados no destino. | | `updated` | Atualizados no destino. | | `skipped` | Ignorados: nada a mudar. | | `failed` | Com falha. Uma contagem de falha deixa a execução `partial`. | # Manifesto > O JSON que diz o que um conector pode fazer: cron, hosts, rotas, agentes locais, segredos e escritas. O manifesto diz o que o conector pode fazer. Ele é conferido antes de uma versão ser guardada: o gatilho do cron, os hosts e as rotas que o código chama, os agentes locais que ele alcança, os nomes dos segredos e se ele escreve em outro sistema. Nome repetido numa lista é recusado. Gerada do schema que a API publica em `GET /connect/manifest.schema.json` (Zoppy-crm/zoppy-connect\@f1ae3a72be71). A IA de construção escreve o manifesto junto com o código; ninguém o edita à mão. ## Exemplo: um sistema na nuvem e a Zoppy [Seção intitulada “Exemplo: um sistema na nuvem e a Zoppy”](#exemplo-um-sistema-na-nuvem-e-a-zoppy) ```json { "version": 1, "triggers": [ { "type": "cron", "everyMinutes": 30 } ], "hosts": ["api.exemplo.com.br", "partners.zoppy.com.br"], "routes": [ { "host": "partners.zoppy.com.br", "methods": ["GET"], "path": "/customers" }, { "host": "partners.zoppy.com.br", "methods": ["POST", "PUT"], "path": "/customers/**" } ], "agents": [], "secrets": [ { "name": "EXEMPLO_TOKEN", "label": "Token da API do sistema", "help": "No sistema, em Configurações > Integrações > Token." } ], "externalWrites": true } ``` ## Exemplo: banco do ERP pelo agente local [Seção intitulada “Exemplo: banco do ERP pelo agente local”](#exemplo-banco-do-erp-pelo-agente-local) ```json { "version": 1, "triggers": [ { "type": "cron", "everyMinutes": 60 } ], "hosts": ["partners.zoppy.com.br"], "routes": [ { "host": "partners.zoppy.com.br", "methods": ["GET", "POST"], "path": "/customers/**" } ], "agents": [ { "agentId": "6f1c2b0e-3a4d-4c1b-9e2f-0a1b2c3d4e5f", "http": [], "databases": [ { "name": "erp", "engine": "sqlserver", "host": "srvsql01", "port": 1433, "database": "ERP", "username": "ERP_DB_USER", "password": "ERP_DB_PASSWORD" } ] } ], "secrets": [ { "name": "ERP_DB_USER", "label": "Usuário só leitura do banco do ERP" }, { "name": "ERP_DB_PASSWORD", "label": "Senha desse usuário" } ], "externalWrites": true } ``` ## Campos [Seção intitulada “Campos”](#campos) | Campo | Tipo | Obrigatório | Regras | Descrição | | ------------------------------- | --------- | ----------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `version` | number | sim | de 1 a 1 | Formato do manifesto: sempre `1`. | | `triggers` | object\[] | sim | mínimo 1 item(ns); máximo 1 item(ns) | O que inicia uma execução: um cron, na V1. | | `triggers[].type` | string | sim | um de `cron` | Sempre `cron`: roda a cada `everyMinutes` minutos. | | `triggers[].everyMinutes` | integer | sim | de 5 a 1440 | Intervalo entre execuções, em minutos. | | `hosts` | string\[] | sim | máximo 20 item(ns) | Domínios que o código chama com `ctx.http` e `ctx.soap`, sempre por `https`. Um host novo espera uma pessoa da loja aprovar antes do ensaio. | | `hosts[]` | string | sim | — | Um domínio, sem esquema, porta nem caminho. IP e domínio de rede interna (`.local`, `.internal`, `.lan`…) são recusados. | | `routes` | object\[] | não | máximo 100 item(ns); padrão `[]` | Método e caminho liberados por host. Um host com rotas só aceita as rotas dele; `partners.zoppy.com.br` sempre precisa de rotas. | | `routes[].host` | string | sim | — | Um dos `hosts` do manifesto. | | `routes[].methods` | string\[] | sim | mínimo 1 item(ns); máximo 6 item(ns) | Métodos HTTP liberados nesse caminho. `DELETE` nunca é aceito em `partners.zoppy.com.br`. | | `routes[].path` | string | sim | — | Padrão de caminho, sem query string, começando com `/`: segmentos literais, `*` vale um segmento e `**` no fim vale o resto (ex.: `/api/v1/customers/*`). | | `readRoutes` | object\[] | não | máximo 50 item(ns); padrão `[]` | POST, PUT ou PATCH que só consultam (APIs que buscam por POST, operações SOAP de leitura). Fora destas rotas, todo POST, PUT ou PATCH é tratado como escrita. Aprovadas junto com o host; nunca em `partners.zoppy.com.br`. | | `readRoutes[].host` | string | sim | — | Um dos `hosts` do manifesto, nunca `partners.zoppy.com.br`. | | `readRoutes[].methods` | string\[] | sim | mínimo 1 item(ns); máximo 3 item(ns) | Métodos que, nesse caminho, são leitura: `POST`, `PUT` ou `PATCH`. | | `readRoutes[].path` | string | sim | — | Padrão de caminho, com a mesma sintaxe de `routes[].path`. | | `agents` | object\[] | não | máximo 3 item(ns); padrão `[]` | Agentes locais dentro da rede da loja e o que o conector alcança por cada um: APIs internas e bancos de dados. | | `agents[].agentId` | string | sim | UUID | Id do agente local (aparece na tela de agentes). O gateway manda essas chamadas pelo túnel dele. | | `agents[].http` | object\[] | não | máximo 20 item(ns); padrão `[]` | APIs dentro da rede da loja, chamadas com `ctx.http`. | | `agents[].http[]` | object | sim | — | Uma API interna. Pelo agente, só `GET` e `HEAD`. | | `agents[].http[].host` | string | sim | — | Host na rede da loja, como a máquina do agente o resolve: um nome, um domínio ou um IPv4, sem esquema, porta nem caminho. Um host do agente nunca é chamado pela internet. | | `agents[].http[].port` | integer | sim | de 1 a 65535 | Porta da API (8080, 443…). | | `agents[].http[].tls` | boolean | sim | — | `true` quando a API é `https`; `false` quando é `http` simples dentro da rede. | | `agents[].databases` | object\[] | não | máximo 10 item(ns); padrão `[]` | Bancos lidos com `ctx.db.query`, só leitura. | | `agents[].databases[]` | object | sim | — | Um banco que o agente lê com um usuário só leitura. | | `agents[].databases[].name` | string | sim | formato `^[a-z][a-z0-9_]{0,31}$` | Como o código chama este banco em `ctx.db.query({ database })`: nome curto em minúsculas, como `erp`. | | `agents[].databases[].engine` | string | sim | um de `sqlserver`, `oracle`, `firebird`, `mysql`, `postgres` | O motor do banco. | | `agents[].databases[].host` | string | sim | — | Host do banco na rede da loja. | | `agents[].databases[].port` | integer | sim | de 1 a 65535 | Porta do banco (1433 SQL Server, 1521 Oracle, 3050 Firebird, 3306 MySQL, 5432 Postgres). | | `agents[].databases[].database` | string | sim | — | O banco como o servidor o conhece: nome do banco, *service name* do Oracle, caminho ou alias do Firebird. | | `agents[].databases[].username` | string | sim | formato `^[A-Z][A-Z0-9_]{0,63}$` | Nome do segredo do conector com o usuário só leitura do banco. Precisa estar em `secrets`. | | `agents[].databases[].password` | string | sim | formato `^[A-Z][A-Z0-9_]{0,63}$` | Nome do segredo do conector com a senha desse usuário. Precisa estar em `secrets`. | | `secrets` | object\[] | sim | máximo 20 item(ns) | Segredos que o código usa com `ctx.secret(nome)`. Uma pessoa preenche o valor na tela; ele nunca fica no código nem passa pelo chat. | | `secrets[].name` | string | sim | formato `^[A-Z][A-Z0-9_]{0,63}$` | Nome que o código usa, em `MAIÚSCULAS_COM_SUBLINHADO`. `ZOPPY_TOKEN` é reservado: o acesso à Zoppy é da loja e o gateway o injeta sozinho. | | `secrets[].label` | string | sim | — | O que a pessoa preenche, em português. | | `secrets[].help` | string | não | — | Onde a pessoa encontra o valor no outro sistema. | | `externalWrites` | boolean | sim | — | Se o conector escreve em outro sistema (inclusive na Zoppy). Sem isso, `ctx.http.write` e `client.write` são recusados. | ## Regras entre campos [Seção intitulada “Regras entre campos”](#regras-entre-campos) * Todo `routes[].host` precisa estar em `hosts`. * `partners.zoppy.com.br` em `hosts` exige as rotas que o conector usa em `routes`, e `DELETE` nunca é aceito nele. * Hosts, nomes de segredo, rotas (host + caminho), agentes, alvos dos agentes e nomes de banco não se repetem. * `username` e `password` de um banco do agente são nomes declarados em `secrets`. * Um host não pode estar ao mesmo tempo em `hosts` (internet) e em `agents[].http` (rede da loja). # MCP do Connect > Ligar Claude, Cursor ou outro cliente MCP ao Zoppy Connect para consultar e operar conectores com prévia e confirmação: endereço, login, permissões e ferramentas. Gerada do catálogo do próprio servidor MCP e dos metadados OAuth (Zoppy-crm/zoppy-connect\@f1ae3a72be71). O servidor MCP deixa uma IA **consultar e operar** o Connect da loja com as permissões de quem conectou: ver conectores, execuções, quarentena, escritas guardadas e agentes locais, e ligar, pausar, rodar agora, reprocessar ou confirmar a primeira escrita, sempre com prévia e confirmação. Segredos ficam fora da conversa. ## Quem pode conectar [Seção intitulada “Quem pode conectar”](#quem-pode-conectar) * Usuários **MASTER** ou **ADMIN** da loja. Outros papéis não conseguem aprovar nenhuma permissão. * Lojas com o Connect **liberado** pelo time da Zoppy. * Com o **termo do Connect aceito** por um MASTER da loja (no primeiro acesso à tela). A liberação e o termo são conferidos de novo em cada chamada: se o Connect for desligado para a loja, as ferramentas param de responder. ## Endereço do servidor [Seção intitulada “Endereço do servidor”](#endereço-do-servidor) | Ambiente | Servidor MCP | | -------- | ---------------------------------------------- | | Produção | `https://api-connect.zoppy.com.br/mcp` | | Staging | `https://api-connect-staging.zoppy.com.br/mcp` | O transporte é Streamable HTTP (só `POST`) e o login é OAuth: não há chave de API para colar. ## Conectar o cliente [Seção intitulada “Conectar o cliente”](#conectar-o-cliente) ### Claude Code [Seção intitulada “Claude Code”](#claude-code) ```sh claude mcp add --transport http zoppy-connect https://api-connect.zoppy.com.br/mcp ``` Use `--scope user` para ter o servidor em todos os projetos (o padrão, `local`, vale só para o projeto atual). Depois, dentro do Claude Code, rode `/mcp` e siga o login no navegador. Se o navegador não abrir sozinho, copie o endereço que o Claude Code mostra. ### Claude (web e Claude Desktop) [Seção intitulada “Claude (web e Claude Desktop)”](#claude-web-e-claude-desktop) No Claude, os servidores remotos entram como conectores personalizados da sua conta e valem também no Claude Desktop: 1. Abra **Customize > Connectors**. 2. Clique em **+ Add** e em **Add custom connector**. 3. Dê um nome (por exemplo, Zoppy Connect), cole `https://api-connect.zoppy.com.br/mcp` e clique em **Continue**. 4. Confira a autenticação detectada (OAuth) e clique em **Continue**. 5. Em **Authentication**, escolha **Sign in now**, clique em **Add** e siga o login no navegador. Em planos Team e Enterprise, quem adiciona é o dono da organização, em **Organization settings > Connectors**; cada pessoa depois clica em **Connect** em **Customize > Connectors**. O Claude Desktop não conecta servidores remotos escritos no `claude_desktop_config.json`: use o caminho acima. ### Cursor [Seção intitulada “Cursor”](#cursor) No `~/.cursor/mcp.json` (todos os projetos) ou no `.cursor/mcp.json` do projeto: ```json { "mcpServers": { "zoppy-connect": { "url": "https://api-connect.zoppy.com.br/mcp" } } } ``` O Cursor faz o login OAuth do servidor (código de autorização com PKCE). Funciona no aplicativo desktop, que recebe o retorno do login em `localhost`; o Cursor na web e os agentes do Cursor voltam para um endereço que o Connect não aceita. ### Outros clientes MCP [Seção intitulada “Outros clientes MCP”](#outros-clientes-mcp) Qualquer cliente com Streamable HTTP e OAuth do MCP funciona, se ele: * descobrir o servidor de autorização pelo `WWW-Authenticate` da primeira resposta `401` (metadados em `https://api-connect.zoppy.com.br/.well-known/oauth-protected-resource/mcp`); * se registrar sozinho (registro dinâmico, sem segredo de cliente) e usar PKCE `S256`; * receber o retorno do login em `http` na própria máquina (`127.0.0.1` ou `[::1]` em qualquer porta, ou `localhost`), ou num destes endereços: `https://claude.ai/api/mcp/auth_callback`, `https://claude.com/api/mcp/auth_callback`. Clientes que não fazem OAuth sozinhos, ou que só aceitam uma chave fixa, não conectam. ## Login e consentimento [Seção intitulada “Login e consentimento”](#login-e-consentimento) 1. O cliente abre o navegador na Zoppy. Entre com a sua conta. 2. A tela de consentimento mostra o aplicativo, a loja, para onde o navegador volta e as permissões pedidas. Marque ao menos uma e autorize. 3. O cliente recebe um acesso que vale 1 hora(s) e se renova sozinho. A autorização vence em 30 dias: aí o cliente pede o login de novo, e o papel e a liberação são conferidos outra vez. ## Permissões [Seção intitulada “Permissões”](#permissões) | Permissão | O que libera | Ferramentas | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | `connect:read` | Ver os conectores, as execuções (passos, erros e contadores), a quarentena e os agentes locais. | `list_connectors`, `list_runs`, `read_run`, `read_quarantine`, `list_held_writes`, `list_local_agents` | | `connect:operate` | Ligar, pausar, rodar agora, reprocessar a quarentena e confirmar a primeira escrita guardada, sempre com prévia e confirmação. | `preview_operation`, `confirm_operation` | O cliente só vê as ferramentas das permissões que você marcou. Para ganhar outra, desconecte e conecte de novo marcando a que falta. ## Prévia e confirmação [Seção intitulada “Prévia e confirmação”](#prévia-e-confirmação) Nenhuma ferramenta muda nada num passo só: 1. `preview_operation` mostra o que vai acontecer (título e mudanças) e devolve um `confirmationToken`. Nada muda ainda. 2. O cliente mostra a prévia e pergunta se você concorda. 3. Só depois do seu sim ele chama `confirm_operation` com o token. O token vale **uma vez**, por **10 minutos**, e só para quem pediu a prévia. Vencido ou já usado, responde `connect.mcp_confirmation_invalid`: peça a prévia de novo. | Operação | O que faz | | ---------------------- | --------------------------------------------------------------------------------------- | | `enable` | Liga o conector: ele passa a rodar no cron, com a versão publicada. | | `pause` | Pausa o conector: o cron para até alguém ligar de novo. | | `run_now` | Abre uma execução agora, fora do cron. O conector precisa estar ligado. | | `reprocess_quarantine` | Reprocessa registros da quarentena: os de `itemIds`, ou os abertos mais novos. | | `confirm_held_write` | Manda a primeira escrita guardada (`heldWriteId`) e libera as próximas naquele sistema. | As instruções que o servidor manda ao cliente ao conectar: > Zoppy Connect: os conectores que movem dados entre a loja na Zoppy e outros sistemas. Leia com list_connectors, list_runs, read_run, read_quarantine, list_held_writes e list_local_agents. Toda mudança é em dois passos: preview_operation mostra o que vai acontecer e devolve um confirmationToken; só chame confirm_operation depois que a pessoa concordar com a prévia. O token vale uma vez, por 10 minutos. Construir, publicar e editar o código dos conectores é só pela tela do Connect. ## Ferramentas [Seção intitulada “Ferramentas”](#ferramentas) ### `list_connectors` [Seção intitulada “list_connectors”](#list_connectors) O ponto de partida: devolve os ids que as outras ferramentas pedem. Pergunte “como estão os meus conectores?”. Permissão `connect:read`; só leitura. Descrição que o cliente recebe: *Os conectores da loja com o estado de cada um, a versão publicada, o cron e a última execução.* Sem parâmetros. ### `list_runs` [Seção intitulada “list_runs”](#list_runs) Para saber se um conector está rodando bem e achar a execução que falhou. Pergunte “o que aconteceu nas últimas execuções do conector de clientes?”. Permissão `connect:read`; só leitura. Descrição que o cliente recebe: *As últimas execuções de um conector, da mais nova para a mais antiga, com estado e motivo da falha.* | Parâmetro | Tipo | Obrigatório | Regras | Descrição | | ------------- | ------- | ----------- | --------- | --------------------------------------------- | | `connectorId` | string | sim | UUID | Id do conector, como list_connectors devolve | | `limit` | integer | não | de 1 a 50 | Quantas execuções, das mais novas (padrão 10) | ### `read_run` [Seção intitulada “read_run”](#read_run) Para entender uma falha: os passos, os contadores e o erro com a linha do código. Pergunte “por que a última execução falhou?”. Permissão `connect:read`; só leitura. Descrição que o cliente recebe: *Uma execução inteira: estado, contadores, os passos, o erro com a linha do código e a quarentena dela. Dados pessoais vêm mascarados.* | Parâmetro | Tipo | Obrigatório | Regras | Descrição | | ------------- | ------ | ----------- | ------ | -------------------------------------------- | | `connectorId` | string | sim | UUID | Id do conector, como list_connectors devolve | | `runId` | string | sim | UUID | Id da execução, como list_runs devolve | ### `read_quarantine` [Seção intitulada “read_quarantine”](#read_quarantine) Os registros que o outro sistema recusou e o motivo de cada um, para decidir se vale reprocessar. Permissão `connect:read`; só leitura. Descrição que o cliente recebe: *Os registros abertos na quarentena do conector, com o motivo e as tentativas. Dados pessoais vêm mascarados.* | Parâmetro | Tipo | Obrigatório | Regras | Descrição | | ------------- | ------- | ----------- | ---------- | -------------------------------------------- | | `connectorId` | string | sim | UUID | Id do conector, como list_connectors devolve | | `limit` | integer | não | de 1 a 500 | Quantos registros abertos (padrão 50) | ### `list_held_writes` [Seção intitulada “list_held_writes”](#list_held_writes) A primeira escrita em cada sistema fica guardada até uma pessoa confirmar. Mostra o que seria enviado. Permissão `connect:read`; só leitura. Descrição que o cliente recebe: *As escritas guardadas do conector: a primeira escrita em cada sistema espera a confirmação de uma pessoa.* | Parâmetro | Tipo | Obrigatório | Regras | Descrição | | ------------- | ------ | ----------- | ------ | -------------------------------------------- | | `connectorId` | string | sim | UUID | Id do conector, como list_connectors devolve | ### `list_local_agents` [Seção intitulada “list_local_agents”](#list_local_agents) Se o agente instalado no computador da loja está conectado e o que ele alcança. Permissão `connect:read`; só leitura. Descrição que o cliente recebe: *Os agentes locais da loja, se estão conectados, o que alcançam e que conectores usam cada um.* Sem parâmetros. ### `preview_operation` [Seção intitulada “preview_operation”](#preview_operation) Primeiro passo de toda mudança. Não muda nada: mostra o que vai acontecer e devolve o `confirmationToken`. Permissão `connect:operate`; só leitura. Descrição que o cliente recebe: *Primeiro passo de uma mudança: mostra o que vai acontecer e devolve um confirmationToken. Nada muda ainda. Mostre a prévia à pessoa antes de confirmar.* | Parâmetro | Tipo | Obrigatório | Regras | Descrição | | ------------- | --------- | ----------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `operation` | string | sim | um de `enable`, `pause`, `run_now`, `reprocess_quarantine`, `confirm_held_write` | enable liga; pause pausa; run_now roda agora; reprocess_quarantine reprocessa a quarentena; confirm_held_write manda a primeira escrita guardada | | `connectorId` | string | sim | UUID | Id do conector, como list_connectors devolve | | `heldWriteId` | string | não | UUID | Só em confirm_held_write: a escrita guardada, como list_held_writes devolve | | `itemIds` | string\[] | não | UUID; de 1 a 500 itens | Só em reprocess_quarantine: os registros; sem ele vão os abertos mais novos | ### `confirm_operation` [Seção intitulada “confirm_operation”](#confirm_operation) Segundo passo: aplica a mudança da prévia. O cliente deve pedir o seu sim antes de chamar. Permissão `connect:operate`; muda o Connect. Descrição que o cliente recebe: *Segundo passo: aplica a mudança da prévia com o confirmationToken dela, depois que a pessoa concordou.* | Parâmetro | Tipo | Obrigatório | Regras | Descrição | | ------------------- | ------ | ----------- | ------------------ | --------------------------------------------------------------------------------- | | `confirmationToken` | string | sim | até 256 caracteres | O confirmationToken devolvido por preview_operation; vale uma vez, por 10 minutos | ## O que fica de fora [Seção intitulada “O que fica de fora”](#o-que-fica-de-fora) * **Construir, editar o código e publicar.** São pela tela do Connect, pela [conversa de construção](/store/build-by-chat/) ou pela [CLI](/reference/cli/). * **Segredos.** Os valores são preenchidos só no formulário da tela. * **Aprovar hosts e criar conectores.** Só na tela. Os erros das ferramentas vêm com `code` e `message` em português, os mesmos do [catálogo de erros](/reference/errors/). ## Endpoints do OAuth [Seção intitulada “Endpoints do OAuth”](#endpoints-do-oauth) Para quem implementa um cliente. Caminhos relativos a `https://api-connect.zoppy.com.br` (ou ao endereço da staging): | Método | Caminho | O que faz | | ------ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | GET | `/.well-known/oauth-protected-resource` | Metadados do recurso protegido (RFC 9728), o mesmo que o caminho com `/mcp`. | | GET | `/.well-known/oauth-protected-resource/mcp` | Metadados do recurso protegido do MCP (RFC 9728): o recurso, o servidor de autorização e os escopos. | | GET | `/.well-known/oauth-authorization-server` | Metadados do servidor de autorização (RFC 8414): endpoints, escopos, PKCE `S256`. | | GET | `/.well-known/oauth-authorization-server/mcp` | O mesmo que o caminho sem `/mcp`, para clientes que procuram pelo caminho do recurso. | | GET | `/oauth/authorize` | Início do login: confere o cliente, o PKCE e o recurso e manda para a tela de consentimento. | | GET | `/oauth/consent` | Tela de consentimento mínima da própria API, para ambientes sem a do Connect. Em produção e staging, o consentimento abre na tela do Connect. | | POST | `/oauth/token` | Troca o código pelo acesso e renova o acesso; reusar um refresh revoga a concessão inteira. | | POST | `/oauth/register` | Registro dinâmico do cliente (RFC 7591), sem segredo de cliente (`token_endpoint_auth_method: none`). | | POST | `/oauth/revoke` | Revoga a concessão do token informado (RFC 7009). | | GET | `/oauth/authorizations/:requestId` | O pedido que a tela de consentimento mostra: o aplicativo, a loja e as permissões pedidas. | | POST | `/oauth/authorizations/:requestId/decision` | A decisão da pessoa: aprovar com as permissões marcadas ou recusar. | | POST | `/mcp` | O servidor MCP (Streamable HTTP), com `Authorization: Bearer `. | | ALL | `/mcp` | Qualquer outro método responde 405: o servidor só aceita POST. | Metadados do servidor de autorização em produção: ```json { "issuer": "https://api-connect.zoppy.com.br", "authorization_endpoint": "https://api-connect.zoppy.com.br/oauth/authorize", "token_endpoint": "https://api-connect.zoppy.com.br/oauth/token", "registration_endpoint": "https://api-connect.zoppy.com.br/oauth/register", "revocation_endpoint": "https://api-connect.zoppy.com.br/oauth/revoke", "scopes_supported": ["connect:read", "connect:operate", "connect:build"], "response_types_supported": ["code"], "response_modes_supported": ["query"], "grant_types_supported": ["authorization_code", "refresh_token"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none"], "revocation_endpoint_auth_methods_supported": ["none"], "authorization_response_iss_parameter_supported": true } ``` # API de Parceiros da Zoppy > As regras de um conector que lê ou escreve na Zoppy — o host lógico, as rotas, nunca DELETE, deduplicação e paginação. O Connect lê e escreve os dados da loja na Zoppy **só pela API de Parceiros**. A documentação dos recursos, dos campos e das respostas está em **[partners.zoppy.com.br](https://partners.zoppy.com.br)**; a IA de construção a lê antes de escrever um conector que fala com a Zoppy. ## Host lógico [Seção intitulada “Host lógico”](#host-lógico) O código e o manifesto usam sempre `https://partners.zoppy.com.br`, em qualquer ambiente. O gateway troca esse host pelo endereço da API de Parceiros do ambiente em que o conector roda (staging ou produção). Nunca escreva outro host da Zoppy num conector. ## Acesso [Seção intitulada “Acesso”](#acesso) O conector não declara nem lê o acesso à Zoppy. O gateway injeta o acesso da loja só nas chamadas para `partners.zoppy.com.br`. `ZOPPY_TOKEN` é um nome reservado e não pode estar em `secrets`. ## Rotas obrigatórias e nunca DELETE [Seção intitulada “Rotas obrigatórias e nunca DELETE”](#rotas-obrigatórias-e-nunca-delete) * `partners.zoppy.com.br` em `hosts` exige as rotas que o conector usa em `routes`, com os métodos de cada uma. * `DELETE` nunca é aceito na Zoppy, nem no manifesto nem no gateway (`connect.route_not_allowed`). * Toda escrita na Zoppy usa `ctx.http.write` e exige `externalWrites: true`. A primeira escrita real espera uma pessoa da loja confirmar. ```json { "hosts": ["partners.zoppy.com.br"], "routes": [ { "host": "partners.zoppy.com.br", "methods": ["GET"], "path": "/customers" }, { "host": "partners.zoppy.com.br", "methods": ["GET", "POST", "PUT"], "path": "/customers/**" } ] } ``` ## Regras de cada recurso [Seção intitulada “Regras de cada recurso”](#regras-de-cada-recurso) São as regras atuais da API de Parceiros, que a IA segue: * **Sempre buscar antes de escrever**: o registro já existe? então atualize em vez de criar. * **Uma escrita por chamada**: não há escrita em lote. * **Pedido** recusa um `externalId` repetido com `422`: trate como “já importado”, não como erro. * **Cliente** é deduplicado pelo telefone. * **Produto** é deduplicado pelo nome. * **Loja** faz upsert pelo `externalId`. * **Leitura incremental**: cursor por `updatedAt` mais o id, com `pageSize` 50. * **Recusa de um registro** (4xx) vai para `ctx.quarantine`; falha geral (5xx, fora do ar) é erro da execução. ## Acesso revogado [Seção intitulada “Acesso revogado”](#acesso-revogado) Um `401` da API de Parceiros quer dizer que o acesso da loja foi desativado: a execução falha, todos os conectores da loja pausam com o motivo `zoppy_token_revoked` e os administradores são avisados. Veja [A Zoppy no Connect](/store/zoppy-in-connect/#se-o-acesso-for-desativado). # SDK do conector (ctx) > Tudo o que o código de um conector enxerga: http, soap, banco local pelo agente, segredos, estado, log, quarentena e contagens. Gerada dos tipos do pacote `@zoppy-connect/connect-sdk` (Zoppy-crm/zoppy-connect\@f1ae3a72be71). Um conector é um arquivo TypeScript que exporta uma função e recebe o `ctx`: ```ts import type { ConnectorContext, ConnectorResult } from '@zoppy-connect/connect-sdk'; export default async function run(ctx: ConnectorContext): Promise { let cursor = (ctx.state.get('cursor') as string | undefined) ?? '1970-01-01T00:00:00Z'; for (;;) { const resposta = await ctx.http.request({ method: 'GET', url: 'https://api.exemplo.com.br/v1/clientes', headers: { Authorization: `Bearer ${ctx.secret('EXEMPLO_TOKEN')}` }, query: { alteradoDesde: cursor, limite: '100' }, }); const clientes = resposta.json() as { id: string; alteradoEm: string }[]; ctx.count('clientes', 'read', clientes.length); if (clientes.length === 0) { return { done: true }; } cursor = clientes[clientes.length - 1].alteradoEm; ctx.state.set('cursor', cursor); if (ctx.shouldYield()) { return { done: false }; } } } ``` O código não tem rede nem variáveis de ambiente: só o que está aqui. Imports só do Node ou de tipos; o resto é recusado na compilação. Os limites de cada fatia estão em [Limites](/reference/limits/). ## O conector [Seção intitulada “O conector”](#o-conector) ### `ConnectorMain` [Seção intitulada “ConnectorMain”](#connectormain) A função que o arquivo do conector exporta como `default`. Recebe o `ctx` e devolve nada, ou um `ConnectorResult`. ```ts type ConnectorMain = (ctx: ConnectorContext) => Promise; ``` ### `ConnectorResult` [Seção intitulada “ConnectorResult”](#connectorresult) O que a função do conector devolve. Sem retorno, ou com `done: true`, a execução termina. | Membro | Tipo | Descrição | | -------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `done` | `boolean (opcional)` | `false` pede mais uma fatia: guarde o cursor em `ctx.state` antes de devolver. Nada ou `true` termina a execução. | | `output` | `Readonly> (opcional)` | O que a execução devolve a quem a chamou, na fatia que termina. Ignorado quando o manifesto não declara saída (o caso de todo conector na V1, que só roda pelo cron) e nas fatias que continuam. | ### `ConnectorContext` [Seção intitulada “ConnectorContext”](#connectorcontext) O `ctx`: tudo o que o código do conector enxerga. O código roda sem rede; cada chamada abaixo passa pelo gateway, que só deixa sair o que o manifesto declara. | Membro | Tipo | Descrição | | -------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `http` | `{ … }` | Chamadas HTTP pelo gateway, separadas entre ler e escrever. | | `http.request` | `request(request: HttpRequest): Promise` | Lê do outro sistema, seja qual for o método: um `POST` que só consulta (comum em APIs que pesquisam por `POST` e em SOAP) também vai aqui. É de verdade também no ensaio. Um `POST`, `PUT`, `PATCH` ou `DELETE` feito aqui aparece na lista de escritas feitas como leitura (`writesAsReads`) do ensaio, para uma pessoa conferir. | | `http.write` | `write(request: HttpRequest): Promise` | Cria, altera ou apaga algo no outro sistema. No ensaio, a chamada é registrada sem sair (`planned: true`). Em produção, exige `externalWrites: true` no manifesto, e a primeira escrita do conector em cada host espera uma pessoa confirmar: até lá ela fica guardada, as outras escritas daquela execução não saem e cada uma responde `planned: true`. Nada enviado aqui é desfeito. | | `soap` | `soap(wsdlUrl: string, options?: SoapOptions): Promise` | Um serviço SOAP pelo WSDL (buscado pelo gateway, num host do manifesto, com os imports). Cada operação vira uma função: `client.read.(args)` quando só consulta, `client.write.(args)` quando cria ou altera algo, com as mesmas regras de `http.request` e `http.write`. O WSDL é buscado uma vez por fatia. | | `xmlToJson` | `xmlToJson(xml: string): JsonValue` | Converte XML em dados simples, do mesmo jeito que uma resposta SOAP chega: para um serviço que responde com XML dentro de um texto. | | `db` | `LocalDatabases` | Consultas só de leitura num banco dentro da rede da loja (SQL Server, Oracle, Firebird, MySQL ou Postgres), pelo agente local que o manifesto indica em `agents`. O agente roda a consulta lá com o usuário só leitura dos segredos do conector; um único `SELECT` por chamada, e a lista local da máquina decide o que ele alcança. | | `secret` | `secret(name: string): string` | Uma referência a um segredo do manifesto. O gateway troca a referência pelo valor na saída; o código nunca vê o valor. | | `state` | `ConnectorState` | Memória que sobrevive entre fatias e entre execuções. Uma execução começa de onde a última que terminou bem (com sucesso ou parcial) parou. | | `log` | `log(message: string, data?: JsonValue, level?: LogLevel): void` | Registra uma linha nos passos da execução, com dados opcionais. Dados pessoais são mascarados. | | `shouldYield` | `shouldYield(): boolean` | Fica `true` quando a fatia está perto do fim: guarde o cursor em `ctx.state` e devolva `{ done: false }` para continuar na próxima fatia. | | `mode` | `RunMode` | `live` aplica as escritas; `dry_run` (o ensaio) as registra como planejadas. Leituras externas são de verdade nos dois. | | `quarantine` | `quarantine(record: JsonValue, reason: string): void` | Separa um registro em vez de movê-lo, com o motivo, para uma pessoa revisar depois; a execução segue com o próximo e termina `partial`. Use para dado que o outro lado recusa (um 4xx) ou que não dá para mapear (CPF/CNPJ faltando, código de produto desconhecido). Há um teto de registros por fatia, e cada um conta para o limite de tamanho do log. | | `count` | `count(object: string, kind: CountKind, amount?: number): void` | Conta o que aconteceu com os registros de um objeto nesta execução (pedidos, clientes, produtos): lidos na origem, criados ou atualizados do outro lado, ignorados (nada a mudar) ou com falha. O resumo da execução são essas contagens, por objeto e tipo. | | `input` | `JsonValue \| null` | O que iniciou a execução. No reprocesso da quarentena, `{ quarantined: [{ id, record, reason }] }` com os registros escolhidos; numa execução do cron, manual ou de ensaio, `null`. | ### `ConnectorState` [Seção intitulada “ConnectorState”](#connectorstate) Guardado entre fatias e execuções. | Membro | Tipo | Descrição | | ------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `get` | `get(key: string): JsonValue \| undefined` | Lê um valor guardado. Guarde aqui o cursor da origem (a data da última alteração lida, a última chave) e zere o que só uma execução usa (uma página). | | `set` | `set(key: string, value: JsonValue): void` | Guarda um valor JSON para a próxima fatia e para a próxima execução. | ### `RunMode` [Seção intitulada “RunMode”](#runmode) `live` (produção) ou `dry_run` (ensaio). ```ts type RunMode = 'live' | 'dry_run'; ``` ### `CountKind` [Seção intitulada “CountKind”](#countkind) O tipo de contagem de `ctx.count`: lidos, criados, atualizados, ignorados ou com falha. ```ts type CountKind = 'read' | 'created' | 'updated' | 'skipped' | 'failed'; ``` ### `LogLevel` [Seção intitulada “LogLevel”](#loglevel) Nível de uma linha de `ctx.log`: `info` (o padrão), `warn` ou `error`; `warn` e `error` ficam marcados para quem lê a execução. ```ts type LogLevel = 'info' | 'warn' | 'error'; ``` ## HTTP [Seção intitulada “HTTP”](#http) ### `HttpRequest` [Seção intitulada “HttpRequest”](#httprequest) Uma chamada HTTP de `ctx.http.request` ou `ctx.http.write`. | Membro | Tipo | Descrição | | --------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `method` | `HttpMethod` | Método HTTP. | | `url` | `string` | URL absoluta: `https` num host de `hosts` do manifesto, ou `http(s)://host:porta` de uma API de um agente do manifesto, que desce pelo túnel do agente até a rede da loja. O gateway recusa qualquer outra. | | `headers` | `Readonly> (opcional)` | Cabeçalhos. Use `ctx.secret('NOME')` no valor de um cabeçalho de autenticação. | | `query` | `Readonly> (opcional)` | Parâmetros da query string. | | `body` | `unknown (opcional)` | Um texto vai como está; qualquer outro valor vai como JSON. | ### `HttpResponse` [Seção intitulada “HttpResponse”](#httpresponse) A resposta de uma chamada HTTP. | Membro | Tipo | Descrição | | --------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `planned` | `boolean` | `true` quando uma escrita não chegou ao outro sistema: no ensaio, ou numa escrita real esperando a primeira confirmação. | | `status` | `number` | Código HTTP da resposta. | | `headers` | `Readonly>` | Cabeçalhos da resposta. | | `body` | `string` | Corpo da resposta como texto. | | `json` | `json(): unknown` | O corpo interpretado como JSON. | ### `HttpMethod` [Seção intitulada “HttpMethod”](#httpmethod) Os métodos HTTP aceitos. ```ts type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'; ``` ## Banco de dados local (pelo agente) [Seção intitulada “Banco de dados local (pelo agente)”](#banco-de-dados-local-pelo-agente) ### `LocalDatabases` [Seção intitulada “LocalDatabases”](#localdatabases) Os bancos dos agentes do manifesto. | Membro | Tipo | Descrição | | ------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `query` | `query(query: SqlQuery): Promise` | Roda uma consulta só de leitura num banco dos agentes do manifesto. É de verdade também no ensaio, como toda leitura. | ### `SqlQuery` [Seção intitulada “SqlQuery”](#sqlquery) Uma consulta de `ctx.db.query`. | Membro | Tipo | Descrição | | ---------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `database` | `string` | O nome de um banco dos agentes do manifesto (`agents[].databases[].name`). | | `sql` | `string` | Um `SELECT` (ou `WITH ... SELECT`), nada que mude dados. Os marcadores são os do próprio banco, por posição: SQL Server `@p1, @p2...`; Oracle `:1, :2...`; Firebird e MySQL `?`; Postgres `$1, $2...`. Pagine na própria consulta: `ORDER BY` uma chave e `WHERE chave > última lida`, com a última guardada em `ctx.state`. | | `params` | `readonly SqlParam[] (opcional)` | Os parâmetros, na ordem dos marcadores. | | `maxRows` | `number (opcional)` | No máximo esta quantidade de linhas (o gateway limita); se houver mais, a resposta vem com `truncated: true`. | ### `SqlParam` [Seção intitulada “SqlParam”](#sqlparam) Um parâmetro da consulta. Um `Date` vai como data e hora; para decimal exato, data sem hora ou bytes, diga o tipo: `{ type: 'decimal', value: '1234.50' }`, `{ type: 'date', value: '2026-10-31' }`, `{ type: 'binary', value: base64 }`. ```ts type SqlParam = | string | number | boolean | null | Date | { readonly type: 'datetime' | 'date' | 'decimal' | 'binary'; readonly value: string }; ``` ### `SqlResult` [Seção intitulada “SqlResult”](#sqlresult) O resultado de uma consulta. | Membro | Tipo | Descrição | | ----------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------- | | `columns` | `readonly SqlColumn[]` | As colunas, na ordem do `SELECT`, com o tipo de cada uma. | | `rows` | `readonly SqlRow[]` | As linhas, cada uma por nome de coluna. | | `rowCount` | `number` | Quantas linhas vieram. | | `truncated` | `boolean` | `true` quando a consulta tinha mais linhas que `maxRows` (ou que o tamanho de uma resposta): leia a próxima página. | ### `SqlColumn` [Seção intitulada “SqlColumn”](#sqlcolumn) Uma coluna do resultado. | Membro | Tipo | Descrição | | ------ | --------------- | --------------------------------- | | `name` | `string` | Nome (ou apelido) da coluna. | | `type` | `SqlColumnType` | Como os valores da coluna chegam. | ### `SqlColumnType` [Seção intitulada “SqlColumnType”](#sqlcolumntype) Decimais e inteiros grandes chegam como texto exato, datas e horas como texto ISO, bytes em base64. ```ts type SqlColumnType = | 'text' | 'integer' | 'decimal' | 'float' | 'boolean' | 'date' | 'time' | 'datetime' | 'datetimeoffset' | 'binary' | 'other'; ``` ### `SqlRow` [Seção intitulada “SqlRow”](#sqlrow) Uma linha por nome de coluna. Dê um apelido único a cada coluna: nome repetido fica com o último valor. ```ts type SqlRow = { readonly [column: string]: JsonValue }; ``` ## SOAP [Seção intitulada “SOAP”](#soap) ### `SoapOptions` [Seção intitulada “SoapOptions”](#soapoptions) Opções de `ctx.soap`. | Membro | Tipo | Descrição | | ------------ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `endpoint` | `string (opcional)` | O endereço a chamar no lugar do `soap:address` do WSDL: `https`, num host do manifesto. | | `port` | `string (opcional)` | A porta do WSDL, pelo nome; por padrão, a primeira porta SOAP 1.1. | | `headers` | `Readonly> (opcional)` | Cabeçalhos HTTP de toda chamada, inclusive a busca do WSDL (um `Authorization` com `ctx.secret`). | | `soapHeader` | `string (opcional)` | XML que vai dentro do `soap:Header` de toda chamada (WS-Security, um token de sessão), com segredos como `ctx.secret('NOME')`. | ### `SoapClient` [Seção intitulada “SoapClient”](#soapclient) O cliente de um serviço SOAP. | Membro | Tipo | Descrição | | --------------------------- | ------------------- | --------------------------------------------------------------------------------------------------- | | `endpoint` | `string` | O endereço que o cliente chama. | | `operations` | `readonly string[]` | As operações da porta, por nome. | | `read` | `{ … }` | Operações que só consultam: de verdade também no ensaio. | | `read.[operation: string]` | `SoapOperation` | Uma operação de leitura, pelo nome. | | `write` | `{ … }` | Operações que criam, alteram ou apagam algo no outro sistema: escrita externa, planejada no ensaio. | | `write.[operation: string]` | `SoapOperation` | Uma operação de escrita, pelo nome. | ### `SoapOperation` [Seção intitulada “SoapOperation”](#soapoperation) Uma operação do serviço: recebe os argumentos e devolve a resposta. ```ts type SoapOperation = (args?: SoapArguments) => Promise; ``` ### `SoapArguments` [Seção intitulada “SoapArguments”](#soaparguments) Os argumentos de uma operação: os campos de entrada por nome, em qualquer ordem (o cliente escreve na ordem do WSDL). | Membro | Tipo | Descrição | | ----------------- | ----------- | -------------------- | | `[field: string]` | `JsonValue` | Um campo de entrada. | ### `SoapResponse` [Seção intitulada “SoapResponse”](#soapresponse) A resposta de uma operação SOAP. | Membro | Tipo | Descrição | | --------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `planned` | `boolean` | `true` quando uma escrita não chegou ao outro sistema (ensaio, ou esperando a primeira confirmação): `data` vem `null`. | | `status` | `number` | Código HTTP da resposta. | | `fault` | `SoapFault \| null` | O `soap:Fault` que o serviço respondeu, ou `null`. | | `data` | `JsonValue` | O conteúdo do elemento de saída da operação como dados simples, por nome de elemento, sem namespaces: todo valor é texto (converta números e datas), `xsi:nil` vira `null`, e um campo que o WSDL diz que repete é sempre uma lista. | | `xml` | `string` | A resposta como chegou, para o que `data` deixa de fora (atributos). | ### `SoapFault` [Seção intitulada “SoapFault”](#soapfault) Um `soap:Fault`. | Membro | Tipo | Descrição | | --------- | ----------- | ------------------------------------- | | `code` | `string` | Código da falha. | | `message` | `string` | Mensagem da falha. | | `detail` | `JsonValue` | Detalhe da falha, como dados simples. | ## Dados e erros [Seção intitulada “Dados e erros”](#dados-e-erros) ### `JsonValue` [Seção intitulada “JsonValue”](#jsonvalue) Qualquer valor JSON. ```ts type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }; ``` ### `ConnectGatewayError` [Seção intitulada “ConnectGatewayError”](#connectgatewayerror) O gateway recusou ou não completou a chamada: host fora do manifesto, segredo desconhecido, endereço bloqueado, conector pausado. O `code` é um dos códigos `connect.*` do [catálogo de erros](/reference/errors/). | Membro | Tipo | Descrição | | -------- | -------- | ---------------------------------------------------------- | | `status` | `number` | Código HTTP da recusa. | | `code` | `string` | Código estável do erro (ex.: `connect.route_not_allowed`). | ### `ConnectLimitError` [Seção intitulada “ConnectLimitError”](#connectlimiterror) Um limite da execução foi atingido. O contexto lembra do erro: a execução termina com falha por esse motivo mesmo que o código capture a exceção. | Membro | Tipo | Descrição | | -------- | ------------- | ------------------------- | | `reason` | `LimitReason` | Qual limite foi atingido. | ### `LimitReason` [Seção intitulada “LimitReason”](#limitreason) Os limites que geram `ConnectLimitError`: chamadas, tamanho de resposta, tamanho do log, registros em quarentena. ```ts type LimitReason = 'calls' | 'response_size' | 'log_size' | 'quarantine'; ``` # Custos da IA > Os tetos de gasto da IA do Connect por loja e por conversa, e o que acontece quando um deles é atingido. A IA do Connect tem dois tetos de gasto, absorvidos pela Zoppy: | Teto | Valor | | ------------------------------------------------ | ------ | | Por mês, por loja, somando Copiloto e construção | R$ 100 | | Por conversa de construção | R$ 50 | O Copiloto não tem teto por conversa; ele conta só para o teto do mês. O painel mostra o modo (Copiloto ou construção) e o gasto do mês em reais. ## Quando um teto é atingido [Seção intitulada “Quando um teto é atingido”](#quando-um-teto-é-atingido) * **Teto da conversa de construção**: aquela conversa de construção para, e a tela avisa. * **Teto do mês**: o Copiloto e a construção param para a loja inteira até o mês virar, e a tela avisa (`connect.ai_month_cap_reached`). Os tetos valem só para a IA. **Os conectores ligados continuam rodando** normalmente: executar um conector não usa IA. # Construir pela conversa > Como criar uma integração sem escrever código — o Copiloto, o modo construção, os acessos, os hosts e a ordem da conversa. Você nunca escreve código no Zoppy Connect. Você conversa; a IA pesquisa a documentação do sistema, escreve o conector, ensaia e corrige. ## Copiloto e modo construção [Seção intitulada “Copiloto e modo construção”](#copiloto-e-modo-construção) O mesmo painel de conversa tem dois modos, e um selo mostra em qual você está e quanto a IA já gastou no mês. | Modo | Para quê | O que pode fazer | | -------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | **Copiloto** | Tirar dúvidas e entender o que está acontecendo: “por que essa execução terminou parcial?”, “quais conectores estão pausados?” | Só lê: a tela aberta, os conectores, as versões, as execuções, a quarentena, os agentes, o catálogo e esta documentação. Não faz nada. | | **Construção** | Criar ou corrigir um conector. Fica ligado a um conector; na tela do conector, o painel é a própria conversa de construção. | Pesquisa e lê documentação, pede acessos e hosts, escreve versões, ensaia e lê execuções. | Quando você pede ao Copiloto para construir ou corrigir algo, ele propõe a troca. No seu sim, a conversa segue em modo construção, levando um resumo do que já foi dito, ligada a um conector novo ou a um existente. Numa execução com erro, o botão **pedir para a IA corrigir** leva a execução para a conversa de construção: a IA lê o erro, corrige a versão em edição e ensaia de novo. ## A ordem da conversa [Seção intitulada “A ordem da conversa”](#a-ordem-da-conversa) A IA segue sempre a mesma ordem, recalculada a cada mensagem: 1. **Conexão.** Primeiro ela garante que alcança o sistema: pede os segredos (pelo formulário, nunca pelo chat), os hosts que precisa chamar ou um agente local pareado, e faz um ensaio só de leitura. A Zoppy já vem conectada. 2. **Reconhecimento.** Ela olha os dados reais só em agregado (quantos registros, que campos existem, como as datas vêm). Num banco local, isso inclui descobrir as tabelas e colunas. 3. **O pedido.** Só então ela escreve o que você pediu: “traga os clientes do ERP a cada 30 minutos”, “mande os pedidos da Zoppy para o ERP”. Se a IA tentar ensaiar antes de ter os acessos, o próprio Connect recusa e ela volta a pedir o que falta. ## Acessos: segredos fora do chat [Seção intitulada “Acessos: segredos fora do chat”](#acessos-segredos-fora-do-chat) Quando a IA precisa de um token, uma senha ou o usuário do banco, ela abre um **pedido de acesso**: um formulário fora do chat, com o nome do que preencher e onde achar o valor no outro sistema. O valor fica cifrado e ninguém (nem você, nem a IA, nem o código) consegue lê-lo de volta. Se você colar um segredo no chat por engano, ele é mascarado antes de chegar à IA e vira um pedido de acesso. ## Hosts: aprovar para onde o conector fala [Seção intitulada “Hosts: aprovar para onde o conector fala”](#hosts-aprovar-para-onde-o-conector-fala) O conector só chama os domínios declarados no manifesto. Quando uma versão pede um domínio novo, uma pessoa da loja aprova na tela do conector (com prévia e confirmação) antes do ensaio. `partners.zoppy.com.br`, a Zoppy, já vem aprovado. ## Sistema instalado na loja [Seção intitulada “Sistema instalado na loja”](#sistema-instalado-na-loja) Se o sistema roda num computador da loja sem API na internet, a IA verifica se há um agente local pareado que alcança aquele banco ou API. Se não houver, ela orienta a pedir a instalação ao time da Zoppy ([Agente local: instalação](/support/local-agent/)). O agente só lê: o Connect não escreve no sistema local. ## Integração nativa [Seção intitulada “Integração nativa”](#integração-nativa) Se a loja já tem uma integração nativa da Zoppy ativa para o mesmo dado, a IA avisa e pede a sua confirmação antes de escrever, para não duplicar. ## Memória [Seção intitulada “Memória”](#memória) A IA enxerga os conectores, as versões e as execuções da sua loja e parte deles (“faz igual ao de clientes, mas para pedidos”); reaproveita os segredos do mesmo sistema com o seu sim. Ela também lê as notas do catálogo sobre o sistema: as oficiais, escritas pelo time da Zoppy, e as aprendidas por outras lojas, que são curtas, nunca têm código, link, segredo ou dado pessoal e só entram depois de um ensaio limpo de quem as escreveu. ## Custos [Seção intitulada “Custos”](#custos) A IA tem teto de gasto: R$ 100 por mês por loja, somando Copiloto e construção, e R$ 50 por conversa de construção. Veja [Custos da IA](/store/ai-costs/). # Ensaio > O ensaio roda o conector de verdade sem gravar nada — amostra e completo, escritas planejadas e escritas feitas como leitura. O **ensaio** roda a versão em edição do conector contra os sistemas de verdade, mas **não grava nada**: nem na Zoppy, nem no outro sistema. * **Leituras são de verdade**, inclusive no banco do sistema local. Você vê os dados reais que o conector leu. * **Escritas não saem.** Cada uma vira uma **requisição planejada**: a requisição exata que o conector mandaria, com método, endereço e corpo (segredos aparecem só como marcadores). ## Amostra e completo [Seção intitulada “Amostra e completo”](#amostra-e-completo) | Ensaio | O que roda | Quando usar | | ------------ | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Amostra** | Só a primeira fatia da execução. | Ver rápido se o conector funciona. Se a amostra foi cortada (havia mais para ler), a tela marca que falta um ensaio completo. | | **Completo** | A execução inteira, em fatias, lendo a origem toda. | Antes de publicar: **publicar exige um ensaio completo limpo daquela versão**, sem criação sem consulta. Uma versão nova (mesmo de um conector já ligado) precisa de novo ensaio. | ## O que conferir no resultado [Seção intitulada “O que conferir no resultado”](#o-que-conferir-no-resultado) * **Status e contagens**: quantos registros foram lidos, quantos seriam criados, atualizados ou ignorados, por objeto (clientes, pedidos, produtos). * **Requisições planejadas**: o que sairia para cada sistema. * **Escritas feitas como leitura**: chamadas `POST`, `PUT`, `PATCH` ou `DELETE` que o conector fez pelo caminho de leitura. Algumas APIs consultam por `POST` (e aí está certo); se for uma escrita de verdade, peça para a IA corrigir, porque ela teria saído no ensaio. * **Criação sem consulta** (`connect.write_without_lookup`): o conector criaria um registro sem antes consultar se ele já existe. Toda escrita precisa ser idempotente: o conector busca pela chave natural (id externo, documento, e-mail ou telefone) antes de criar, ou atualiza pelo id, porque o mesmo registro volta a passar por ele depois de uma confirmação, de uma queda ou de um reprocesso. Com esse problema o ensaio não conta como limpo e a publicação é recusada; a IA corrige e ensaia de novo. * **Passos**: cada chamada, consulta ao banco local e log, com dados pessoais mascarados. * **Quarentena**: registros que o conector separaria, com o motivo. ## Quando o ensaio é recusado antes de começar [Seção intitulada “Quando o ensaio é recusado antes de começar”](#quando-o-ensaio-é-recusado-antes-de-começar) | Código | O que fazer | | --------------------------------- | ------------------------------------------------------------ | | `connect.hosts_awaiting_approval` | Aprove os hosts novos na tela do conector. | | `connect.access_incomplete` | Preencha os segredos que faltam no formulário de acesso. | | `connect.run_in_progress` | O conector já tem uma execução rodando; espere ela terminar. | Todos os códigos estão no [catálogo de erros](/reference/errors/). ## O que o ensaio não garante [Seção intitulada “O que o ensaio não garante”](#o-que-o-ensaio-não-garante) O ensaio sabe o que o conector mandaria, mas não sabe se o outro sistema (ou a Zoppy) aceitaria cada escrita. Por isso a [primeira escrita em cada sistema](/store/first-write/) espera a sua confirmação, e os registros recusados vão para a [quarentena](/store/runs-and-quarantine/). # Primeira escrita > A primeira escrita real em cada sistema espera a confirmação de uma pessoa. O que acontece antes, durante e depois. Escrever num sistema é o que mais tem risco, e **nada se desfaz**: o Connect nunca apaga nada na Zoppy, e o que vai para outro sistema não tem desfazer. Por isso, em produção, a **primeira escrita do conector em cada sistema** espera uma pessoa da loja confirmar. Vale também para a Zoppy. ## Como funciona [Seção intitulada “Como funciona”](#como-funciona) 1. O conector publicado e ligado roda e chega à primeira escrita num sistema (por exemplo, o primeiro cliente criado na Zoppy). 2. Essa escrita **fica guardada**, exatamente como sairia (segredos aparecem só como marcadores). As outras escritas daquela execução **não saem**. 3. Na tela do conector, em **escritas esperando confirmação**, você abre a prévia, confere e confirma. 4. A escrita guardada sai **uma vez, na hora**. Daí em diante, as escritas do conector naquele sistema saem sem perguntar. 5. A execução que guardou a escrita **não avança o cursor** (na tela ela aparece com o cursor mantido). Enquanto houver escrita guardada ou confirmada esperando, o agendamento não roda o conector e **Rodar agora** responde `connect.writes_awaiting_confirmation`; o ensaio continua liberado. 6. Depois que a escrita sai (ou é descartada), a próxima execução recomeça do último cursor bom e refaz as escritas que não saíram, inclusive a do registro já confirmado, que o conector encontra na busca antes de escrever. Uma versão publicada que passa a escrever num **sistema novo** pede uma nova confirmação nesse sistema. ## Situações especiais [Seção intitulada “Situações especiais”](#situações-especiais) | Código | O que significa | | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `connect.writes_awaiting_confirmation` | O conector tem uma escrita esperando confirmação. Confirme ou descarte a escrita antes de rodar de novo. | | `connect.held_write_connector_paused` | O conector está pausado, então a escrita não saiu. Ela ficou confirmada e sai sozinha quando o conector for ligado de novo. | | `connect.held_write_outdated` | A versão publicada não escreve mais nesse sistema. A próxima execução guarda uma nova escrita para confirmar. | | `connect.held_write_relay_failed` | A escrita não saiu agora e continua confirmada. Ela sai sozinha em instantes. | | `connect.held_write_send_uncertain` | A escrita falhou no meio do caminho e **pode ter chegado** ao outro sistema. Ela não é repetida: confira no outro sistema. | | `connect.held_write_decided` | Essa escrita já foi decidida (já saiu, está saindo ou foi trocada por uma mais nova). | ## Antes de escrever, o conector busca [Seção intitulada “Antes de escrever, o conector busca”](#antes-de-escrever-o-conector-busca) Para não duplicar dados, toda escrita é idempotente: os conectores sempre **buscam antes de escrever** (o cliente já existe? o pedido já foi importado?), e o ensaio recusa uma criação sem consulta (`connect.write_without_lookup`). Eles seguem as regras de cada recurso da Zoppy ([API de Parceiros da Zoppy](/reference/partners-api/)). Se a loja tem uma integração nativa ativa para o mesmo dado, a IA avisa antes. # Primeiros passos > Quem pode usar o Zoppy Connect, como a loja é liberada e o que acontece no primeiro acesso. ## Quem pode usar [Seção intitulada “Quem pode usar”](#quem-pode-usar) * **Lojas liberadas pelo time da Zoppy.** O Connect é ligado por loja; se a sua ainda não tem, a tela explica como pedir a liberação ao seu atendimento. * **Plano pago.** O Connect acessa os dados da loja pela API de Parceiros da Zoppy, que não está disponível no plano gratuito. * **Usuários MASTER ou ADMIN.** Outros papéis não abrem o Connect. O login é o mesmo da Zoppy: você entra pela Zoppy (com a verificação em duas etapas, se estiver ligada) e abre o Connect pelo botão que aparece para quem tem acesso. ## No primeiro acesso [Seção intitulada “No primeiro acesso”](#no-primeiro-acesso) 1. **Termo do Connect.** No primeiro acesso de um MASTER, a tela pede o aceite do termo do Connect, com registro de quem aceitou e quando. 2. **Acesso à Zoppy.** Na primeira integração da loja, o Connect gera sozinho o acesso dele à API de Parceiros da Zoppy, com o seu login. Você não precisa copiar token nenhum. Veja [A Zoppy no Connect](/store/zoppy-in-connect/). ## A tela [Seção intitulada “A tela”](#a-tela) * **Catálogo de sistemas**: a Zoppy (oficial) e os sistemas que já se sabe integrar, com notas do que funciona. Sistemas que só autenticam por OAuth aparecem como “em breve”. * **Conectores**: cada integração, com o estado (rascunho, ligado ou pausado), as versões, os segredos, os hosts aprovados, as execuções, a quarentena e as escritas esperando confirmação. * **Agentes locais**: as máquinas da loja com o agente instalado, para integrar sistemas que rodam no computador da loja. * **Copiloto**: o painel de conversa, presente em todas as telas. Veja [Construir pela conversa](/store/build-by-chat/). ## O caminho de uma integração [Seção intitulada “O caminho de uma integração”](#o-caminho-de-uma-integração) 1. [Construir pela conversa](/store/build-by-chat/): você conta o que quer, a IA pede os acessos, conhece os dados e escreve o conector. 2. [Ensaio](/store/dry-run/): a IA roda o conector de verdade, sem gravar nada, e mostra o que ele faria. 3. [Publicar e ligar](/store/publish-and-enable/): você confere a prévia e confirma. 4. [Primeira escrita](/store/first-write/): a primeira escrita em cada sistema espera o seu sim. 5. [Execuções e quarentena](/store/runs-and-quarantine/): acompanhe o que rodou e resolva os registros recusados. # Publicar, ligar e pausar > Versões, prévia e confirmação, o cron, rodar agora e pausar um conector. ## Versões [Seção intitulada “Versões”](#versões) Cada mudança que a IA faz no código ou no manifesto vira uma **versão** nova. Na tela do conector você vê todas, o **diff** entre duas versões, o código (só leitura) e pode **restaurar** uma versão antiga (o que cria uma versão nova com aquele conteúdo). Se outra versão entrar enquanto alguém edita, a mudança é recusada para não sobrescrever (`connect.version_conflict`): abra a versão mais nova e repita. Há sempre duas versões que importam: * a **versão em edição**, a mais nova, que a IA ensaia; * a **versão publicada**, a que roda de verdade no cron. ## Prévia e confirmação [Seção intitulada “Prévia e confirmação”](#prévia-e-confirmação) Publicar, ligar, pausar, aprovar hosts, confirmar uma escrita e reprocessar a quarentena sempre têm dois passos: 1. **Prévia**: a tela mostra exatamente o que vai mudar. 2. **Confirmação**: você confirma. A confirmação vale por 10 minutos, uma vez só, para você, para aquele conector e para aquele estado. Se algo mudar no meio (uma versão nova, outra pessoa pausou), ela é recusada (`connect.confirmation_stale`) e você gera a prévia de novo. ## Publicar [Seção intitulada “Publicar”](#publicar) Publicar faz a versão em edição valer nas execuções reais. Só é possível com um **ensaio completo limpo** daquela versão (`connect.not_rehearsed` se faltar). Publicar uma versão nova de um conector ligado troca o que roda a partir da próxima execução. ## Ligar [Seção intitulada “Ligar”](#ligar) Ligar põe o conector publicado no **cron** do manifesto, a cada 5 a 1.440 minutos. A primeira execução sai na hora. O cron nunca abre uma execução enquanto outra do mesmo conector ainda está rodando. Um conector que fala com a Zoppy só liga com o [acesso à Zoppy](/store/zoppy-in-connect/) ativo. ## Rodar agora [Seção intitulada “Rodar agora”](#rodar-agora) Com o conector ligado, **rodar agora** abre uma execução real fora do horário do cron. ## Pausar [Seção intitulada “Pausar”](#pausar) Pausar para o conector até alguém ligar de novo. Além da pausa manual, o conector pode ser pausado sozinho: | Motivo | O que aconteceu | Como voltar | | ----------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | | Manual | Uma pessoa pausou. | Ligar de novo. | | Falhas seguidas | 3 execuções reais seguidas terminaram com erro. Os administradores recebem um aviso. | Ver o erro, pedir para a IA corrigir, ensaiar, publicar e ligar. | | Acesso à Zoppy revogado | O acesso do Connect à Zoppy foi desativado. Todos os conectores da loja pausam. | Um administrador abre o Connect, que gera um acesso novo e oferece religar os conectores. | | Emergência | O time da Zoppy pausou o conector, a loja ou o Connect inteiro. | Só o time da Zoppy libera. | # Execuções e quarentena > Acompanhar o que cada execução fez, entender um erro e resolver os registros que foram para a quarentena. ## Execuções [Seção intitulada “Execuções”](#execuções) Cada vez que o conector roda, nasce uma **execução**. Ela pode ter várias **fatias** de até 10 minutos: o conector lê um pedaço, guarda onde parou e continua na fatia seguinte. | Status | Significado | | -------------- | ------------------------------------------------------------------------------------------ | | Rodando | Em andamento. | | Sucesso | Terminou bem. | | Parcial | Terminou, mas parte dos registros foi para a quarentena ou falhou. O resto foi processado. | | Falhou | Parou com erro. A tela mostra o motivo e, num erro do código, a mensagem e a linha. | | Tempo esgotado | Uma fatia passou do tempo. | O detalhe de cada execução mostra: * **Contagens** por objeto: lidos, criados, atualizados, ignorados e com falha. * **Passos**: cada chamada a outro sistema, cada consulta ao banco local e cada log, com dados pessoais mascarados. Ficam guardados por 30 dias. * **O erro**, com a linha do código onde aconteceu. * Num ensaio, as **requisições planejadas** e as **escritas feitas como leitura** ([Ensaio](/store/dry-run/)). * Numa execução real, as escritas que saíram para outros sistemas. Uma execução que falha não perde nada: a próxima retoma do ponto em que a última execução bem-sucedida (ou parcial) parou. Depois de 3 execuções reais seguidas com erro, o conector é pausado e os administradores recebem um aviso. Para corrigir, use **pedir para a IA corrigir** na execução com erro. Os motivos de falha e todos os estados estão em [Limites e estados](/reference/limits/). ## Quarentena [Seção intitulada “Quarentena”](#quarentena) Quando a Zoppy ou o outro sistema **recusa um registro** (um telefone inválido, um produto que não existe, um CPF faltando), o conector não para: ele separa aquele registro na **quarentena**, com o motivo, e segue com os outros. A execução termina **parcial**. Na quarentena você pode: * **Corrigir** um registro: trocar os campos errados. Ele volta com os valores novos no próximo reprocesso. * **Descartar** um registro: ele sai da quarentena sem ser reprocessado. * **Reprocessar**: escolher os registros (ou os mais novos em aberto, até 500 de uma vez), ver a prévia e confirmar. O Connect abre uma execução de reprocesso com a versão publicada, só com esses registros. O que não voltar para a quarentena sai como resolvido quando ela termina bem. Os registros da quarentena ficam 30 dias depois da última mudança, com dados pessoais mascarados na tela. ## O que mais pode dar errado [Seção intitulada “O que mais pode dar errado”](#o-que-mais-pode-dar-errado) Veja o [catálogo de erros](/reference/errors/), com a mensagem e o que fazer para cada código. Se o conector usa o agente local, a [página do agente local](/support/local-agent/) traz o diagnóstico do agente fora do ar. # A Zoppy no Connect > Como o Connect lê e escreve na Zoppy pela API de Parceiros, o acesso que ele gera para a loja e o que acontece se esse acesso for desativado. Para o Connect, a Zoppy é mais um sistema do catálogo: o oficial, o primeiro da lista. Ele lê e escreve os dados da loja **só pela API de Parceiros da Zoppy** ([documentação](https://partners.zoppy.com.br)), do mesmo jeito que faria com qualquer outro sistema, e nunca acessa o banco da Zoppy diretamente. ## O acesso da loja [Seção intitulada “O acesso da loja”](#o-acesso-da-loja) Na primeira integração da loja, o Connect gera sozinho, com o login de quem está construindo, um **acesso à API de Parceiros** para a loja. Esse acesso: * fica guardado cifrado, como um segredo da loja, com o registro de quem o gerou; * é usado só em chamadas para a Zoppy (o gateway nunca o manda para outro sistema); * nunca aparece para ninguém, nem para o código do conector. Só lojas com plano pago geram esse acesso (`connect.zoppy_free_tier`). ## O que o conector pode fazer na Zoppy [Seção intitulada “O que o conector pode fazer na Zoppy”](#o-que-o-conector-pode-fazer-na-zoppy) * Só as rotas que o manifesto declara para `partners.zoppy.com.br`. * **Nunca apagar**: `DELETE` é sempre recusado. * A primeira escrita real na Zoppy espera a sua confirmação, como em qualquer sistema ([Primeira escrita](/store/first-write/)). ## Se o acesso for desativado [Seção intitulada “Se o acesso for desativado”](#se-o-acesso-for-desativado) Se o acesso do Connect for desativado na Zoppy (por exemplo, ao revogar os tokens da loja), a próxima chamada de um conector é recusada pela Zoppy e: 1. a execução falha; 2. **todos os conectores da loja pausam**, com o motivo “acesso à Zoppy revogado”; 3. os administradores da loja recebem um aviso. Para voltar, um administrador abre o Connect: ele gera um acesso novo e mostra a prévia para **religar** os conectores que pausaram por esse motivo. Confirme e eles voltam ao cron. # Comandos do agente > Referência dos comandos e opções do zoppy-agent: pair, status, allow, service, update e os outros. Gerada da ajuda do próprio binário (Zoppy-crm/zoppy-connect-agent\@c7f6ec9495ee). Agente local do Zoppy Connect: abre uma conexão de saída até a Zoppy e executa, dentro da sua rede, só o que a lista local libera. Opções que valem para todos os comandos: | Opção | Tipo | Descrição | | ------------ | ------ | ----------------------------------------------------------------------- | | `--config` | string | arquivo de configuração (padrão do sistema ou ZOPPY_AGENT_CONFIG) | | `--data-dir` | string | pasta de dados com a credencial (padrão do sistema ou ZOPPY_AGENT_DATA) | Rode como administrador (Windows) ou root (Linux) os comandos que mudam a máquina: `pair`, `unpair`, `allow add` e `allow remove`, `service`, `update`. ## `zoppy-agent allow` [Seção intitulada “zoppy-agent allow”](#zoppy-agent-allow) Lista local do que o agente pode alcançar. Tudo que não estiver aqui é recusado, mesmo que o pedido venha da Zoppy. A lista fica só nesta máquina; a Zoppy não consegue mudá-la. O serviço relê o arquivo sozinho a cada alteração. ## `zoppy-agent allow add` [Seção intitulada “zoppy-agent allow add”](#zoppy-agent-allow-add) Libera um host (http) ou banco (sql) ## `zoppy-agent allow add http` [Seção intitulada “zoppy-agent allow add http”](#zoppy-agent-allow-add-http) Libera uma API interna (ex.: ) ```sh zoppy-agent allow add http [url] [flags] ``` Exemplos: ```sh zoppy-agent allow add http http://protheus.matriz:8080/rest zoppy-agent allow add http https://sankhya.local/mge --method GET,POST zoppy-agent allow add http --host 10.0.0.0/24 --port 8080 ``` | Opção | Tipo | Descrição | | ------------------------ | ------- | ---------------------------------------------------------------- | | `--ca-file` | string | arquivo PEM da CA interna que assinou o certificado do host | | `--host` | string | host, IP, \*.dominio ou CIDR (10.0.0.0/24) | | `--insecure-skip-verify` | bool | aceita qualquer certificado deste host (evite; prefira –ca-file) | | `--method` | strings | métodos liberados (padrão: GET,HEAD) | | `--path` | string | só caminhos que começam com este (ex.: /rest) | | `--port` | ints | portas (padrão: a do esquema) | | `--scheme` | strings | http, https (padrão: os dois) | ## `zoppy-agent allow add sql` [Seção intitulada “zoppy-agent allow add sql”](#zoppy-agent-allow-add-sql) Libera leitura num banco (SQL Server, Oracle, Firebird, MySQL ou Postgres) ```sh zoppy-agent allow add sql [flags] ``` Exemplos: ```sh zoppy-agent allow add sql --engine sqlserver --host SRVSQL01 --database PROTHEUS zoppy-agent allow add sql --engine oracle --host 10.0.0.20 --port 1521 --database ORCL zoppy-agent allow add sql --engine firebird --host srv-erp --database 'C:\ERP\DADOS.FDB' zoppy-agent allow add sql --engine mysql --host 10.0.0.30 --database erp --trust-server-certificate zoppy-agent allow add sql --engine postgres --host srv-pg --database erp ``` | Opção | Tipo | Descrição | | ---------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------- | | `--ca-file` | string | SQL Server, MySQL, Postgres: arquivo PEM da CA interna que assinou o certificado do servidor | | `--database` | stringArray | banco (SQL Server, MySQL, Postgres), service name (Oracle) ou caminho/alias (Firebird); repita para vários | | `--encrypt` | string | disable: conexão sem criptografia (evite) | | `--engine` | string | sqlserver, oracle, firebird, mysql ou postgres | | `--host` | string | host, IP, \*.dominio ou CIDR | | `--legacy-auth` | bool | Firebird: aceita Legacy_Auth (só servidores anteriores ao Firebird 3) | | `--port` | int | porta (padrão: 1433, 1521, 3050, 3306 ou 5432) | | `--trust-server-certificate` | bool | SQL Server, MySQL, Postgres: aceita qualquer certificado do servidor (evite; prefira –ca-file) | ## `zoppy-agent allow check` [Seção intitulada “zoppy-agent allow check”](#zoppy-agent-allow-check) Testa se um endereço seria liberado, sem acessar a rede ## `zoppy-agent allow check http` [Seção intitulada “zoppy-agent allow check http”](#zoppy-agent-allow-check-http) Testa uma URL contra a lista ```sh zoppy-agent allow check http [flags] ``` | Opção | Tipo | Descrição | | ---------- | ------ | ---------------------- | | `--method` | string | método (default “GET”) | ## `zoppy-agent allow check sql` [Seção intitulada “zoppy-agent allow check sql”](#zoppy-agent-allow-check-sql) Testa um banco contra a lista ```sh zoppy-agent allow check sql [flags] ``` | Opção | Tipo | Descrição | | ------------ | ------ | ---------------------------------------------- | | `--database` | string | banco | | `--engine` | string | sqlserver, oracle, firebird, mysql ou postgres | | `--host` | string | host | | `--port` | int | porta | ## `zoppy-agent allow list` [Seção intitulada “zoppy-agent allow list”](#zoppy-agent-allow-list) Mostra a lista local ```sh zoppy-agent allow list [flags] ``` ## `zoppy-agent allow remove` [Seção intitulada “zoppy-agent allow remove”](#zoppy-agent-allow-remove) Tira um item da lista (veja os números em `allow list`) ## `zoppy-agent allow remove http` [Seção intitulada “zoppy-agent allow remove http”](#zoppy-agent-allow-remove-http) Tira o item http de número N (veja `allow list`) ```sh zoppy-agent allow remove http [flags] ``` | Opção | Tipo | Descrição | | -------- | ------ | ------------------------------ | | `--host` | string | tira todos os itens deste host | ## `zoppy-agent allow remove sql` [Seção intitulada “zoppy-agent allow remove sql”](#zoppy-agent-allow-remove-sql) Tira o item sql de número N (veja `allow list`) ```sh zoppy-agent allow remove sql [flags] ``` | Opção | Tipo | Descrição | | -------- | ------ | ------------------------------ | | `--host` | string | tira todos os itens deste host | ## `zoppy-agent pair` [Seção intitulada “zoppy-agent pair”](#zoppy-agent-pair) Pareia este agente com a sua empresa na Zoppy. Gere o código em Conectores > Agente local > Instalar (vale 30 minutos e uma vez só) e rode, como administrador/root: zoppy-agent pair K7QD-92XM ```sh zoppy-agent pair [flags] ``` | Opção | Tipo | Descrição | | ----------- | ------ | ------------------------------------------------------------------------------- | | `--force` | bool | parear de novo mesmo já pareado (o agente antigo deve ser revogado na tela) | | `--gateway` | string | endereço da Zoppy (padrão: ) | | `--staging` | bool | parear com o ambiente de staging () | ## `zoppy-agent rotate-key` [Seção intitulada “zoppy-agent rotate-key”](#zoppy-agent-rotate-key) Pede a troca da chave do agente na próxima conexão ```sh zoppy-agent rotate-key [flags] ``` ## `zoppy-agent run` [Seção intitulada “zoppy-agent run”](#zoppy-agent-run) Roda o agente em primeiro plano (o serviço usa este comando) ```sh zoppy-agent run [flags] ``` ## `zoppy-agent service` [Seção intitulada “zoppy-agent service”](#zoppy-agent-service) Instala e controla o serviço (Windows ou systemd) ## `zoppy-agent service install` [Seção intitulada “zoppy-agent service install”](#zoppy-agent-service-install) Instala e inicia o serviço (como administrador/root) ```sh zoppy-agent service install [flags] ``` ## `zoppy-agent service restart` [Seção intitulada “zoppy-agent service restart”](#zoppy-agent-service-restart) Reinicia o serviço ```sh zoppy-agent service restart [flags] ``` ## `zoppy-agent service start` [Seção intitulada “zoppy-agent service start”](#zoppy-agent-service-start) Inicia o serviço ```sh zoppy-agent service start [flags] ``` ## `zoppy-agent service stop` [Seção intitulada “zoppy-agent service stop”](#zoppy-agent-service-stop) Para o serviço ```sh zoppy-agent service stop [flags] ``` ## `zoppy-agent service uninstall` [Seção intitulada “zoppy-agent service uninstall”](#zoppy-agent-service-uninstall) Remove o serviço (mantém config e credencial) ```sh zoppy-agent service uninstall [flags] ``` ## `zoppy-agent status` [Seção intitulada “zoppy-agent status”](#zoppy-agent-status) Mostra conexão, versão, visto por último e a lista local ```sh zoppy-agent status [flags] ``` | Opção | Tipo | Descrição | | -------- | ---- | ------------- | | `--json` | bool | saída em JSON | ## `zoppy-agent unpair` [Seção intitulada “zoppy-agent unpair”](#zoppy-agent-unpair) Apaga a credencial deste agente (revogue também na tela da Zoppy) ```sh zoppy-agent unpair [flags] ``` ## `zoppy-agent update` [Seção intitulada “zoppy-agent update”](#zoppy-agent-update) Procura uma versão nova no manifesto de releases, confere a assinatura Ed25519 com a chave pública embutida neste binário e o hash do binário, e só então troca o executável e reinicia o serviço. Versões sem assinatura válida são recusadas. ```sh zoppy-agent update [flags] ``` | Opção | Tipo | Descrição | | --------- | ---- | ------------------------ | | `--check` | bool | só verifica, não instala | ## `zoppy-agent version` [Seção intitulada “zoppy-agent version”](#zoppy-agent-version) Mostra a versão ```sh zoppy-agent version [flags] ``` # Agente local: instalação > Instalar, parear e liberar o agente local numa máquina da loja (Windows, Linux ou Docker), criar o usuário só leitura e diagnosticar. Do README do agente (Zoppy-crm/zoppy-connect-agent\@c7f6ec9495ee). Os comandos e as opções completas estão em [Comandos do agente](/support/agent-commands/). Guia de implantação assistida e de suporte para o time da Zoppy (Enablement implanta na V1; o CS encaminha os casos). O texto fala com quem está na máquina da loja, para poder ser lido junto com o lojista na chamada. O Zoppy Agent é um programa pequeno que você instala numa máquina **dentro da rede da loja**. Ele deixa a Zoppy ler, só nos sistemas que você liberar, dados do ERP (API interna do TOTVS Protheus, Sankhya e afins, ou leitura direta do banco **SQL Server, Oracle, Firebird, MySQL ou Postgres**) **sem abrir nenhuma porta para a internet e sem VPN**. ## Roteiro da implantação assistida [Seção intitulada “Roteiro da implantação assistida”](#roteiro-da-implantação-assistida) 1. **Antes da chamada**: confirme com a loja a máquina (sempre ligada, enxerga o ERP), o motor e o nome do banco do ERP, e quem tem senha de administrador do banco para criar o usuário só leitura. 2. **Usuário só leitura**: o DBA da loja cria o usuário com os comandos de [Usuário só leitura](#usu%C3%A1rio-s%C3%B3-leitura-no-banco). 3. **Código**: um MASTER ou ADMIN da loja gera o código na tela (**Conectores › Agente local › Instalar**). 4. **Instalação e pareamento**: [Windows](#windows-msi), [Linux](#linux-deb--rpm) ou [Docker](#docker-linux-nas-quem-j%C3%A1-usa-container). Em staging, pareie com `zoppy-agent pair --staging `. 5. **Lista local**: libere só o banco/API que o conector vai ler ([Lista local](#lista-local-o-que-o-agente-alcan%C3%A7a)) e teste com `zoppy-agent allow check`. 6. **Conferência**: `zoppy-agent status` mostra **conectado**, e o agente aparece como conectado na tela. 7. **Conector**: no chat do conector, o usuário e a senha do banco são informados como segredos do conector (nunca ficam no agente). Se algo der errado, vá para [Diagnóstico](#diagn%C3%B3stico-agente-fora-do-ar). ```plaintext Internet │ Rede da loja (atrás do firewall) │ ┌──────────┐ conexão de SAÍDA │ ┌─────────────┐ ┌──────────────────┐ │ Zoppy │ ◄═══════════════════╪═══│ Zoppy Agent │ ───► │ Protheus (REST) │ │ Connect │ 443, cifrada │ │ │ ───► │ SQL Server (ERP) │ └──────────┘ │ └─────────────┘ └──────────────────┘ │ lista local: só o que você liberou ``` 1. O agente abre **uma conexão de saída** (HTTPS/WebSocket na porta 443) até a Zoppy e a mantém aberta. 2. A Zoppy manda os pedidos por essa conexão; o agente executa dentro da rede e devolve a resposta. 3. Antes de qualquer acesso, o agente confere a **lista local**. O que não estiver nela é recusado **pelo próprio agente**, mesmo que o pedido venha da Zoppy. A lista fica só nesta máquina: a Zoppy não consegue mudá-la. ## Antes de instalar [Seção intitulada “Antes de instalar”](#antes-de-instalar) * **Onde instalar:** numa máquina **sempre ligada** que enxerga o ERP (o próprio servidor do ERP ou do banco serve bem). Notebook não. Se a máquina desligar, os conectores dela param, os administradores são avisados e a próxima execução traz o que faltou. * **Sistemas:** Windows Server 2016 ou mais novo / Windows 10 ou mais novo (64 bits), Linux 64 bits (x86_64 ou ARM64) com systemd, ou qualquer máquina com Docker. Mac não é suportado. * **Rede:** saída para `agent-connect.zoppy.com.br` (a conexão com a Zoppy) e `downloads-agent-connect.zoppy.com.br` (as atualizações) na porta **443**. Nenhuma porta de entrada. Se a sua rede usa proxy, veja [Proxy](#proxy). * **Banco de dados:** crie um **usuário só de leitura** para a Zoppy (exemplos em [Usuário só leitura](#usu%C3%A1rio-s%C3%B3-leitura-no-banco)). É pré-requisito: o agente também bloqueia escrita por conta própria, mas a primeira proteção é o usuário não ter permissão. * **Código de pareamento:** na tela da Zoppy, em **Conectores › Agente local › Instalar**, um MASTER ou ADMIN clica em **Gerar código** (vale 30 minutos e uma vez só; tentativas erradas demais do mesmo IP são bloqueadas por um tempo). * **Ambiente:** o padrão é produção (`agent-connect.zoppy.com.br`). Para o staging, pareie com `--staging` (`agent-connect-staging.zoppy.com.br`). ## Instalação [Seção intitulada “Instalação”](#instalação) ### Windows (.msi) [Seção intitulada “Windows (.msi)”](#windows-msi) 1. Baixe o `zoppy-agent.msi` pela tela da Zoppy (ou em ) e execute como administrador. O instalador não tem assinatura de código: se o Windows SmartScreen avisar, clique em **Mais informações › Executar assim mesmo**. Ele instala o serviço **Zoppy Agent** (início automático) em `C:\Program Files\Zoppy Agent`. 2. Abra o **Prompt de Comando como administrador** e pareie: ```bat zoppy-agent pair K7QD-92XM ``` Instalação silenciosa já pareando (para quem distribui por GPO/SCCM): ```bat msiexec /i zoppy-agent.msi /qn PAIRCODE=K7QD-92XM ``` 3. Libere o que o agente pode alcançar (veja [Lista local](#lista-local-o-que-o-agente-alcan%C3%A7a)). Arquivos no Windows: configuração em `C:\ProgramData\ZoppyAgent\config.yaml`; credencial e logs em `C:\ProgramData\ZoppyAgent\data` (só SYSTEM, Administradores e a conta do serviço leem). Se essas pastas já existiam com outro dono antes da instalação, o agente não inicia e avisa no log: reinstale ou apague a pasta. ### Linux (.deb / .rpm) [Seção intitulada “Linux (.deb / .rpm)”](#linux-deb--rpm) ```sh # Debian/Ubuntu (em ARM64: zoppy-agent_arm64.deb) curl -fLO https://downloads-agent-connect.zoppy.com.br/agent/stable/zoppy-agent_amd64.deb sudo apt install ./zoppy-agent_amd64.deb # RHEL/Rocky/Alma/Oracle Linux (em ARM64: zoppy-agent.aarch64.rpm) sudo dnf install https://downloads-agent-connect.zoppy.com.br/agent/stable/zoppy-agent.x86_64.rpm sudo zoppy-agent pair K7QD-92XM ``` O pacote cria o usuário de sistema `zoppy-agent`, instala o serviço `zoppy-agent.service` (systemd, isolado: sem acesso a `/home`, sistema de arquivos só leitura) e o timer de atualização `zoppy-agent-update.timer`. Sem pacote (binário avulso): ```sh curl -fLO https://downloads-agent-connect.zoppy.com.br/agent/stable/zoppy-agent-linux-amd64 # em ARM64: zoppy-agent-linux-arm64 sudo install -m 0755 zoppy-agent-linux-amd64 /usr/local/bin/zoppy-agent sudo zoppy-agent service install sudo zoppy-agent pair K7QD-92XM ``` Arquivos no Linux: configuração em `/etc/zoppy-agent/config.yaml`; credencial em `/var/lib/zoppy-agent` (só o usuário `zoppy-agent` lê); logs no journal (`journalctl -u zoppy-agent`). ### Docker (Linux, NAS, quem já usa container) [Seção intitulada “Docker (Linux, NAS, quem já usa container)”](#docker-linux-nas-quem-já-usa-container) ```sh docker run -d --name zoppy-agent --restart unless-stopped \ -e ZOPPY_PAIR_CODE=K7QD-92XM \ -v zoppy-agent-config:/etc/zoppy-agent \ -v zoppy-agent-data:/var/lib/zoppy-agent \ public.ecr.aws/zoppy/zoppy-agent:stable docker exec -u 0 zoppy-agent zoppy-agent allow add sql --engine sqlserver --host SRVSQL01 --database PROTHEUS docker exec zoppy-agent zoppy-agent status ``` A lista local (`/etc/zoppy-agent`) pertence ao root dentro do container: o agente só lê, e quem muda a lista é o `docker exec -u 0`. O código de pareamento só é usado na primeira vez; depois a credencial fica no volume `zoppy-agent-data`. Se o container não resolver os nomes da rede interna, use `--network host` ou `--dns `. No Docker, a atualização é pela imagem (`docker pull` e recriar o container). ### Downloads [Seção intitulada “Downloads”](#downloads) Sempre a versão mais nova em `https://downloads-agent-connect.zoppy.com.br/agent/stable/`: `zoppy-agent.msi`, `zoppy-agent_amd64.deb`, `zoppy-agent_arm64.deb`, `zoppy-agent.x86_64.rpm`, `zoppy-agent.aarch64.rpm`, os binários `zoppy-agent-windows-amd64.exe`, `zoppy-agent-linux-amd64` e `zoppy-agent-linux-arm64`, e o `SHA256SUMS` para conferir (`sha256sum -c --ignore-missing SHA256SUMS`). Uma versão específica fica em `https://downloads-agent-connect.zoppy.com.br/agent//` (ex.: `.../agent/1.0.0/zoppy-agent.msi`) e, no Docker, em `public.ecr.aws/zoppy/zoppy-agent:`. ## Lista local (o que o agente alcança) [Seção intitulada “Lista local (o que o agente alcança)”](#lista-local-o-que-o-agente-alcança) Sem lista, o agente recusa tudo. Libere só o necessário. Os comandos gravam no arquivo de configuração e o serviço relê sozinho (não precisa reiniciar). Rode como administrador/root. ```sh # API interna (Protheus REST): só GET e HEAD, só caminhos /rest... zoppy-agent allow add http http://protheus.matriz:8080/rest # API que usa POST para consultar (Sankhya): libere o POST explicitamente zoppy-agent allow add http https://sankhya.matriz/mge --method GET,POST # Banco SQL Server (leitura) zoppy-agent allow add sql --engine sqlserver --host SRVSQL01 --database PROTHEUS # Oracle (database = service name) e Firebird (database = caminho ou alias) zoppy-agent allow add sql --engine oracle --host 10.0.0.20 --port 1521 --database ORCL zoppy-agent allow add sql --engine firebird --host srv-erp --database "C:\ERP\DADOS.FDB" # MySQL/MariaDB e Postgres (database = nome do banco) zoppy-agent allow add sql --engine mysql --host 10.0.0.30 --database erp --trust-server-certificate zoppy-agent allow add sql --engine postgres --host srv-pg --database erp zoppy-agent allow list # mostra a lista numerada zoppy-agent allow remove http 1 # tira o item http 1 zoppy-agent allow check http http://protheus.matriz:8080/rest/pedidos # testa sem acessar a rede ``` Regras da lista: * **Host**: nome (`protheus.matriz`), IP (`10.0.0.5`), curinga só no começo (`*.empresa.local`) ou faixa (`10.0.0.0/24`). Um nome liberado não libera o IP dele, e vice-versa; com faixa ou IP, o agente resolve o nome e só conecta se o endereço cair na faixa. * **HTTP**: esquema, host, porta e (opcional) prefixo de caminho. **Métodos: só GET e HEAD por padrão**; POST, PUT, PATCH e DELETE só se você listar. O agente não segue redirecionamentos e não usa proxy nas chamadas internas. * **SQL**: engine, host, porta (padrão 1433, 1521, 3050, 3306 ou 5432) e os bancos. Só `SELECT` (ou `WITH ... SELECT`), um comando por vez, dentro de transação só leitura (Oracle, Firebird, MySQL e Postgres, e sempre desfeita no fim) ou sempre desfeita (SQL Server). Qualquer outro comando é recusado, e também: dicas de bloqueio (`TABLOCKX`, `HOLDLOCK`, `UPDLOCK`…), sequências e geradores (`NEXT VALUE FOR`, `NEXTVAL`, `GEN_ID`), nomes com três ou quatro partes no SQL Server (`OUTRO.dbo.TABELA`, servidor vinculado: o banco liberado é o único lido), database links no Oracle (`tabela@link`), pacotes `DBMS_`/`UTL_` e `HTTPURITYPE`, no MySQL `INTO OUTFILE`/`DUMPFILE`, `LOAD_FILE`, `GET_LOCK`, comentários executáveis (`/*! */`) e tabela de outro banco (`outro.tabela`; só `information_schema` é aceito), no Postgres `COPY`, `pg_read_file` e afins, `lo_*`, `dblink`, `nextval`/`setval`, `set_config`, `query_to_xml` e nomes com três partes (`outro.public.tabela`), bloqueios `FOR SHARE`/`FOR KEY SHARE`, e caracteres de controle. No MySQL, escreva aspas dentro de texto dobradas (`'it''s'`), não com barra invertida. Um valor de coluna maior que o limite do pedido encerra a resposta como truncada; uma linha acima de 512 KiB é recusada (`too_large`). * **Conexão cifrada com o banco (padrão)**: no SQL Server, MySQL e Postgres o agente exige TLS e confere o certificado do servidor pelo nome do host; no Firebird exige criptografia do protocolo e só aceita autenticação SRP. Sem isso, alguém na rede entre o agente e o banco lê a senha (no SQL Server ela vai só embaralhada) e as linhas, e pode alterar as respostas. Exceções, por servidor, só se precisar: * `--ca-file C:\certs\ca-interna.pem` (SQL Server, MySQL ou Postgres com certificado de CA interna: **preferível**); * `--trust-server-certificate` (certificado autoassinado, o padrão do MySQL 8: aceita qualquer certificado, inclusive o de quem estiver no meio); * `--encrypt disable` (sem criptografia: no SQL Server só o login vai cifrado, sem conferir o certificado; no Firebird, MySQL e Postgres aceita conexão em texto claro, comum em Postgres instalado sem SSL); * `--legacy-auth` (Firebird anterior ao 3, que só tem Legacy_Auth). O `allow add` e o `allow list` mostram quando uma exceção está ligada. * **HTTPS com certificado interno**: `--ca-file C:\certs\ca-interna.pem` (preferível) ou `--insecure-skip-verify` (aceita qualquer certificado daquele host). No Linux, guarde a CA em `/etc/zoppy-agent/`. O arquivo é YAML e pode ser editado à mão (exemplo em `packaging/config.example.yaml`). Se ele ficar inválido, o agente **recusa tudo** até ser corrigido e avisa no log e no `status`. ## Ver se está funcionando [Seção intitulada “Ver se está funcionando”](#ver-se-está-funcionando) ```sh zoppy-agent status ``` ```plaintext Agente local do Zoppy Connect Versão: 1.0.0 (contrato v1) Serviço: active Agente: SRV-MATRIZ · empresa Acme Conexão: conectado Conectado desde: 31/10/2026 14:02:10 Visto por último: há 4 s Lista local (/etc/zoppy-agent/config.yaml): http 1 http protheus.matriz porta 8080 caminho /rest · métodos GET, HEAD sql 1 sqlserver SRVSQL01:1433 · bancos PROTHEUS ``` `zoppy-agent status --json` dá a mesma informação para monitoramento. Na tela da Zoppy, cada agente aparece com estado, versão, visto por último, o que alcança e os conectores que o usam. Logs: Linux `journalctl -u zoppy-agent`; Windows `C:\ProgramData\ZoppyAgent\data\logs\agent.log`; Docker `docker logs zoppy-agent`. Os logs registram pedido, destino, duração e resultado; **nunca** senhas, texto das consultas ou linhas de dados. ## Atualização [Seção intitulada “Atualização”](#atualização) O agente só instala versões **assinadas digitalmente pela Zoppy**: ele confere a assinatura do manifesto de versões com a chave pública embutida no próprio programa e o hash do arquivo baixado (e de novo o da cópia instalada). Versão sem assinatura válida, manifesto vencido ou de outro canal é recusado. O download fica numa pasta só do root/SYSTEM (`/var/lib/zoppy-agent-update`, `C:\ProgramData\ZoppyAgent\data-update`), fora do alcance da conta do serviço. * Windows: a tarefa agendada **Zoppy Agent Update** (como SYSTEM) verifica a cada 6 h e reinicia o serviço na versão nova. * Linux: o timer `zoppy-agent-update.timer` verifica a cada \~6 h. * Manual: `zoppy-agent update --check` (só verifica) ou `zoppy-agent update`. * Desligar a atualização automática: no `config.yaml`, `updates: { disabled: true }`. ## Proxy [Seção intitulada “Proxy”](#proxy) Se a saída para a internet passa por proxy, defina `HTTPS_PROXY` para o serviço ou, mais simples, no `config.yaml`: ```yaml proxy: http://proxy.empresa.local:3128 ``` O proxy vale só para a conexão com a Zoppy; as chamadas internas nunca passam por ele. ## Usuário só leitura no banco [Seção intitulada “Usuário só leitura no banco”](#usuário-só-leitura-no-banco) SQL Server: ```sql CREATE LOGIN zoppy_leitura WITH PASSWORD = ''; USE PROTHEUS; CREATE USER zoppy_leitura FOR LOGIN zoppy_leitura; ALTER ROLE db_datareader ADD MEMBER zoppy_leitura; ``` Oracle: ```sql CREATE USER zoppy_leitura IDENTIFIED BY ""; GRANT CREATE SESSION TO zoppy_leitura; GRANT SELECT ON erp.pedidos TO zoppy_leitura; -- uma linha por tabela liberada ``` Firebird: ```sql CREATE USER zoppy_leitura PASSWORD ''; GRANT SELECT ON PEDIDOS TO zoppy_leitura; -- uma linha por tabela liberada ``` MySQL / MariaDB: ```sql CREATE USER 'zoppy_leitura'@'%' IDENTIFIED BY ''; GRANT SELECT ON erp.* TO 'zoppy_leitura'@'%'; -- só o banco do ERP ``` Postgres: ```sql CREATE USER zoppy_leitura PASSWORD ''; GRANT CONNECT ON DATABASE erp TO zoppy_leitura; GRANT USAGE ON SCHEMA public TO zoppy_leitura; GRANT SELECT ON ALL TABLES IN SCHEMA public TO zoppy_leitura; ALTER USER zoppy_leitura SET default_transaction_read_only = on; ``` Parâmetros nas consultas: cada motor usa o seu marcador (`@p1` no SQL Server, `:1` no Oracle, `?` no Firebird e no MySQL, `$1` no Postgres). O usuário e a senha são informados na tela da Zoppy como segredos do conector. O agente recebe a senha só na hora da consulta e nunca a grava em disco. ## Diagnóstico: agente fora do ar [Seção intitulada “Diagnóstico: agente fora do ar”](#diagnóstico-agente-fora-do-ar) Quando o agente não responde, a chamada do conector espera até 15 s e falha com `connect.agent_offline`; a execução falha e a próxima retoma de onde parou. Sem sinal por mais de 5 minutos, os administradores da empresa recebem um aviso, que se resolve sozinho quando o agente volta. Roteiro: 1. `zoppy-agent status` na máquina: o estado e o **Motivo** dizem o que houve. * **conectado**: o problema não é o agente; veja a lista local e o banco. * **revogado na tela da Zoppy**: gere um código novo e rode `zoppy-agent pair --force `. * **credencial recusada**: idem (a credencial foi trocada ou a máquina foi clonada). * **o serviço parece parado**: `zoppy-agent service start` (Windows: serviço **Zoppy Agent**; Linux: `systemctl start zoppy-agent`). * **sem conexão / reconectando**: a máquina não sai na 443 até `agent-connect.zoppy.com.br`. Teste com `curl -I https://agent-connect.zoppy.com.br` e veja [Proxy](#proxy). 2. Máquina desligada ou em suspensão: ligue e desative a suspensão (a máquina precisa ficar sempre ligada). 3. Recusas na consulta: `not_allowed` = falta liberar na lista local (`zoppy-agent allow check sql ...`); `not_read_only` = a consulta não é um `SELECT` único aceito; `auth_failed` = usuário/senha do banco nos segredos do conector; `connect_failed` = banco fora do ar, porta errada ou certificado (veja as exceções de conexão cifrada). 4. Logs (abaixo de [Ver se está funcionando](#ver-se-est%C3%A1-funcionando)) mostram cada pedido, sem senha nem dados. ## Desligar ou remover [Seção intitulada “Desligar ou remover”](#desligar-ou-remover) * **Cortar o acesso na hora:** na tela da Zoppy, **Revogar** o agente. A conexão cai imediatamente e o agente para de tentar. * Parar o serviço: `zoppy-agent service stop` (como administrador/root). * Apagar a credencial desta máquina: `zoppy-agent unpair`. * Desinstalar: Windows pelo Painel de Controle; Linux `sudo apt remove zoppy-agent` / `sudo dnf remove zoppy-agent`; Docker `docker rm -f zoppy-agent`. Configuração e credencial ficam em disco; apague as pastas se quiser. * Trocar a chave do agente (opcional; ele já troca sozinho a cada 90 dias): `zoppy-agent rotate-key`. ## Dúvidas comuns [Seção intitulada “Dúvidas comuns”](#dúvidas-comuns) **Preciso abrir porta no firewall ou ter VPN?** Não. A conexão é só de saída, na 443, como um navegador. **O que a Zoppy vê da minha rede?** Só as respostas dos pedidos que caem na lista local. O agente não varre a rede, não lista máquinas e recusa qualquer endereço fora da lista. A Zoppy vê a lista (para mostrar “o que alcança”), mas não consegue alterá-la. **A Zoppy pode apagar ou alterar algo no ERP?** No banco, não: o usuário é só leitura, o agente só aceita `SELECT` e roda dentro de transação só leitura ou sempre desfeita. Nas APIs, só os métodos que você liberou (por padrão, apenas GET e HEAD). **E se a máquina desligar?** Os conectores que usam este agente param, os administradores da empresa na Zoppy recebem aviso, e na volta a próxima execução traz o que faltou. **Posso instalar em mais de uma máquina?** Sim, cada uma com seu código de pareamento; cada conector escolhe o agente que usa. Não copie a pasta de dados de uma máquina para outra: cada agente tem a própria chave. **Funciona em Linux sem systemd ou em NAS?** Use a imagem Docker, ou rode `zoppy-agent run` no supervisor da máquina.