Convenções da API

Autenticação, formato de requisição e resposta, erros, paginação, idempotência, webhooks e limites.

Convenções da API

O contrato comum das APIs públicas da Asender: autenticação, formato de requisição e resposta, erros, paginação, idempotência, limites e versionamento. Cada ferramenta tem a API dela, num endereço próprio:

APIendereço
Plataforma (envio e leitura de dados por API key)https://api.asender.net
E-mailhttps://api.mail.asender.net
CRMhttps://api.crm.asender.net
Páginashttps://api.pages.asender.net
Identidade (OIDC / OAuth 2.1)https://auth.asender.net
As APIs não falam todas o mesmo dialeto. O que é igual em todas (HTTPS, JSON, Authorization: Bearer, cursor em vez de offset) está abaixo. O que difere (caixa das chaves, envelope de erro, forma da paginação) está dito por superfície — não reaproveite o parser de uma API em outra. Para escolher o meio de integração, comece pelo mapa de integrações.

1. Princípios

  1. JSON em tudo. Corpo de requisição e de resposta são JSON (Content-Type: application/json).
  2. Contrato estrito. Campo que a API não conhece é recusado com 400, não ignorado em silêncio.
  3. A conta vem da credencial. O tenant é sempre resolvido da API key ou do token, nunca de um campo do corpo.
  4. Cursor, nunca offset. Listagens longas paginam por cursor opaco.
  5. Escrita idempotente onde há efeito. Envio aceita Idempotency-Key.
  6. Versão no caminho. Todas as rotas públicas começam em /v1.
  7. Rastreável. Toda resposta traz X-Request-Id; cite-o ao abrir suporte.

2. Autenticação

Cabeçalho

Authorization: Bearer <credencial>

É a única forma aceita. ?api_key= e X-Api-Key não existem (respondem 401).

Credenciais

CredencialFormatoPara quê
API key de servidorsk_live_ + 48 hex (produção), sk_test_ + 48 hex (teste)Integração de máquina, de servidor para servidor. Cada chave tem escopos (email:send, leads:read…); rota fora do escopo responde 403.
Token de aplicativo OAuthJWT (três segmentos) emitido por auth.asender.netApp de terceiro que lê dados da conta com o consentimento do usuário (somente leitura). Ver Entrar com Asender.
Chave pública de Web Pushpk_push_…Só para registrar a inscrição de Web Push do navegador do visitante, em POST /v1/push/site/devices. Não é segredo e não vale em nenhuma outra rota.
Sessão OIDCBearer do usuário logadoO painel. Não é para integração de máquina.

Não existe "chave publicável" genérica de leitura: a API key de servidor nunca vai para o navegador. Para o que roda no site do visitante, use as chaves e o embed próprios — ver No navegador.


3. Formato de requisição

Cabeçalhos em POST/PUT/PATCH

Content-Type: application/json

Caixa dos campos (muda por API)

APIcampos
Plataforma — envio (/v1/emails/*, /v1/sms/send, /v1/push/send…)PascalCase, no estilo do Amazon SES (Source, Destination.ToAddresses, Message.Subject.Data)
Plataforma — leitura de dados (/v1/leads, /v1/campaigns, /v1/analytics/*…)snake_case (next_cursor, updated_after)
E-mailminúsculas (from, to, subject, text, html)
CRM e Páginassnake_case / português (contatos, proximo, paginas)

Exemplo (Plataforma): POST https://api.asender.net/v1/emails/send

{
  "Source": "noreply@example.com",
  "Destination": { "ToAddresses": ["user@example.com"] },
  "Message": {
    "Subject": { "Data": "Olá", "Charset": "UTF-8" },
    "Body": { "Text": { "Data": "Oi!" } }
  }
}

Idempotência

Os POST de envio da Plataforma (/v1/emails/send, /v1/emails/batch, /v1/sms/send, /v1/push/send) aceitam o cabeçalho (tem precedência):

Idempotency-Key: uuid-ou-qualquer-texto      # 1 a 128 caracteres de [A-Za-z0-9._:-]

ou o campo IdempotencyKey no corpo.

Nas outras APIs a idempotência é própria: a Mail API devolve 200 com "idempotent_replay": true; o envio de lead em Páginas aceita Idempotency-Key (ou id_externo) e devolve Idempotent-Replayed: true. Está no guia de cada uma.


4. Formato de resposta

Sucesso na Plataforma (envio)

{
  "MessageId": "msg_xxx",
  "Status": "queued",
  "RequestId": "req_xxx"
}

O envio é assíncrono: 202 Accepted significa "aceito e enfileirado", não "entregue".

Códigos HTTP

CódigoUso
200Sucesso em leitura e atualização
201Recurso criado
202Envio aceito (enfileirado)
400Validação falhou (corpo malformado, campo desconhecido, parâmetro inválido)
401Credencial ausente ou inválida
403Sem permissão, ou chave sem o escopo da rota
404Recurso não encontrado (inclui recurso de outra conta)
405Método HTTP errado
409Conflito (por exemplo IdempotencyConflict)
422Regra de negócio (remetente de domínio não verificado, Idempotency-Key malformada)
429Limite de taxa ou de cota excedido
5xxFalha do serviço ou de uma dependência (503 pode trazer Retry-After)

Paginação

Listagens longas paginam por cursor opaco — passe de volta o valor que recebeu, sem decodificar nem montar. Offset não existe. A forma varia por superfície:

Superfíciepedidoresposta
Plataforma — dados (/v1/leads, /v1/forms/{id}/submissions, /v1/conversions, /v1/campaigns, /v1/messaging/*, /v1/conversations…)?limit= (1–200, padrão 50), ?cursor=, ?updated_after=, ?created_after= (RFC 3339){ "data": [...], "next_cursor": "…" | null } — null quando acabou
Plataforma — histórico de e-mail (GET /v1/emails)?limit=, ?cursor=, ?status=, ?q={ "Messages": [...], "NextCursor": "…", "HasMore": true, "RequestId": "…" }
Plataforma — log de requisições (GET /v1/logs)?limit= (1–100), ?after=, ?before=has_more e next em snake_case (ver o guia da Plataforma)
E-mail (mensagens de uma caixa)?limit=, ?cursor={ "messages": [...], "next_cursor": "…" }
CRM (contatos, timeline)?limite=, ?cursor={ "contatos": [...], "proximo": "…" } — vazio quando acabou
Páginas?limite= em algumas coleçõeslista sob chave nomeada ({"paginas":[…]}); analytics por janela ?de=&ate= — sem cursor

Para sincronizar de forma incremental, prefira updated_after (nas rotas de dados da Plataforma) a reler tudo.

GET /v1/leads?limit=100&cursor=eyJ…

{ "data": [ { "id": "ctt_…", "email": "…" } ], "next_cursor": "eyJ…" }

Cabeçalhos de resposta

Presentes na Plataforma (api.asender.net):

X-Request-Id: req_xxx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Cache-Control: no-store

Nas rotas sujeitas a limite de taxa (ver Limites):

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1681234567     # instante (epoch, segundos) em que a janela reabre
Retry-After: 1                    # só em 429 (e em alguns 503): segundos para tentar de novo

5. Erros

Dois envelopes de erro

Cada superfície tem o seu envelope. Leia o Content-Type e o código HTTP primeiro e escolha o parser pela API que respondeu:

Superfícieenvelope
Plataforma — envio e histórico (/v1/emails*, /v1/sms/send, /v1/push/*…){ "Error": { "Type", "Code", "Message", "Details" }, "RequestId" }
Plataforma — leitura de dados (/v1/leads, /v1/campaigns, /v1/analytics/*, /v1/me…){ "error": { "type", "message" } }
E-mail (api.mail.asender.net){ "error": "<Código>", "message": "<texto>" }
CRM (api.crm.asender.net){ "erro": "<mensagem>" }
Páginas — autoria (api.pages.asender.net){ "erro": "<mensagem>" }
Páginas — coleta pública e runtime{ "error": { "message": "<texto>" } }
Identidade — /token e /userinfo{ "error": "<código OAuth>", "error_description": "<texto>" } (RFC 6749); no /authorize o erro volta por redirecionamento, em ?error=

Os envelopes coexistem de propósito: unificar seria uma mudança incompatível. Um cliente que fala com mais de uma API precisa de um parser para cada. Na Plataforma, 404 e 405 em /v1/* também usam o envelope SES-like (Code NotFound / MethodNotAllowed), com uma dica quando o caminho é de outra API.

Estrutura do envelope SES-like (Plataforma — envio)

{
  "Error": {
    "Type": "Sender",
    "Code": "ValidationError",
    "Message": "Messages must contain at least one entry.",
    "Details": { "Destination.ToAddresses": "must contain at least one address" }
  },
  "RequestId": "req_xxx"
}

Os códigos de cada API estão na página dela; aqui só o que é comum.

Quem repete e quem não repete

Repita (com espera) em 429 (respeite Retry-After) e em 502/503. Não repita 400, 401, 403, 404 nem 422: são determinísticos, repetir só repete o erro.


6. Webhooks

A Asender emite webhooks (eventos de entrega da conta, leads do CRM, submissões de formulário e entregas do conector de Integrações) e recebe webhooks de provedores (por exemplo, o status de SMS). Todo webhook que a Asender emite usa o formato único Asender Webhook v1 (ADR-0133): os cabeçalhos Asender-Webhook-Id (estável entre tentativas, a chave de deduplicação), Asender-Event e Asender-Signature: t=<unix>,v1=<hex>, onde v1 é o HMAC-SHA256 do segredo sobre "<t>.<corpo cru>", com tolerância de 300 s e possibilidade de vários v1= durante a rotação do segredo. Os cabeçalhos de assinatura anteriores de cada emissor são descontinuados e saem em 2027-04-20 (Sunset). Os endpoints de eventos da conta são gerenciados pela API pública em /v1/webhooks (escopo webhooks:manage, só chave). O formato, um verificador por linguagem (Node, Python, Go, PHP), as tentativas de cada emissor e a migração estão no guia de webhooks.

Regras que valem para qualquer webhook que você receber:


7. CORS e navegador

A API de servidor (sk_…) é de servidor para servidor; não a chame do navegador (isso exigiria expor a chave). O que roda no navegador do visitante usa chaves e rotas próprias, com origem autorizada: o registro de Web Push (POST /v1/push/site/devices com pk_push_…) e a coleta de formulários e analytics de Páginas. Veja No navegador.


8. Limites

Limite de taxa (Plataforma)

Cota do plano (E-mail)

A Mail API barra o envio ao atingir o teto do plano com 429 QuotaExceeded e os cabeçalhos X-Quota-* (qual janela barrou, quando reabre, Retry-After).

Outros tetos

Cada API declara os seus (por exemplo, Páginas limita o envio de lead por chave por minuto e o tamanho do corpo). Estão no guia da API.


9. Versionamento

O número da versão vai no caminho (/v1). Mudança incompatível cria uma nova versão maior; mudança compatível (campo novo opcional, valor novo de enum, rota nova) não muda a versão — escreva o cliente para ignorar o que não conhece, nos campos de resposta. (Nos campos de requisição vale o contrário: o corpo é estrito.)

Quando algo for ser descontinuado, a Asender avisa pelo registro de mudanças do guia da API e dá prazo antes de remover. Um cabeçalho Sunset por resposta ainda não é emitido (planejado).


10. Lote de e-mails

POST https://api.asender.net/v1/emails/batch envia até 500 e-mails independentes numa chamada. Escopo email:send.

{
  "Messages": [
    { "Source": "noreply@example.com",
      "Destination": { "ToAddresses": ["a@example.com"] },
      "Message": { "Subject": { "Data": "Olá A" }, "Body": { "Text": { "Data": "Oi A" } } } },
    { "Source": "noreply@example.com",
      "Destination": { "ToAddresses": ["b@example.com"] },
      "Message": { "Subject": { "Data": "Olá B" }, "Body": { "Text": { "Data": "Oi B" } } } }
  ]
}

Cada item tem o mesmo formato de POST /v1/emails/send. Resposta, 202:

{
  "Results": [
    { "Index": 0, "Status": "queued", "MessageId": "msg_xxx", "MessageIds": ["msg_xxx"], "ReusedIdempotency": false },
    { "Index": 1, "Status": "rejected", "Error": { "Code": "ValidationError", "Details": { "Destination.ToAddresses": "…" } } }
  ],
  "Count": 2,
  "RequestId": "req_xxx"
}

11. Filtros

As listagens aceitam filtros por query string, e cada rota documenta os seus. O que é comum nas rotas de dados da Plataforma: limit, cursor, updated_after, created_after (datas em RFC 3339, por exemplo 2026-10-19T12:00:00Z). Valor inválido é 400 (InvalidParameter), nunca "corrigido" em silêncio.


12. Segurança


13. SDKs

Hoje o contrato é a OpenAPI unificada e as páginas de cada API; não há SDK oficial de servidor. Se você escrever um cliente, ele deve:

No navegador existe o @asender/pages-sdk, para formulários e leads de Páginas — ver No navegador.