This is the full 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.