TypeScript zero-dependency

SDK oficial para integrar CRM, campanhas, métricas e webhooks em produção.

Cliente TypeScript gerado pelo próprio ambiente, sem dependências, compatível com Node 18+, Bun, Deno, browsers controlados e edge runtimes. Inclui retries para 429/5xx, parsing de CSV e erros padronizados via DestriaApiError.

Compatibilidade pública

O endpoint /api/v1/sdk.ts retorna X-DestrIA-SDK-Version para pinning em pipelines e também exporta SDK_VERSION, OPENAPI_VERSION e client.sdkVersion para leitura em runtime. O gate sdk:surface no CI valida essa superfície antes de cada release.

Release 1.0.461
createDestriaClient
DestriaClient
DestriaClientOptions
DestriaApiError
SDK_VERSION
OPENAPI_VERSION
RateLimitInfo
parseRateLimit(response)
request<T>()
listContacts() / upsertContact() / getContact() / deleteContact()
listCampaigns() / createCampaign() / enrollContactInCampaign()
listCampaignEvents() / getCampaignMetrics()
listWebhooks() / createWebhook() / rotateWebhookSecret()
listWebhookDeliveries() / retryWebhookDelivery() / exportWebhookDeliveriesCsv()
cronCampaignsProcess() / cronCampaignsDispatch() / cronIdempotencyCleanup()
onRateLimit(info, ctx)
client.lastRateLimit
client.sdkVersion

Versionamento

Leia X-DestrIA-SDK-Version ao baixar o arquivo e valide no runtime com SDK_VERSION, OPENAPI_VERSION e client.sdkVersion antes de iniciar workers críticos.

SemVer operacional

Novos métodos podem ser adicionados em releases 1.x; remoções ou mudanças incompatíveis só entram em major version documentada.

Gate público

O teste sdk:surface falha se SDK_VERSION, OPENAPI_VERSION, client.sdkVersion, RateLimitInfo, parseRateLimit, DestriaApiError.rateLimit, onRateLimit, lastRateLimit ou helpers cron desaparecerem.

Instalação por runtime

O endpoint https://seu-projeto.lovable.app/api/v1/sdk.ts sempre aponta para o ambiente atual, evitando base URLs hardcoded entre preview, staging e produção.

Node.js / Next.js / workers server-side

Baixe o arquivo gerado pelo ambiente e importe em jobs, route handlers, server actions ou filas.

Instalar

curl -L https://seu-projeto.lovable.app/api/v1/sdk.ts -o src/lib/destria-mailer-sdk.ts

Uso

import { createDestriaClient, DestriaApiError } from "./lib/destria-mailer-sdk";

const destria = createDestriaClient({
  apiKey: process.env.DESTRIA_API_KEY!,
  baseUrl: "https://seu-projeto.lovable.app/api/v1",
  maxRetries: 2,
});

export async function syncLead(lead: { email: string; firstName?: string; company?: string }) {
  try {
    const { data: contact } = await destria.upsertContact({
      email: lead.email,
      first_name: lead.firstName,
      company: lead.company,
      add_tags: ["node-sync"],
      metadata: { source: "app" },
    });

    await destria.enrollContactInCampaign("cmp_outbound_q3", {
      email: contact.email,
      first_name: contact.first_name ?? undefined,
      company: contact.company ?? undefined,
    });
  } catch (err) {
    if (err instanceof DestriaApiError && err.status === 409) {
      console.info("Contato bloqueado por supressão/status protegido", err.code);
      return;
    }
    throw err;
  }
}

Smoke test mínimo

Use este teste no CI ou na primeira configuração de um cliente para validar chave, CORS server-side, rate-limit e parsing de resposta.

  • A chave precisa pertencer ao workspace esperado.
  • Falhas 401 devem bloquear deploy ou rotação.
  • Falhas 429 indicam excesso de concorrência no job.

sdk-smoke-test.ts

import { createDestriaClient, DestriaApiError, SDK_VERSION, OPENAPI_VERSION } from "./destria-mailer-sdk";

const destria = createDestriaClient({
  apiKey: process.env.DESTRIA_API_KEY!,
  baseUrl: "https://seu-projeto.lovable.app/api/v1",
  maxRetries: 2,
});

try {
  const contacts = await destria.listContacts({ limit: 1 });
  console.log("SDK OK", { sdkVersion: SDK_VERSION, openapiVersion: OPENAPI_VERSION, clientVersion: destria.sdkVersion, pagination: contacts.pagination });
} catch (err) {
  if (err instanceof DestriaApiError) {
    console.error("DestrIA API", err.status, err.code, err.retryAfterSeconds, err.body);
    process.exitCode = 1;
  } else {
    throw err;
  }
}

Métodos disponíveis

Referência operacional dos métodos exportados por DestriaClient.

17 métodos/grupos
CategoriaMétodoEndpointUso real
Baixo nível
request<T>()
request<T>(path, { method, query, body, headers, signal, accept })
qualquer /api/v1/*

Executa chamadas autenticadas com headers padrão, parsing JSON/texto, retry e DestriaApiError.

  • Use para endpoints futuros sem esperar nova versão do SDK.
  • accept: 'text' é usado para CSV.
Contatos
listContacts()
listContacts({ limit, cursor, status, q, tags, tag, tag_match })
GET /contacts

Lista contatos paginados do workspace com filtros por status, busca e tags all/any.

  • Retorna { data, pagination }.
  • tags aceita array ou CSV.
Contatos
upsertContact()
upsertContact({ email, local_id, first_name, add_tags, metadata, custom_fields, ... })
POST /contacts

Cria ou atualiza contato por e-mail com merge seguro de tags, metadata e custom_fields.

  • Statuses protegidos e supressões críticas não são sobrescritos.
  • Use local_id para reconciliação com CRM.
Contatos
getContact() / deleteContact()
getContact(id) · deleteContact(id)
GET/DELETE /contacts/{id}

Consulta por UUID/local_id/email ou executa soft-delete com supressão manual.

  • deleteContact preserva histórico e evita novos envios.
  • E-mails devem ser passados sem encode manual; o SDK encodeia.
Campanhas
listCampaigns()
listCampaigns({ status, q, list_id, account_id, since, until, include_sequence })
GET /campaigns

Lista campanhas com métricas agregadas e sequência sanitizada opcional.

  • include_sequence não retorna anexos data_url nem corpo completo.
  • Use local_id para integrar com ferramentas externas.
Campanhas
createCampaign()
createCampaign({ local_id, name, status, sequence, schedule, ... })
POST /campaigns

Cria ou atualiza campanha por local_id, útil para seeds de CI e integrações que preparam campanhas antes do enrollment.

  • Não aceita status archived via API pública.
  • sequence, schedule e metrics têm limites de tamanho validados no servidor.
Campanhas
enrollContactInCampaign()
enrollContactInCampaign(campaignId, input, { idempotencyKey })
POST /campaigns/{id}/enroll

Inscreve um contato em campanha por UUID ou local_id, cria o contato se necessário e retorna created/queued/enrollment_id.

  • Use idempotencyKey estável para retries seguros.
  • Retorna 409 para supressão sensível, status protegido ou chave reutilizada com outro payload.
  • Alias compatível: enrollCampaign(campaignId, input).
Campanhas
listCampaignEvents() / getCampaignMetrics()
listCampaignEvents(id, params) · getCampaignMetrics(id, { since, until, max_events })
GET /campaigns/{id}/events · GET /campaigns/{id}/metrics

Consulta eventos cursor-paginados e métricas detalhadas por etapa, variante A/B e links.

  • Salve next_cursor apenas após processar a página.
  • Use max_events para controlar custo de agregações.
Webhooks
listWebhooks() / createWebhook()
listWebhooks(params) · createWebhook({ url, events, active, local_id })
GET/POST /webhooks

Lista e provisiona webhooks HTTPS assinados por HMAC-SHA256.

  • O secret é retornado apenas na criação.
  • URLs privadas, localhost e HTTP são bloqueados.
Webhooks
getWebhook() / updateWebhook() / deleteWebhook()
getWebhook(id) · updateWebhook(id, patch) · deleteWebhook(id)
GET/PATCH/DELETE /webhooks/{id}

Gerencia URL, eventos, ativação e rotação de secret com janela de graça.

  • rotation_grace aceita none, 15m, 1h, 24h e 7d.
  • Ao rotacionar, salve o novo secret imediatamente.
Entregas
listWebhookDeliveries() / exportWebhookDeliveriesCsv()
listWebhookDeliveries(filters) · exportWebhookDeliveriesCsv(filters)
GET /webhook-deliveries

Consulta ou exporta histórico de entregas com filtros por evento, status, origem e período.

  • Headers sensíveis são redigidos no backend.
  • CSV usa accept: 'text' internamente.
Entregas
retryWebhookDelivery() / listWebhookDeliveriesForWebhook()
retryWebhookDelivery(deliveryId) · listWebhookDeliveriesForWebhook(webhookId, filters)
POST /webhook-deliveries/{id}/retry · GET /webhooks/{id}/deliveries

Reenvia entregas elegíveis e lista histórico escopado por webhook.

  • Retry respeita backoff, limite de tentativas e webhook pausado.
  • Use exportWebhookDeliveriesForWebhookCsv para CSV escopado.
Cron
cronCampaignsProcess()
cronCampaignsProcess({ limit, per_campaign_limit, dry_run })
POST /api/public/cron/campaigns-process

Executa o avanço de inscrições e prepara próximos envios a partir de jobs externos ou schedulers gerenciados.

  • Exige a chave pública/anon do backend no header apikey e Content-Type application/json.
  • 400/413/415 são erros de contrato e não devem ser repetidos.
Cron
cronCampaignsDispatch()
cronCampaignsDispatch({ limit, dry_run })
POST /api/public/cron/campaigns-dispatch

Dispara a fila pronta, respeitando autenticação cron, rate limit curto e respostas tipadas para observabilidade.

  • Use dry_run em smoke tests antes de ativar o agendamento real.
  • 429/5xx podem ser repetidos com backoff; o SDK respeita Retry-After.
Cron
cronIdempotencyCleanup()
cronIdempotencyCleanup({ older_than, limit, dry_run })
POST /api/public/cron/idempotency-cleanup

Remove chaves de idempotência antigas com limite de varredura para manter o store operacional.

  • Agende em baixa frequência e monitore deleted/scanned.
  • Payloads acima de 8 KB retornam 413 antes do parse.
Utilitários
parseRateLimit()
parseRateLimit(response) → { limit, remaining, resetAt, retryAfter }
Client-side helper (não faz requisição)

Lê os headers RateLimit-Limit/Remaining/Reset (com fallback para X-RateLimit-*) e Retry-After de qualquer Response, retornando limites tipados para observabilidade e backoff manual.

  • Aceita headers padronizados RFC (RateLimit-*) e legados (X-RateLimit-*).
  • resetAt é sempre Date; interpreta valores absolutos (unix seconds/ms) ou relativos (delta-seconds).
  • Use em conjunto com respostas 429 para ajustar concorrência ou pausar workers automaticamente.
  • Também disponível como `error.rateLimit` em qualquer `DestriaApiError` — não é preciso invocar manualmente após um throw.
Utilitários
onRateLimit / lastRateLimit
createDestriaClient({ onRateLimit(info, { path, method, status, ok }) }) • client.lastRateLimit
Client-side (sem requisição extra)

Hook opcional invocado em toda resposta com headers de rate-limit e cache do último snapshot no client, para telemetria e autotuning sem envolver cada chamada.

  • Erros no callback são engolidos — nunca quebram a request original.
  • Use `client.lastRateLimit` para dashboards; use `onRateLimit` para métricas contínuas (Prometheus, StatsD).

Tratamento de erros

Todo erro HTTP final vira DestriaApiError com status, code, body, headers, retryAfter, retryAfterSeconds e rateLimit.

400/422
Entrada inválida

Corrija payload, schema, e-mail, campos JSONB ou filtros antes de reenviar.

401
API key inválida

Revogue a chave exposta, gere uma nova em /api e valide o header Authorization.

404
Recurso não encontrado

Confirme workspace, UUID/local_id e se a chave pertence ao tenant correto.

409
Conflito operacional

Trate como resultado esperado para contato suprimido, status protegido, campanha arquivada ou retry ainda em backoff.

413
Payload grande

Reduza o corpo para o limite do endpoint. Nos crons públicos o máximo é 8 KB e o erro não deve ser repetido.

415
Content-Type inválido

Envie application/json nos helpers cron; ajuste o scheduler antes de reenviar.

429
Rate limit

O SDK respeita Retry-After antes de falhar; reduza concorrência e monitore X-RateLimit-Plan.

5xx
Falha transitória

O SDK tenta novamente com backoff exponencial; persista o evento externo para novo processamento se exceder maxRetries.

errors.ts

import { createDestriaClient, DestriaApiError } from "./destria-mailer-sdk";

const destria = createDestriaClient({ apiKey: process.env.DESTRIA_API_KEY!, baseUrl: "https://seu-projeto.lovable.app/api/v1" });

try {
  await destria.enrollContactInCampaign("cmp_outbound_q3", {
    email: "[email protected]",
    first_name: "Ana",
    custom_fields: { crm_id: "deal_123" },
  });
} catch (err) {
  if (!(err instanceof DestriaApiError)) throw err;

  switch (err.status) {
    case 409:
      // contato suprimido/status protegido: marque opt-out no CRM e não tente novamente
      console.info("Enrollment bloqueado", err.code, err.body);
      return;
    case 429:
      // o SDK já tentou novamente; use retryAfterSeconds para reagendar job externo
      console.warn("Rate limit", { retryAfter: err.retryAfterSeconds });
      return;
    case 401:
      // chave inválida/exposta: alerte operação e rotacione credencial
      throw new Error("DESTRIA_API_KEY inválida ou expirada");
    default:
      throw err;
  }
}

Helpers cron seguros

Os métodos cronCampaignsProcess, cronCampaignsDispatch e cronIdempotencyCleanup chamam rotas públicas com apikey, rate limit por IP e JSON estrito.

  • Configuração obrigatória

    Defina cronBaseUrl como a origem do app e cronApiKey como a chave pública/anon do backend usada pelo scheduler no header apikey.

  • Erros que não repetem

    400, 413 e 415 indicam contrato inválido. Ajuste payload, tamanho ou headers antes de tentar novamente.

  • Erros repetíveis

    429 e 5xx seguem o mesmo backoff do SDK e expõem retryAfterSeconds para reagendar jobs externos.

cron-dispatch.ts

import { createDestriaClient, DestriaApiError } from "./destria-mailer-sdk";

const destria = createDestriaClient({
  apiKey: process.env.DESTRIA_API_KEY!,
  baseUrl: "https://seu-projeto.lovable.app/api/v1",
  cronBaseUrl: "https://seu-projeto.lovable.app",
  cronApiKey: process.env.BACKEND_PUBLIC_API_KEY!,
  maxRetries: 2,
});

export async function runDispatchCron() {
  try {
    const result = await destria.cronCampaignsDispatch({ limit: 100, dry_run: false });
    console.log("dispatch ok", {
      dispatched: result.dispatched,
      campaigns: result.campaigns_touched,
    });
    return result;
  } catch (err) {
    if (!(err instanceof DestriaApiError)) throw err;

    if (err.status === 413) {
      // Payload acima de 8 KB: erro de configuração, não tente novamente.
      throw new Error("Payload do cron excede 8 KB");
    }

    if (err.status === 415) {
      // O SDK envia application/json; se isto aparecer, há proxy/scheduler alterando headers.
      throw new Error("Scheduler enviou Content-Type inválido");
    }

    if (err.status === 429) {
      // O SDK já respeitou Retry-After durante os retries; reprograme a próxima janela.
      console.warn("Cron limitado", { retryAfter: err.retryAfterSeconds });
      return null;
    }

    if (err.status === 400) {
      console.error("JSON inválido ou parâmetro fora do contrato", err.body);
      return null;
    }

    throw err;
  }
}

Observabilidade de rate limit

onRateLimit e lastRateLimit funcionam nas chamadas REST e nos helpers cron, permitindo reduzir concorrência, reagendar jobs e alimentar métricas externas com o mesmo formato.

  • Cobertura completa

    O hook roda em respostas de sucesso, 429 e 5xx quando houver headers RateLimit-*, inclusive em cronCampaignsDispatch.

  • Backoff operacional

    Use error.rateLimit no catch e client.lastRateLimit fora dele para pausar filas sem parse manual de headers.

rate-limit-observability.ts

import { createDestriaClient, DestriaApiError } from "./destria-mailer-sdk";

const destria = createDestriaClient({
  apiKey: process.env.DESTRIA_API_KEY!,
  baseUrl: "https://seu-projeto.lovable.app/api/v1",
  cronBaseUrl: "https://seu-projeto.lovable.app",
  cronApiKey: process.env.BACKEND_PUBLIC_API_KEY!,
  onRateLimit: (rateLimit, ctx) => {
    console.info("destria rate limit", {
      path: ctx.path,
      status: ctx.status,
      remaining: rateLimit.remaining,
      resetAt: rateLimit.resetAt?.toISOString(),
    });
  },
});

try {
  await destria.cronCampaignsDispatch({ limit: 50 });
} catch (err) {
  if (err instanceof DestriaApiError && err.rateLimit.remaining === 0) {
    console.warn("aguarde nova janela", {
      retryAfter: err.rateLimit.retryAfter,
      resetAt: err.rateLimit.resetAt?.toISOString(),
      lastSeen: destria.lastRateLimit,
    });
    return;
  }
  throw err;
}

Retries, idempotência e performance

Retry automático

O SDK tenta novamente 408, 409, 425, 429 e 5xx até maxRetries. Para 429, respeita Retry-After; caso contrário usa backoff exponencial até 8s.

Idempotência externa

Para Zaps, Makes e filas, persista o ID do evento externo. Se um job falhar depois da resposta da API, reexecutar upsert/enroll é seguro dentro das regras do backend.

Paginação estável

Sempre processe a página antes de salvar next_cursor. Em BI incremental, salve cursor por campanha e por workspace para evitar perda ou duplicidade.

Próximos recursos de integração

Combine este SDK com OpenAPI, Postman, receitas no-code e guia de webhooks para validar o fluxo ponta a ponta antes de liberar automações em produção.