#Conecte seu agente de IA ao seu backend

Para um agente de IA trabalhar com dados reais, você dá sua API a ele como tools: cada tool é uma requisição HTTP descrita em um arquivo YAML, com os parâmetros que o modelo preenche e os secrets de que a requisição precisa. Com a Vatio você lista essas tools no vatio.yml, e o mesmo agente as chama no WhatsApp, no Instagram e na web. Para os dados de um visitante logado, seu backend assina um JWT e a Vatio só verifica, então uma tool privada lê os dados dessa pessoa e de mais ninguém.

Este guia conecta de ponta a ponta a API de uma loja de bicicletas: secrets, uma tool que lê, uma tool que age, o que volta para o agente e tools privadas para clientes logados.

#Por que o agente precisa da sua API

Sem tools, um agente só consegue repetir as instruções e o conhecimento dele. Se você perguntar se tem a Trail 29 no quadro M em estoque, ele ou admite que não sabe ou chuta, e chutar um estoque, um preço ou um agendamento é a coisa mais cara que um agente pode dizer.

Conhecimento é para o que muda devagar: políticas, horários, como um serviço funciona. Tools são para o que é verdade agora (estoque, status de um pedido, horários livres) e para tudo o que faz alguma coisa, como agendar. Seu backend continua sendo a fonte da verdade e continua fazendo as validações; a Vatio opera a conversa e faz a chamada. Cada chamada aparece no inbox, ao lado da resposta que ela produziu.

#Como uma tool funciona

  1. Uma tool é um arquivo, tools/<chave>.yml; o nome do arquivo é a chave. Ela é ativada quando a chave está em agent.tools.
  2. O modelo lê description e when_to_use, decide chamar e preenche parameters, um JSON schema.
  3. A Vatio monta a requisição, substitui os placeholders e envia dos próprios servidores.
  4. respond escolhe o que o modelo vê, e o modelo escreve a resposta a partir de result: ok ou result: error.

Uma tool é uma requisição só, de propósito. Condições, uma segunda chamada ou uma resposta calculada são código: ficam no seu backend, atrás de um endpoint que faz o trabalho inteiro.

#Guarde o endereço e a chave como secrets

vatio secrets set PEDALIA_API https://api.pedalia.example
vatio secrets set PEDALIA_API_KEY

Se você omitir o valor, o set pede com a entrada oculta. As tools leem os secrets como $env.KEY, as mudanças valem a partir da próxima chamada sem deploy, e vatio secrets list mostra nomes, nunca valores.

#Uma tool que lê

tools/consultar_estoque.yml:

description: Consulta estoque e preço de uma bicicleta, peça ou acessório.
when_to_use: O cliente pergunta se algo está disponível ou quanto custa.
parameters:
  type: object
  properties:
    produto: { type: string, description: "Nome ou modelo do produto" }
  required: [produto]
access: public
request:
  method: GET
  base_url: "$env.PEDALIA_API"
  path: /produtos
  requires: [params.produto]
  headers:
    Authorization: "Bearer $env.PEDALIA_API_KEY"
  query:
    q: "$params.produto"
respond:
  data:
    produtos: "$.data"
  message:
    when_empty: Nenhum produto corresponde a esse nome.
    default: "Encontrei {{count}} produtos."

Os valores em base_url, path, headers, query e body aceitam estes placeholders:

PlaceholderOrigem
$params.<nome>Os argumentos da tool, preenchidos pelo modelo
$env.<KEY>Os secrets do workspace
$auth.subject, $auth.token, $auth.claims.<chave>O visitante verificado, em tools privadas
$contact.name, $contact.email, $contact.phone_numberO perfil do contato

#Uma tool que age

tools/agendar_revisao.yml:

description: Agenda um serviço de oficina para a bicicleta do cliente.
when_to_use: O cliente escolheu um serviço e um dia, e confirmou o nome.
parameters:
  type: object
  properties:
    servico: { type: string, description: "ex.: regulagem de freios, revisão completa" }
    dia: { type: string, description: "YYYY-MM-DD" }
    nome: { type: string }
    telefone: { type: string, description: "Só Instagram e web. No WhatsApp a tool usa o número do contato" }
  required: [servico, dia]
access: public
request:
  method: POST
  base_url: "$env.PEDALIA_API"
  path: /agendamentos-oficina
  requires: [params.servico, params.dia]
  headers:
    Authorization: "Bearer $env.PEDALIA_API_KEY"
  body:
    servico: "$params.servico"
    dia: "$params.dia"
    nome: "$params.nome || $contact.name"
    telefone: "$params.telefone || $contact.phone_number"
respond:
  data:
    agendamento: "$.agendamento"

Valores ausentes ficam fora da requisição, então liste em requires o que o endpoint não pode deixar de receber, e a tool falha antes de enviar meio agendamento. || pega o primeiro valor que não está vazio: o que o cliente acabou de dizer, ou o que o perfil do contato já tem. No WhatsApp o perfil já tem o telefone do cliente, então o agente só pede o número no Instagram e na web. Uma tool usada só no WhatsApp pode dispensar o parâmetro: envie $contact.phone_number e coloque contact.phone_number em requires, e, se alguém esconde o número atrás de um nome de usuário do WhatsApp, a Vatio pede o número com um botão de um toque.

#O que volta para o agente

Toda tool devolve result (ok ou error) e uma message que não é vazia:

{ "result": "error", "message": "O sábado está lotado. Segunda às 10:00 está livre." }

respond.data mapeia chaves de saída para caminhos JSON: $ é o corpo inteiro, $.data é o campo data. {{count}} é o tamanho do primeiro array nos dados mapeados. Sem respond.message, a mensagem é o campo message da sua própria API, então seu backend pode dizer o que aconteceu com as próprias palavras.

Devolva status HTTP de verdade. Um 2xx vira ok e qualquer outro, error. Um 409 quando o sábado lotou um segundo atrás vira um honesto “segunda às 10:00 está livre”, enquanto uma falha escondida dentro de um 200 vira a confirmação confiante de um agendamento que não existe. Uma busca vazia continua sendo ok: funcionou, e a resposta foi não. O modelo vê os resultados das tools, então nunca devolva uma credencial em um deles.

#Ative as tools no vatio.yml

workspace: pedalia
agent:
  name: Pedalia Bicicletas
  instructions: |
    Você ajuda os clientes da Pedalia Bicicletas com estoque, preços,
    agendamentos na oficina e os pedidos deles.

    Estoque e preços: chame consultar_estoque. Nunca diga um preço ou um
    estoque que consultar_estoque não tenha devolvido.

    Oficina: pergunte o serviço e o dia, depois o nome do cliente e,
    exceto no WhatsApp, o telefone. Repita o agendamento antes de chamar
    agendar_revisao. Só diga que está agendado quando agendar_revisao
    tiver devolvido.

    Pedidos: chame meus_pedidos. Se ela não conseguir confirmar quem é o
    cliente, peça que ele entre na conta em pedalia.example.
  tools: [consultar_estoque, agendar_revisao, meus_pedidos, identify_contact]
auth:
  public_key: identity.pub

As tools nativas são ativadas do mesmo jeito: knowledge_lookup, identify_contact (salva um nome que o visitante informa), request_contact_info (pede a um visitante do WhatsApp que compartilhe o número dele) e remember e recall para a memória sobre o contato. handoff é anexada quando você declara agent.handoff.

#Tools privadas: os dados do próprio visitante

consultar_estoque e agendar_revisao são públicas: rodam para qualquer pessoa. meus_pedidos é privada: só roda para um visitante cuja identidade seu backend assinou.

vatio auth --new-key

Isso grava dois arquivos, e eles vão para lugares diferentes. identity.pub fica no workspace, vai no commit e é nomeado pelo bloco auth: acima. identity.pem vai para o seu backend como segredo, por exemplo em uma variável de ambiente VATIO_IDENTITY_PRIVATE_KEY, e depois você apaga o arquivo do diretório do workspace. Nunca faça vatio secrets set da chave privada: quem lê esse armazenamento é a Vatio, e com sua chave privada ali a Vatio poderia emitir identidades em vez de só conferi-las.

Seu backend assina o token com RS256:

ClaimObrigatórioValor
subsimO id do seu usuário. Vira $auth.subject
audsimO slug do seu workspace, exato
expsimSegundos Unix. Mantenha curto: é conferido de novo a cada chamada de tool
name, email, phone_numbernãoPreenchem o perfil do contato
metadatanãoUm link profile e badges que o inbox mostra sobre a pessoa

Qualquer outra claim chega como $auth.claims.<nome>. aud é obrigatório porque uma assinatura prova quem assinou, não para quê: sem ele, um token de redefinição de senha assinado com a mesma chave passaria como identidade.

// Node (jsonwebtoken)
import jwt from "jsonwebtoken";

const visitorToken = jwt.sign(
  { sub: String(user.id), name: user.name, email: user.email },
  process.env.VATIO_IDENTITY_PRIVATE_KEY,
  { algorithm: "RS256", audience: "pedalia", expiresIn: "1h" }
);

tools/meus_pedidos.yml repassa esse token para a sua API:

description: Os pedidos recentes do cliente logado e o status de cada um.
when_to_use: Um cliente logado pergunta sobre os próprios pedidos ou entregas.
access: private
request:
  method: GET
  base_url: "$env.PEDALIA_API"
  path: /me/pedidos
  headers:
    Authorization: "Bearer $auth.token"
respond:
  data:
    pedidos: "$.pedidos"
  message:
    when_empty: Este cliente ainda não tem pedidos.
    default: "Encontrei {{count}} pedidos."

Seu endpoint verifica o JWT com a chave pública e restringe a consulta ao sub dele. Repare no que a tool não recebe: um id de cliente. Uma tool privada nunca recebe o id do usuário como parâmetro, porque quem preenche os parâmetros é o modelo, e ele pode ser convencido a preencher esse com o de outra pessoa. A Vatio recusa o deploy:

tool meus_pedidos: a private tool cannot take "user_id" as a parameter — the model
fills parameters and can be talked into filling that one with someone else's id.
Use $auth.subject, which comes from the verified token

O mesmo vale para customer_id, account_id, member_id, patient_id e subject.

#Passe o token para o widget

Coloque o token na tag do widget só para visitantes logados. Quem não está logado não recebe token, e o agente responde com as tools públicas.

<script async src="https://cdn.vatio.ai/v1/widget.js"
        data-workspace="pedalia"
        data-token="vatpub_..."
        data-visitor-token="JWT_ASSINADO"></script>

Quando alguém entra na conta sem recarregar a página, chame window.VatioWidget.identify(novoToken) (null desconecta). A conversa que a pessoa já tinha continua, agora com um nome; um token com outro sub começa uma nova, então em um computador compartilhado um cliente nunca vê o chat de outro. Gere um token novo a cada carregamento de página para que uma conversa longa sobreviva ao primeiro exp. O SDK de navegador recebe o mesmo token como visitorToken, e o SDK de iOS como VatioChat(vatio, visitorToken:).

#Se seus usuários fazem login com Supabase

Pule o par de chaves, a assinatura e o data-visitor-token. Rode:

vatio auth --supabase --sign-in-url https://pedalia.example/login

Ou clique em Conectar Supabase em Canais → Supabase, no inbox, e escolha o projeto; o comando passa a usá-lo. Ele encontra o projeto no seu .env (ou no cliente que o Lovable gera), escreve auth: supabase: https://<ref>.supabase.co no vatio.yml e guarda SUPABASE_URL e SUPABASE_ANON_KEY como secrets. A partir daí o widget lê a sessão do Supabase que sua página já tem, acompanha login, logout e renovações do token, e no WhatsApp e no Instagram o botão Entrar abre sua página de login, onde o widget entrega a sessão. Uma tool privada chama o Supabase como o visitante, então sua Row Level Security decide o que ele vê:

access: private
request:
  method: GET
  base_url: "$env.SUPABASE_URL"
  path: /rest/v1/orders
  query:
    select: "*"
  headers:
    apikey: "$env.SUPABASE_ANON_KEY"
    Authorization: "Bearer $auth.token"

O projeto precisa assinar com JWT signing keys (ES256), não com o secret legado; se não assinar, o push diz como trocar. Veja Supabase (em inglês).

#Teste

vatio diff
vatio push
vatio chat "Vocês têm a Trail 29 no quadro M?"
vatio chat "Agenda uma regulagem de freios no sábado, sou a Ana, 5555-0142"
vatio chat --new "Cadê meu pedido?"
vatio chat show

Nenhum comando invoca uma tool diretamente: as tools são testadas por conversas, e chat show mostra cada chamada, os argumentos e o que seu backend respondeu. A CLI fala como você, o developer, não como um cliente, então teste as tools privadas em uma página sua: crie um token com vatio tokens create --env preview --origin http://localhost:3000, entre na conta e leia o chat com vatio chat show CHAT_ID. Um token recusado (outra chave, expirado, aud errado) deixa o visitante anônimo em vez de falhar, e o chat show diz por quê.

Quando já há pessoas reais falando com o agente, a página Ferramentas do inbox mostra como seu backend vem respondendo: a taxa de erro de cada tool, as que não responderam a tempo, a latência p50/p95 e as chamadas que falharam com o que seu backend devolveu. Uma tool com falha quase nunca aparece na conversa, porque o agente pede desculpas e segue, então é ali que você fica sabendo.

Seu coding agent lê a mesma página pelo terminal: vatio tools --env live a imprime como Markdown que começa dizendo como trabalhá-la, ou como JSON com --json, sobre a última hora, o último dia, 7 ou 30 dias (--period 1h). Cada chamada que falhou traz o vatio chat show que abre a conversa dela, então o agente vai de “esta tool falha 4% das vezes” à requisição, à resposta e à correção no seu backend sem você copiar nada do inbox.

#Erros comuns

#Próximos passos

Se o agente ainda não existe, comece por Construir um agente de IA. Quando os clientes já usam o agente, os supervisores reportam as respostas erradas e seu coding agent corrige a causa, que muitas vezes é uma resposta de tool sem um campo: veja Melhorar seu agente. A referência completa está em Tools e Authentication.

Perguntas frequentes

Como conecto um agente de IA à minha própria API?

Descreva cada endpoint como uma tool: um arquivo YAML com uma requisição HTTP, os parâmetros que o modelo preenche e placeholders para seus secrets. Adicione a chave dela em agent.tools no vatio.yml, faça push e teste com vatio chat.

O agente consegue ler os dados de um usuário logado?

Sim, no seu site ou app, e no WhatsApp e no Instagram depois que ele entra na conta. Marque a tool com access: private e faça seu backend assinar um JWT RS256 para o visitante. A Vatio verifica com sua chave pública e repassa o token para a tool, e seu endpoint restringe a consulta ao sub do token. A Vatio nunca tem a chave privada, então consegue conferir quem a pessoa é, mas nunca afirmar isso por conta própria.

Por que uma tool privada não pode receber um parâmetro user_id?

Porque quem preenche os parâmetros é o modelo, e ele pode ser convencido a preencher esse com o id de outra pessoa. Uma tool privada que declara user_id, customer_id, account_id, member_id, patient_id ou subject faz o deploy falhar. Leia o usuário de $auth.subject ou do próprio token.

Tools privadas funcionam no WhatsApp e no Instagram?

Sim, depois que o contato entra na conta: com um sign_in_url no vatio.yml, a Vatio envia um botão Entrar que abre o seu site, sua página redireciona de volta com o mesmo token que você coloca no widget, e a conversa continua. Até lá ele é anônimo e só tools públicas rodam. Para alguém consultar o próprio caso sem entrar na conta, use uma tool pública que receba um número de referência mais um segundo dado, e faça seu backend verificar se os dois batem.

Meus usuários fazem login com Supabase. Preciso assinar um JWT?

Não. Rode vatio auth --supabase: ele nomeia seu projeto no vatio.yml, e o widget lê a sessão do Supabase que sua página já tem, sem código. No WhatsApp e no Instagram o botão Entrar abre sua página de login e o widget entrega a sessão. Uma tool privada repassa $auth.token para a API REST do Supabase, então sua Row Level Security decide o que o visitante vê.

O agente consegue chamar uma API rodando em localhost?

Não. As tools rodam nos servidores da Vatio, então localhost fica fora de alcance. Exponha seu backend local com um túnel e defina essa URL como secret só do preview (vatio secrets set KEY URL --env preview), o que muda o valor só ali enquanto o live continua chamando a produção.

Se uma tool é uma requisição só, onde fica a lógica de vários passos?

No seu backend. Tudo o que precisa de condições, de uma segunda chamada ou de uma resposta calculada é código, então coloque atrás de um endpoint que faz o trabalho inteiro e dê esse endpoint ao agente como tool.

Atualizado: