Pular para o conteúdo

Automação, lotes e webhooks

API · v1 · Produção · Revisado em · Markdown

Produção: https://api.agilink.io/v1. Homologação isolada: https://api.staging.agilink.io/v1. Os 20 endpoints com requests e responses e a referência OpenAPI usam o contrato validado. OAuth será implementado na Tarefa 2; MCP, na Tarefa 3. A área de desenvolvedores gerencia credenciais, uso, operações, auditoria e webhooks.

Envie Authorization: Bearer <sessão-humana-ou-chave> em todas as chamadas. Cada operação exige a permissão indicada na referência e acesso ao app. Não há credencial em URL, download público ou wildcard de permissão. Cada mutação exige Idempotency-Key UUID v4 por intenção; preserve a chave e o corpo em respostas perdidas. Os recibos duram 24 horas. If-Match é obrigatório para commit de importação, alteração/desativação/rotação do webhook e reentrega. Use a revisão consultada imediatamente antes da operação.

As permissões de automação não substituem as permissões do recurso. Lotes exigem batches:create e links:create, links:update ou links:delete conforme a ação. Importação exige imports:create, imports:validate, imports:commit, imports:read nas respectivas etapas e links:create no preflight/commit/execução. Exportação exige exports:create e links:read ou analytics:read; download exige também exports:read e a leitura original atual. Cadastro/envio de webhook requer webhooks:create; leitura, edição, teste, rotação, retry e desativação têm permissões próprias. Habilitar/reautorizar um destino exige webhooks:create.

O executor reconstrói o grant atual e verifica sessão/chave, dono, app, estado do job e lease antes de cada efeito. Reduzir o alcance ou revogar uma chave impede os próximos efeitos. Revogação não desfaz itens já concluídos nem bytes já enviados. A origem da assinatura é o segredo do webhook, nunca o bearer da API.

POST /link-batches recebe appId e até 50 itens. Cada clientId deve ser único no lote e usar letras, dígitos, ponto, hífen ou underscore, até 100 caracteres. O limite HTTP continua 32 KiB, incluindo JSON. Um erro no envelope impede o cadastro; erros nos dados de domínio de um item ficam no resultado daquele item.

{
"appId": "app_loja",
"items": [
{"clientId":"novo-1","action":"create","input":{"appId":"app_loja","domain":"go.example.com","slug":"produto","desktopUrl":"https://shop.example.com/produto"}},
{"clientId":"arquivar-1","action":"archive","linkId":"go.example.com~-~-antigo","revision":"20000000-0000-4000-8000-000000000001","input":{"lifecycle":{"archived":true}}}
]
}
Ação Campos do item Efeito
create input com LinkCreate e mesmo app Cria pelo contrato atual de links; domínio deve estar autorizado
update linkId, revision, input LinkPatch Altera apenas campos aceitos pela API de links
move linkId, revision, input.folderId UUID ou null Vincula/desvincula pasta do mesmo app
archive linkId, revision, input.lifecycle.archived boolean Preserva os demais campos do ciclo de vida
delete linkId, revision, sem input Exclui respeitando dependências, QR, histórico e publicação

Consulte GET /operations/{id} e GET /link-batches/{id}/items?limit=50. Estados terminais: succeeded, partially_succeeded, failed, cancelled. Cada resultado tem clientId, ordinal, estado, identificador/revisão ou erro sanitizado. Falhas de revisão não são sobrescritas. Um item é confirmado na mesma transação do efeito; retomada não executa novamente itens confirmados. Uma falha transitória desfaz somente a transação atual.

Retente em um novo lote apenas os itens falhos que forem corrigidos, preservando seus clientId e consultando revisões atuais. Não reenvie itens concluídos com nova intenção: isso poderia gerar colisões. A mesma chave e corpo no endpoint original apenas repetem o recibo.

  1. POST /imports: envie appId, format (csv ou json) e content como texto dentro do JSON. Máximo 24.000 bytes UTF-8 de conteúdo, 50 linhas/objetos e 32 KiB totais. URLs de arquivo não são buscadas.
  2. Opcionalmente envie mapping de coluna de origem para campo da API. Com mapping, colunas não mapeadas são ignoradas; sem mapping, os nomes já devem ser campos aceitos. O app vem do envelope, não de uma coluna.
  3. POST /imports/{id}/validate com {}: normaliza e valida domínio, campos, app, colisões existentes e duplicatas internas. Produz estados valid/invalid, sem criar links. Repita o preflight se o contexto mudar.
  4. Consulte resumo e GET /imports/{id}/items. Para relatório completo autenticado, use ?format=csv, sem cursor/limit.
  5. POST /imports/{id}/commit com {} e If-Match da validação: enfileira somente os itens válidos. Não existe commit sem preflight ou quando todos são inválidos. A execução revalida colisões e permissões; o preflight não reserva slugs.

CSV aceita vírgula, CRLF/LF, campos entre aspas, aspas duplicadas e quebras de linha em campos citados; BOM UTF-8 é opcional. Cabeçalhos duplicados, número de colunas inconsistente, UTF-8 malformado, NUL e CSV inválido são rejeitados. webOnly aceita true, false ou vazio no CSV. JSON recebe um array de objetos. clientId é opcional; o padrão é row-1, row-2 etc. Demais campos seguem LinkCreate; recursos associados avançados podem usar JSON ou lotes. Não há importação de segredos, permissões ou chaves.

{"appId":"app_loja","format":"csv","mapping":{"dominio":"domain","codigo":"slug","destino":"desktopUrl"},"content":"dominio,codigo,destino\ngo.example.com,produto,https://shop.example.com/produto"}

POST /exports recebe appId, dataset (links, events, summary), format (json, csv) e, para eventos/resumo, filters.start, filters.end e opcionais timezone: "UTC" e group. O intervalo inicial é inclusivo e o final exclusivo; limite de 366 dias, conforme analytics.

O job produz arquivo a partir de uma consulta limitada na execução, até 1.000 registros ou 1 MiB. Se ultrapassar, falha explicitamente, sem exportação silenciosamente truncada. events usa a projeção sanitizada de analytics e todos os tipos do intervalo, sem IP/hash bruto; summary mantém cobertura/limitações e agrega apenas linhas retidas. Campos objetos são serializados como JSON em células CSV.

Consulte GET /exports/{id} e, quando pronto, GET /exports/{id}/download. O caminho retornado é relativo ao host da API; não contém token. Arquivo disponível por 24 horas após a solicitação, com Cache-Control: no-store, no-transform, Content-Disposition e nosniff. Ausência/expiração/estado indisponível retorna 409 resource_conflict; após retenção do registro, 404. Permissão retirada retorna 403; outra conta/app retorna 404.

CSV de exportação e relatório recebe BOM, aspas escapadas e neutralização com apóstrofo de células que começariam fórmulas (=, +, -, @, inclusive após espaços/controles). Preserve essa proteção ao abrir os arquivos em planilhas.

Cada app pode registrar até 10 destinos HTTPS públicos IPv4, porta 443. Não são aceitos usuário/senha, fragmento, IP literal, localhost, redes privadas, link-local, documentação ou reservadas. A resolução IPv4 é conferida em cada tentativa e o socket TLS conecta ao IP público validado, preservando Host/SNI e validação de certificado. IPv6 exclusivo não é suportado nesta versão. Respostas 3xx não são seguidas. O timeout total da entrega é de 5 segundos; o corpo da resposta é descartado.

O segredo whsec_… aparece apenas na criação/rotação. Guarde-o no servidor receptor. Listas, detalhes, auditoria e recibos não o reexibem; replay da emissão retorna 409 secret_not_replayable e Location do webhook. Os segredos ficam criptografados em AES-256-GCM com chave separada da base, contexto por dono/app/webhook e tag autenticada. Nunca devem ser incluídos em frontend ou logs.

Eventos disponíveis: link.created, link.updated, link.deleted, domain.status_changed, template.publication_succeeded, template.publication_failed, operation.completed, operation.failed, export.ready. A geração ocorre na transação do dado, sem chamada de rede no trigger. Múltiplas alterações confirmadas podem gerar eventos distintos; cliques individuais não geram webhooks.

