Ferramentas para agentes
Para um agente consultar pagamentos ou executar operações, dê a ele ferramentas (function calling) que rodam no seu servidor. O agente escolhe a ferramenta e os argumentos; a sua camada aplica permissões, limites e aprovação, e é a única que enxerga as chaves.
Definições das ferramentas
O formato abaixo é JSON Schema puro, aceito (com pequenas adaptações de envelope) pelas APIs de function calling de Claude, OpenAI e outros frameworks de agentes. Note que há duas de leitura e uma de escrita com controle.
[
{
"name": "sharkpay_get_invoice",
"description": "Consulta uma cobrança pela referência (INV-XXXXXX). Somente leitura.",
"input_schema": {
"type": "object",
"properties": { "reference": { "type": "string", "pattern": "^INV-[A-Za-z0-9]{6}$" } },
"required": ["reference"],
"additionalProperties": false
}
},
{
"name": "sharkpay_list_pending_invoices",
"description": "Lista cobranças da conta (até 50). Somente leitura.",
"input_schema": {
"type": "object",
"properties": {
"limit": { "type": "integer", "minimum": 1, "maximum": 50 },
"start_date": { "type": "string", "format": "date" },
"end_date": { "type": "string", "format": "date" }
},
"additionalProperties": false
}
},
{
"name": "sharkpay_request_payout",
"description": "SOLICITA um saque Pix. Não executa: cria um pedido de aprovação para um humano. Valor máximo por política.",
"input_schema": {
"type": "object",
"properties": {
"amount": { "type": "number", "exclusiveMinimum": 0 },
"pixkey": { "type": "string", "maxLength": 140 },
"keytype": { "type": "string", "enum": ["CPF", "CNPJ", "EMAIL", "PHONE", "EVP"] },
"reason": { "type": "string", "maxLength": 200 }
},
"required": ["amount", "pixkey", "keytype", "reason"],
"additionalProperties": false
}
}
]Implementação com controles (Node.js)
const BASE = process.env.SHARKPAY_BASE_URL;
let token = null, exp = 0;
async function autenticar() {
if (token && Date.now() < exp - 60_000) return token;
const basic = Buffer.from(`${process.env.SHARKPAY_PUBLIC_KEY}:${process.env.SHARKPAY_SECRET_KEY}`).toString('base64');
const r = await fetch(`${BASE}/api/auth`, { method: 'POST', headers: { Authorization: `Basic ${basic}` }, signal: AbortSignal.timeout(15_000) });
if (!r.ok) throw new Error('auth_failed');
const j = (await r.json()).success;
token = j.token; exp = Date.now() + j.expires_in * 1000;
return token;
}
async function api(path, init = {}) {
const r = await fetch(`${BASE}${path}`, {
...init,
headers: { Authorization: `Bearer ${await autenticar()}`, 'Content-Type': 'application/json', ...(init.headers || {}) },
signal: AbortSignal.timeout(30_000),
});
if (r.status === 401) { token = null; }
return r;
}
// Política: o que o agente pode fazer (menor privilégio)
const POLITICA = { valorMaximoSaque: 500.00, chavesPermitidas: new Set(process.env.PIX_ALLOWLIST?.split(',') ?? []) };
const filaAprovacao = []; // em produção: banco + tela/notificação para um humano
export const ferramentas = {
async sharkpay_get_invoice({ reference }) {
if (!/^INV-[A-Za-z0-9]{6}$/.test(reference)) return { erro: 'referencia_invalida' };
const r = await api(`/api/invoice/ref/${reference}`);
if (!r.ok) return { erro: `http_${r.status}` };
const { invoice } = await r.json();
// devolve ao modelo só o necessário (minimização de dados)
return { reference: invoice.ref_id, status: invoice.status, total: invoice.total, due_date: invoice.due_date };
},
async sharkpay_list_pending_invoices({ limit = 10, start_date, end_date }) {
const q = new URLSearchParams({ limit: String(Math.min(limit, 50)) });
if (start_date && end_date) { q.set('start_date', start_date); q.set('end_date', end_date); }
const r = await api(`/api/invoices?${q}`);
if (!r.ok) return { erro: `http_${r.status}` };
const { invoices } = await r.json();
return invoices.filter(i => i.status === 'PENDING').map(i => ({ reference: i.ref_id, total: i.total, due_date: i.due_date }));
},
async sharkpay_request_payout({ amount, pixkey, keytype, reason }) {
if (amount > POLITICA.valorMaximoSaque) return { erro: 'acima_do_limite' };
if (!POLITICA.chavesPermitidas.has(pixkey)) return { erro: 'chave_fora_da_lista_permitida' };
const pedido = { id: crypto.randomUUID(), amount, pixkey, keytype, reason, criadoEm: new Date().toISOString() };
filaAprovacao.push(pedido); // NÃO chama a API ainda
return { status: 'aguardando_aprovacao_humana', pedido: pedido.id };
},
};
// Chamado SOMENTE pela tela/endpoint de aprovação, após um humano autenticado confirmar.
export async function executarSaqueAprovado(pedidoId, aprovador) {
const p = filaAprovacao.find(x => x.id === pedidoId);
if (!p) throw new Error('pedido_inexistente');
const r = await api('/api/pixout/create', {
method: 'POST',
headers: { 'Idempotency-Key': `payout-${p.id}` }, // determinístico: reenviar não duplica
body: JSON.stringify({ amount: p.amount, pixkey: p.pixkey, keytype: p.keytype, description: 'Saque aprovado' }),
});
console.log(JSON.stringify({ evento: 'saque_aprovado', pedido: p.id, aprovador, http: r.status })); // sem segredos
return r.status;
}O que este desenho garante
- O modelo nunca vê chave, token ou cabeçalhos — só os resultados mínimos.
- Leitura e escrita são ferramentas diferentes; ofereça ao agente só as que ele precisa.
- Dinheiro só sai depois de aprovação humana, com limite, lista de chaves permitidas e Idempotency-Key determinística.
- Argumentos são validados na sua camada; trate a saída do modelo como entrada não confiável (pode haver prompt injection em dados como nome do pagador ou descrição).
- Erros voltam ao agente como códigos curtos, sem corpo bruto da API.
Frameworks de agentes
O mesmo objeto de ferramentas funciona em qualquer framework (SDKs de Claude e OpenAI, LangChain, LlamaIndex, Vercel AI SDK, etc.): registre as definições de tools.json e, ao receber uma chamada de ferramenta, execute ferramentas[nome](argumentos) e devolva o resultado. O SharkPay não exige nenhum framework específico, e este guia não depende de serviços pagos.