#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
- Uma tool é um arquivo,
tools/<chave>.yml; o nome do arquivo é a chave. Ela é ativada quando a chave está emagent.tools. - O modelo lê
descriptionewhen_to_use, decide chamar e preencheparameters, um JSON schema. - A Vatio monta a requisição, substitui os placeholders e envia dos próprios servidores.
respondescolhe o que o modelo vê, e o modelo escreve a resposta a partir deresult: okouresult: 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:
| Placeholder | Origem |
|---|---|
$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_number | O 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:
| Claim | Obrigatório | Valor |
|---|---|---|
sub | sim | O id do seu usuário. Vira $auth.subject |
aud | sim | O slug do seu workspace, exato |
exp | sim | Segundos Unix. Mantenha curto: é conferido de novo a cada chamada de tool |
name, email, phone_number | não | Preenchem o perfil do contato |
metadata | não | Um 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
- O WhatsApp e o Instagram são anônimos até o contato entrar na conta. Não há uma página onde colocar um token, então adicione
sign_in_urlemauth:e uma tool privada envia antes ao contato um botão Entrar que abre o seu site; veja a documentação. Para um cliente que não entrou na conta consultar um pedido, use uma tool pública que receba o número do pedido mais um segundo dado, como o email do pedido, e faça seu backend verificar se os dois batem. Ou mande um link para a página logada. - As tools não alcançam
localhost. Elas rodam nos servidores da Vatio. Exponha seu backend local com um túnel e defina a URL como secret só do preview:vatio secrets set PEDALIA_API https://pedalia-dev.tunnel.example --env preview. - Os evals chamam suas tools. Aponte-as para um sandbox com um valor de testes (
vatio secrets set KEY URL --tests); uma gravação cuja URL não tem um é simulada em vez de enviada, então uma repetição nunca agenda um serviço de verdade. - Um endpoint por tarefa. Se responder exige buscar o cliente, depois o pedido, depois a transportadora, escreva um endpoint que responda à pergunta. Três tools encadeadas deixam a lógica em um prompt.
- As validações ficam no código. Se um agendamento pertence a quem está pedindo, quem decide é seu backend, nunca as instruções.
#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: