AI Connect/Como criar/Automações & contas de serviço
Automações & contas de serviço
Faça jobs de cron, webhooks, pipelines de CI e processos noturnos de lote possuírem seu próprio usuário no AI Connect SDK, conectarem sistemas sem interface com credenciais estáticas e agirem com isolamento e orçamento por job, sem navegador em lugar nenhum. Processos viram atores governados, não chaves mudas num arquivo de configuração.
A IA mais valiosa da sua empresa roda sem intervenção humana. Um job noturno que reconcilia o livro-razão, um webhook que triagem um novo lead no CRM, um passo de CI que abre uma issue num deploy que falhou. Até hoje, a infraestrutura tinha exatamente um formato para esse trabalho: uma god-key num arquivo de ambiente, sem identidade, sem medição, sem revogação, e uma revisão de segurança que termina num ganido de ombros.
O AI Connect SDK substitui esse formato pelo que nenhuma plataforma de conectividade ofereceu antes: um processo é um usuário, com o próprio external_id, conectores próprios, credenciais estáticas próprias, apoiado em milhares de conexões de IA desde o primeiro dia. Dê a cada automação a própria identidade, conecte-a uma vez e deixe-a chamar capacidades de forma contínua, isolada, medida e auditável. Cada cron job se torna um funcionário responsável, com crachá, orçamento e desligamento próprios. Essa é uma frase que você não podia escrever sobre nenhuma plataforma de integração do mercado até esta.
vinkius.user('alice_123').capabilities({ include: ['github'] })GET /apps/vk_app_xxx/users/alice_123/tools?connector=githubconst capabilities = await vinkius
.user('alice_123')
.capabilities({ include: ['github'] });CapabilitySet (6)
github__list_issues read-only
github__create_issue POST /repos/{owner}/{repo}/issues
github__list_pull_requests read-only
github__search_code read-only
...O que o modelo "processo-como-usuário" proporciona
| Propriedade | Por que importa para jobs sem intervenção |
|---|---|
| Headless por design | Conectores de token estático (api_key, token) não exigem consentimento interativo, credentials.set() é o fluxo inteiro. |
| Nenhum navegador | Um contêiner de cron com apenas fetch e a sua Application Key executa o SDK por completo. |
| Raio de impacto por job | A conexão própria de cada automação significa que uma chave exposta compromete um job, não o parque todo. |
| Medição independente | Tokens por conexão mostram exatamente quanto custa o job noturno de reconciliação. |
| Repetições determinísticas | Uma chave de idempotência garante que uma chamada repetida seja aplicada exatamente uma vez. |
Estes jobs guardam credenciais reais sem humano no loop. Mantenha todo trecho no servidor, obtenha tokens estáticos do seu secret manager (nunca de um repositório ou prompt de modelo) e dê a cada automação o menor conjunto de conectores com que ela consiga funcionar.
1. Uma automação, um id, um conjunto de conectores
Nomeie o job pelo que ele faz e trate esse nome como o ator dono das conexões.
const JOB = 'svc-nightly-reconcile'; // estável, seguro para URL, menos de 255 chars, sem / \ nem espaços
const JOB_CONNECTORS = ['netsuite', 'stripe', 'sheets'];2. Provisone uma conta de serviço (uma vez, na configuração)
Isto roda uma única vez no onboarding, um operador ou um script de bootstrap fornece os tokens estáticos. Depois, o job apenas usa a conexão.
// scripts/bootstrap-reconcile.ts
import { Vinkius } from '@vinkius/connect';
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!, // do secret manager
});
async function provision(jobId: string, tokens: Record<string, Record<string, string>>) {
const user = vinkius.user(jobId);
await user.ensure({ kind: 'service-account', job: 'nightly-reconcile' });
for (const [slug, values] of Object.entries(tokens)) {
const connector = user.connector(slug);
await connector.connect(); // get-or-create, idempotente
await connector.credentials.set(values); // write-only; ex. { API_KEY: … }
}
return Promise.all(
Object.keys(tokens).map(async (slug) => ({
slug,
status: await user.connector(slug).status(), // espere "ready"
})),
);
}
await provision('svc-nightly-reconcile', {
netsuite: await secrets.read('netsuite.reconcile'),
stripe: await secrets.read('stripe.reconcile'),
sheets: await secrets.read('sheets.reconcile'),
});credentials.set() jamais retorna os valores armazenados, e o status() só informa quais chaves estão configuradas. Um processo pode verificar que seus conectores estão prontos sem jamais conseguir exfiltrar o que recebeu, a credencial é usável, não legível.
3. O job sem intervenção em si
O processo agendado não precisa de navegador, de consentimento, nem de usuário presente. Ele carrega as próprias capacidades e age.
// jobs/nightly-reconcile.ts
import { Vinkius } from '@vinkius/connect';
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
});
async function run() {
const job = vinkius.user('svc-nightly-reconcile');
const capabilities = await job.capabilities({ include: ['netsuite', 'stripe'] });
const fetchOpen = capabilities.findCapability('stripe__list_invoices');
const postEntry = capabilities.findCapability('netsuite__create_journal_entry');
if (!fetchOpen || !postEntry) {
await alertOps('reconcile: uma capacidade está indisponível, a credencial expirou?');
return;
}
const invoices = await fetchOpen.execute({ status: 'open', limit: 200 });
if (invoices.isError) throw new Error('list do stripe falhou: ' + invoices.content[0]?.text);
const entry = await postEntry.execute(
{ lines: toJournalLines(invoices) },
{ idempotencyKey: `reconcile:${runDate()}` }, // uma execução lógica = um lançamento
);
if (entry.isError) await alertOps('lançamento do reconcile rejeitado: ' + entry.content[0]?.text);
}
run().catch(async (error) => {
await alertOps(`reconcile quebrou: ${error.message}`);
});O idempotencyKey é o recurso de automação. Uma repetição, um disparo duplicado de cron, um redeploy no meio da execução, nenhum deles registra duas vezes o lançamento contábil, porque o servidor deduplica replays com a mesma chave. Derive a chave do evento de negócio (a data da execução, o id do ticket, o id da entrega do webhook), jamais de Date.now().
4. Webhooks: um ator, uma chave por entrega
Para automações orientadas a evento, normalmente você mantém um único usuário de conta de serviço, mas toda chamada mutável usa o id da entrega como chave, para que um webhook repetido seja aplicado exatamente uma vez.
// POST /webhooks/lead (verificado)
async function handleLeadWebhook(payload: { id: string; email: string }) {
const user = vinkius.user('svc-lead-intake');
if ((await user.connector('hubspot').status()) !== 'ready') {
await alertOps('CRM do lead-intake não está pronto');
return;
}
const caps = await user.capabilities({ include: ['hubspot'] });
await caps.findCapability('hubspot__create_contact')?.execute(
{ email: payload.email },
{ idempotencyKey: `lead:${payload.id}` }, // entrega repetida -> sem duplicata
);
}5. Pipelines de CI e executores de uso único
Um job de CI se autentica exatamente como um cron: a mesma Application Key, o próprio external_id, credenciais estáticas provisionadas no ambiente. A diferença é o tempo de vida, você disconnect() executores efêmeros quando o pipeline é desmontado.
async function openIssueOnFailedDeploy(runId: string, repo: string) {
const caps = await vinkius.user('ci-deploy-bot').capabilities({ include: ['github'] });
await caps.findCapability('github__create_issue')?.execute(
{ owner: 'acme', repo, title: `Deploy ${runId} falhou` },
{ idempotencyKey: `deploy:${runId}` },
);
}6. Rotacione e aposente de forma limpa
Como tudo depende de um único external_id, desativar uma automação é determinístico.
async function decommission(jobId: string) {
const user = vinkius.user(jobId);
for (const conn of await user.connectors()) {
await user.connector(conn.slug).disconnect();
}
}Para rotacionar uma credencial, chame credentials.set() novamente com o novo valor, a conexão permanece a mesma e nenhuma referência de capacidade quebra.
Confiabilidade e saída estruturada, para processos sem supervisão
Um processo sem intervenção é exatamente onde a robustez embutida no SDK se destaca. Três garantias são herdadas automaticamente:
Repetição automática com proteção contra avalanche. Leituras idempotentes (e qualquer escrita que transporte um idempotencyKey) são repetidas automaticamente: somente para os transitórios 429/502/503/504 e erros de rede, com backoff de jitter completo que respeita o cabeçalho Retry-After do servidor. Seu cron não precisa de um loop de repetição próprio.
Idempotência é o mecanismo de segurança. Para um job de lote, falhar e ser reexecutado é normal. Um idempotencyKey estável elimina a dúvida "a reexecução duplicou o efeito?", porque o servidor deduplica as replays daquela chave. Ao fornecer a chave a uma chamada mutável, o SDK passa a repetir inclusive um POST não-idempotente com segurança.
Consuma resultados como objetos, não como texto. Quando o conector devolve dados estruturados, o result.structuredContent os entrega já interpretados, sem extração frágil de texto no pipeline:
const invoices = await fetchOpen.execute({ status: 'open', limit: 200 });
const list = invoices.structuredContent as { invoices: Array<{ id: string; amount: number }> };
const total = list.invoices.reduce((sum, i) => sum + i.amount, 0);Associe a execução longa de um lote a um timeoutMs por chamada adequado e a um signal abortado no encerramento do processo, e o job conclui de forma previsível durante um deploy progressivo.
Checklist de produção
- [ ] Dê a cada automação um
external_iddistinto e legível (sem uma única conta ampla compartilhada). - [ ] Obtenha todo token estático do seu secret manager; nunca o versione nem o coloque em prompt.
- [ ] Provisone uma vez na configuração; os runtimes só leem
status(), nunca rearmazenam segredos. - [ ] Defina um
idempotencyKeyestável derivado do evento de negócio em todo job mutável. - [ ] Alerte quando
status() !== 'ready'e emisError, para que uma falha silenciosa não se torne invisível. - [ ]
disconnect()contas de serviço efêmeras como parte do teardown.
Você agora tem automações que possuem a própria identidade, conectam sistemas reais sem interface e agem em agenda ou por evento, sem a mecânica de OAuth e sem a divergência de "quem é o dono deste token?" causada por uma conta ampla compartilhada. O seu parque sem supervisão saiu de ser a parte do sistema que as auditorias reclamam para ser a parte com a melhor história de governança, porque cada processo dele finalmente tem um nome.
What you just got
Not a pitch: the properties this build inherits automatically.
Connections and capabilities resolve only inside one external_id. No cross-actor leakage is possible, and you wrote none of that enforcement.
Your server stores secrets and can read back which fields are configured, never the values. Not your code, the model, or a dashboard can exfiltrate them.
Every connection owns a vk_live_* token, so cost and revocation are per connection. One call to disconnect() is a complete, auditable stop.
One CapabilitySet converts to OpenAI, Anthropic, Gemini, Vercel AI SDK, LangChain, LlamaIndex, Workers AI or neutral JSON Schema. Only the last line changes.
idempotencyKey, timeoutMs and AbortSignal per call; automatic full-jitter retries on transient failures; typed VinkiusError branches. No bespoke harness.
A cron container with only fetch and your key runs it. An idempotencyKey from the business event makes a re-run a non-event.
Give it to your AI agent
An Agent Skill (SKILL.md) for this build. Preview the first lines below, then copy or download it into your repo under .claude/skills/: Claude Code, Cursor or any Agent-Skills-compatible agent follows it to implement this pattern correctly.