{"id":"b0000000-0000-4000-8000-000000000001","type":"link.created","schemaVersion":"1","appId":"app_loja","occurredAt":"2026-10-20T01:00:00Z","test":false,"resource":{"id":"go.example.com~-~-produto","revision":"20000000-0000-4000-8000-000000000001"}}

Payload mínimo: busque os dados completos pela API com sua própria permissão. Header Agilink-Event-Id conserva o evento, Agilink-Delivery-Id conserva a entrega. POST /webhooks/{id}/test produz webhook.test com test: true, sem fingir alteração de um recurso.

Header: Agilink-Signature: t=1792458000,v1=<hex>. Calcule HMAC-SHA256 usando o segredo completo, sobre timestamp + "." + corpo bruto UTF-8. Não recodifique JSON antes da verificação. Compare os 32 bytes em tempo constante; aceite timestamp até 300 segundos para mais ou menos e um dos v1 válidos. Durante rotação pode haver dois valores v1, um por segredo, até overlapSeconds (0–86.400).

import {createHmac, timingSafeEqual} from 'node:crypto';
export function verify(secret, header, rawBody) {
const parts = header.split(',');
const t = parts.find(p => p.startsWith('t='))?.slice(2);
if (!/^\d{10,12}$/.test(t ?? '') || Math.abs(Date.now()/1000-Number(t)) > 300) return false;
const expected = createHmac('sha256', secret).update(t+'.').update(rawBody).digest();
return parts.filter(p => p.startsWith('v1=')).some(p => {
const actual = Buffer.from(p.slice(3), 'hex');
return actual.length === expected.length && timingSafeEqual(actual, expected);
});
}

Após verificar, deduplique por event.id de modo durável antes de aplicar um efeito; responda 2xx rapidamente e processe trabalho demorado em fila. A entrega é pelo menos uma vez, sem ordem global. Se o receptor confirmar e a confirmação se perder, haverá repetição legítima do mesmo evento.

A primeira entrega é assíncrona. Até cinco tentativas automáticas por entrega, com intervalos de 30, 60, 120, 240 segundos entre falhas; leases impedem confirmação de um executor antigo. Agendamento é aproximado, conforme cron/capacidade. Timeout, falha de rede, DNS bloqueado e qualquer resposta fora de 2xx falham; não há log de corpo/header do receptor. Metadados sanitizados ficam por sete dias. Dez falhas consecutivas do destino suspendem novos envios; uma entrega 2xx zera o contador.

GET /webhooks/{id}/deliveries e GET /webhook-deliveries/{id} permitem diagnóstico com estado, status HTTP, erro, tentativas e próxima tentativa. POST /webhook-deliveries/{id}/retry com {} e If-Match reinicia uma entrega terminal ainda retida, mantém eventId e exige destino habilitado e autorização atual. Não repita envios em andamento.

Alterar configuração, desativar ou rotacionar muda a revisão do destino: entregas pendentes da revisão anterior são canceladas. A reentrega explícita vincula o evento à revisão nova. Isso evita encaminhar conteúdo antigo automaticamente para um novo endereço. Rotação aceita sobreposição para receptores em atualização. DELETE desativa e preserva histórico; PATCH enabled: true reautoriza com o ator atual. Expirar/revogar a sessão/chave que autorizou o destino impede novos envios até reautorização; não transfere acesso para outra credencial.

Até cinco operações ativas e 100 registros de automação retidos por app; lotes/importações com 50 itens e corpo HTTP de 32 KiB. Um ciclo processa até dez itens e um webhook, respeitando quotas de credencial, conta e app. O executor reenvia à fila o progresso confirmado sem consumir tentativas de recuperação; falhas sem progresso usam leases/backoff e falham após cinco recuperações. Cancelamento interrompe novos itens assim que observado; não desfaz itens confirmados.

A manutenção remove arquivos expirados, segredos anteriores fora da sobreposição e registros/entregas vencidos. Os registros de jobs/auditoria seguem a política da fundação. Backup dos metadados inclui ciphertext; restaurar webhooks exige também a chave externa preservada, que não integra o snapshot de dados. O lançamento foi validado com dados sintéticos em produção; o ensaio de carga foi curto e isolado, sem promessa de capacidade sustentada.