Plataforma

https://api.asender.net · API key (/v1) + sessão OIDC/OAuth 2.1 (/api) · 238 operações · openapi.yaml

Platform API

A API da plataforma Asender: contas (tenants) e sub-contas, membros, convites, API keys, plano e a superfície pública de mensageria por API key. É o que atravessa as ferramentas — a identidade e a conta são compartilhadas; cada produto (Mail, CRM, Páginas) tem a sua própria API num endereço próprio.

Autenticação

/v1/* — API key ou token de app

A superfície pública de máquina aceita duas credenciais, sempre em Authorization: Bearer:

credencialformatoquando usar
API key (chave de servidor)sk_live_… ou sk_test_…integração da sua própria conta, de servidor para servidor. Envia mensagens e lê dados. Veja o guia de chaves de API
Token de app (OAuth 2.1)JWT emitido por auth.asender.netum app seu ou de terceiro que age em nome de um usuário, na conta que ele escolheu ("Entrar com Asender"). Só leitura (veja os escopos abaixo). Veja o guia Entrar com Asender
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx

O tenant_id vem da credencial, nunca de parâmetro, header ou corpo. Esta superfície é rate-limited (ver abaixo).

Só Authorization: Bearer é aceito. X-Api-Key e ?api_key= foram removidos (2026-10-07) e respondem 401.

sk_live_ e sk_test_. O prefixo registra o modo da chave, escolhido na criação (o padrão é live). Hoje a única diferença é o rótulo: o modo aparece no painel e nos logs (filtro mode), o que ajuda a separar tráfego de teste do de produção. Não existe sandbox: uma chave sk_test_ autentica, tem os mesmos escopos e o mesmo rate limit, e a API não trata o envio feito com ela de forma diferente. Para testar sem entregar a ninguém, use destinatários seus. Um sandbox de verdade é planejado.

Chave de servidor não vai ao navegador. Pedido com Origin ou Sec-Fetch-* (sinais de navegador) às rotas de envio de push, WhatsApp, Telegram e canais não oficiais é recusado com 403 ServerKeyInBrowser. Para registrar web push a partir do site existe a chave pública pk_push_…, que só abre POST /v1/push/site/devices.

Servidor MCP. Assistentes de IA acessam estes mesmos dados pelo servidor MCP em https://api.asender.net/mcp (em implantação) — veja o guia MCP.

/api/* — sessão do usuário

A superfície do painel. O usuário autentica no Authorization Server (https://auth.asender.net, OAuth 2.1 + PKCE) e a sessão trafega em cookie HttpOnly — o token nunca vai para o navegador em JavaScript. O tenant da requisição é resolvido da sessão; o header X-Asender-Tenant do front só é aceito depois de provada a pertinência do usuário àquela conta. Conta alheia responde 404, nunca 403 (a existência da conta é informação).

Como obter e usar uma API key

No painel, em Administração → Chaves de API (https://admin.asender.net/t/<conta>/api-keys): crie a chave, escolha os escopos e copie o segredo, que aparece uma única vez. Passo a passo no guia de chaves de API.

O painel usa a Platform API na superfície de sessão, que também serve a quem automatiza a emissão:

# Criar (sessão do usuário; devolve a chave em claro UMA vez)
curl -X POST 'https://api.asender.net/api/tenants/TENANT_ID/api-keys' \
  -H 'Content-Type: application/json' \
  --cookie 'asender_session=...' \
  -d '{ "name": "integração-2fa", "scopes": ["email:send"], "mode": "live" }'

# `scopes` é obrigatório (veja a tabela de escopos); sem ele a emissão responde 400.
# `mode` é opcional: "live" (padrão) ou "test".

# Listar / revogar
curl 'https://api.asender.net/api/tenants/TENANT_ID/api-keys' --cookie '...'
curl -X DELETE 'https://api.asender.net/api/tenants/TENANT_ID/api-keys/KEY_ID' --cookie '...'

A chave emitida aqui autentica as superfícies /v1/* desta API e as APIs de produto (por exemplo POST https://api.mail.asender.net/v1/emails). Guarde-a no servidor — nunca no bundle do navegador.

Rate limits (/v1/*)

A superfície pública é limitada por principal, por janela fixa de 1 segundo: a chave (cada sk_… tem o seu balde) ou, para token de app, o par app × conta (um app barulhento numa conta não consome a cota do mesmo app em outra). O teto padrão é de 100 requisições por segundo. Toda resposta traz o estado do balde:

headersignificado
X-RateLimit-Limitteto de requisições na janela
X-RateLimit-Remainingquantas restam na janela atual
X-RateLimit-Resetepoch (segundos) em que a janela reabre
Retry-After(só no 429) segundos até poder tentar de novo, no mínimo 1

Ao estourar, a resposta é 429:

{ "Error": { "Type": "Sender", "Code": "Throttling", "Message": "Rate limit exceeded." }, "RequestId": "req_…" }

Em 429, espere o Retry-After; sem ele, use backoff exponencial com jitter. Se o limitador estiver indisponível, a API não bloqueia (o controle é de abuso, não de autorização): trate os headers como informativos, não como garantia.

GET /v1/logs tem um teto próprio, menor, de 5 req/s por chave, somado ao limite geral.

A superfície de sessão (/api/auth/*) tem um teto por IP próprio, contra brute-force de login/2FA/recuperação. Não é um limite de integração — é uma defesa da borda de autenticação.

Erros

Todos os erros usam o envelope estilo SES:

{
  "Error": {
    "Type": "Sender",
    "Code": "ValidationError",
    "Message": "One or more fields failed validation.",
    "Details": { "Destination.ToAddresses": "must contain at least one address" }
  },
  "RequestId": "req_a1b2c3"
}
código HTTPCode típicoquando
400ValidationErrorcorpo inválido; Details traz os campos. JSON com campo que a API não conhece nomeia o campo na mensagem (unknown field "X")
401AuthorizationErrorcredencial ausente/ inválida (chave ou sessão)
403InsufficientScopea API key não tem o escopo da rota; a mensagem nomeia o escopo exigido (ex.: API key lacks the required scope: sms:send.) — ver Escopos da API key
403ServerKeyInBrowserchave sk_… usada de um navegador nas rotas de push, WhatsApp, Telegram e canais não oficiais — chame do seu servidor
404NotFoundrecurso fora do escopo do principal (inclui conta alheia)
409IdempotencyConflicta Idempotency-Key já foi usada com um corpo diferente — ver Idempotência
422ValidationErroro corpo é válido, mas o pedido não tem destino possível (ninguém sobra após opt-out, aparelho inexistente) ou a Idempotency-Key é malformada
422RemetenteNaoVerificadoo Source do e-mail não é de um domínio verificado da conta — ver Remetente
429Throttlingrate limit da chave (ver X-RateLimit-*)
503VerificacaoIndisponivelnão deu para confirmar o domínio do Source agora; nada foi enviado — tente de novo (Retry-After)
502InternalErroro serviço de origem falhou ao responder (conta, uso)
5xxInternalErrorfalha do serviço ou de um upstream

O corpo é lido com contrato estrito: campo desconhecido é recusado com 400.

Paginação

As coleções da plataforma paginam por cursor (NextCursor / HasMore), nunca por offset — offset pula ou repete item quando o dado muda entre chamadas. Passe o NextCursor de volta para a próxima página.

As rotas de dados (/v1/leads, /v1/campaigns etc.) e os logs usam snake_case (next_cursor, has_more) e limit de 1 a 200; veja Dados.

Superfície da API

/v1/* — pública (API key ou token de app)

Todas as rotas abaixo ficam em https://api.asender.net, exigem um escopo e respondem no envelope de erro desta API. Rota que não está nesta lista não existe para chave (403, nunca "liberada por omissão").

gruporotasescopo
E-mailPOST /v1/emails/send, POST /v1/emails/batch, GET /v1/sendersemail:send
GET /v1/emails, GET /v1/emails/{id}email:read
SMSPOST /v1/sms/sendsms:send
WhatsApp (oficial, Meta)POST /v1/whatsapp/sendwhatsapp:send
Canais não oficiaisPOST /v1/telegram/sendtelegram:send
POST /v1/whatsweb/sendwhatsweb:send
POST /v1/telegramconta/sendtelegramconta:send
PushPOST /v1/push/sendpush:send
GET/POST /v1/push/devicespush:devices
POST /v1/push/site/devices (navegador)chave pública pk_push_…
ContaGET /v1/accountqualquer chave válida (token de app: account:read)
GET /v1/account/usageaccount:read
GET /v1/mesó token de app, escopo openid
LogsGET /v1/logs, GET /v1/logs/{id}logs:read
Dados (leitura)GET /v1/leads, /v1/leads/{id}leads:read
Leads (escrita)POST /v1/leads, PATCH /v1/leads/{id}leads:write (só chave)
GET /v1/forms, /v1/forms/{id}/submissionsforms:read
GET /v1/conversionsconversions:read
GET /v1/campaigns, /v1/campaigns/{id}, /v1/campaigns/{id}/report, /v1/reports/email/{overview,timeseries,top-emails,domains}campaigns:read
GET /v1/messaging/{sms,whatsapp,push}/messages, /v1/messaging/{sms,whatsapp,push}/reports/overviewmessaging:read
GET /v1/analytics/{overview,breakdown,timeseries,forms}analytics:read
GET /v1/conversations, /v1/conversations/{id}/messagesconversations:read
GET /v1/journeys, /v1/journeys/{id}journeys:read
GET/POST /v1/webhooks, GET/PATCH/DELETE /v1/webhooks/{id}, POST /v1/webhooks/{id}/testwebhooks:manage
DNSGET /v1/dns/zones, GET /v1/dns/zones/{zona}/records, GET /v1/dns/changesdns:read
POST /v1/dns/zones/{zona}/records, PATCH/DELETE /v1/dns/zones/{zona}/records/{rid}dns:write

Os grupos são detalhados nas seções seguintes. As rotas de dados têm um resumo abaixo e o detalhe campo a campo no guia Entrar com Asender (seção 9) e na referência OpenAPI.

Escopos da API key

Cada rota exige um escopo; a chave que não o tem recebe 403 InsufficientScope. Uma chave de e-mail (email:send,email:read) não envia SMS, push nem WhatsApp. Rota sem escopo declarado também é recusada. Existem 28 escopos; a coluna "token de app" diz quais um app OAuth também pode exercer (só leitura; *:send, mail:admin e *:write são exclusivos de chave).

escopoliberatoken de app
email:sendPOST /v1/emails/send, POST /v1/emails/batch, GET /v1/sendersnão
email:readGET /v1/emails, GET /v1/emails/{id}sim
sms:sendPOST /v1/sms/sendnão
push:sendPOST /v1/push/sendnão
push:devicesGET/POST /v1/push/devicesnão
whatsapp:sendPOST /v1/whatsapp/sendnão
telegram:sendPOST /v1/telegram/send (bot do Telegram)não
whatsweb:sendPOST /v1/whatsweb/send (WhatsApp por sessão, canal não oficial)não
telegramconta:sendPOST /v1/telegramconta/send (conta pessoal do Telegram, canal não oficial)não
account:readGET /v1/account/usagesim
logs:readGET /v1/logs, GET /v1/logs/{id} (log das requisições da própria conta)sim
mail:adminadministração de domínios e caixas do e-mail da conta, na API do mail (GET /orgs, POST/GET /orgs/{orgID}/domains, verificação de domínio, POST/GET /domains/{id}/mailboxes…). Não lê mensagens (email:read) nem envia (email:send)não
forms:readGET /v1/forms e GET /v1/forms/{id}/submissions em api.asender.net, e a leitura de formulários em api.pages.asender.netsim
forms:writecriar, editar e apagar formulários e enviar leads (submissões), em api.pages.asender.netnão
pages:readler páginas, versões, rascunho, SEO, domínios e mídia, em api.pages.asender.net (não são rotas de api.asender.net)não
pages:writecriar e editar páginas (versões, rascunho, SEO, importar, duplicar, mudar de domínio, mídia), em api.pages.asender.netnão
pages:publishpublicar e despublicar páginas (ação visível ao público; escopo separado do pages:write), em api.pages.asender.netnão
leads:readGET /v1/leads, GET /v1/leads/{id} (contatos do CRM)sim
leads:writePOST /v1/leads, PATCH /v1/leads/{id} (criar e atualizar contatos; não inclui leitura)não
conversions:readGET /v1/conversionssim
analytics:readGET /v1/analytics/*sim
campaigns:readGET /v1/campaigns*, GET /v1/reports/email/*sim
messaging:readGET /v1/messaging/* (SMS, WhatsApp e push)sim
conversations:readGET /v1/conversations*sim
journeys:readGET /v1/journeys*sim
webhooks:manage/v1/webhooks* (cadastro e teste de endpoints de webhook; o segredo whsec_… sai uma vez, no POST)não
dns:readGET /v1/dns/zones, /v1/dns/zones/{zona}/records, /v1/dns/changes (DNS da conta pela Cloudflare)não
dns:writePOST /v1/dns/zones/{zona}/records, PATCH/DELETE …/records/{rid}; não inclui leituranão

GET /v1/account aceita qualquer chave válida. Emita chaves novas com os escopos mínimos da integração (painel de chaves): escopo é obrigatório e só vale o catálogo acima — a emissão sem escopos, ou com um valor fora do catálogo, responde 400 com a lista válida. Uma chave sem escopo do catálogo recebe 403 InsufficientScope em toda rota.

leads:write vale para a chave de servidor (POST /v1/leads, PATCH /v1/leads/{id}, veja Escrever leads). Token de app OAuth nunca recebe esse escopo: o consentimento não o concede e as rotas respondem 403.

DNS (/v1/dns/*). Só chave sk_* (app OAuth recebe 403). A conexão com a Cloudflare (o token) é feita por uma pessoa no painel, em DNS; a API não cria nem lê conexões. Campos e erros em inglês; o tenant é o da chave. Controles de escrita: ?dry_run=1 devolve o plano sem alterar nada; X-Asender-Confirmar: <nome do registro> é obrigatório no DELETE e em MX/SPF/DMARC do apex (428 ConfirmationRequired sem ele); Idempotency-Key (24 h). Fora de produção toda escrita responde 403 NonProductionBlocked. Toda tentativa de escrita entra em GET /v1/dns/changes com o before, para desfazer.

Exemplo — e-mail pela plataforma. Serve para código de verificação/2FA, e-mail transacional, lote e campanha: aceita qualquer remetente de um domínio verificado na conta, com o escopo email:send (não precisa criar caixa):

curl -X POST 'https://api.asender.net/v1/emails/send' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 8f7c1e2a-…' \
  -d '{
    "Source": "campanhas@empresa.com.br",
    "Destination": { "ToAddresses": ["cliente@exemplo.test"] },
    "Message": {
      "Subject": { "Data": "Novidades de setembro" },
      "Body": { "Html": { "Data": "<h1>Olá</h1>" } }
    }
  }'

Resposta 202:

{ "MessageId": "…", "MessageIds": ["…"], "Status": "queued", "ReusedIdempotency": false, "RequestId": "req_…" }

Remetente só de domínio verificado

O Source de POST /v1/emails/send e de cada item de /v1/emails/batch tem de ser de um domínio verificado nesta conta (SPF e DKIM conferidos em Admin → Domínios, https://admin.asender.net/t/<conta>/dominios). O e-mail sai assinado com o DKIM do seu domínio.

Atenção: aqui basta o domínio estar verificado. Na Mail API (POST /v1/emails) o endereço também precisa ser uma caixa criada no domínio.

Ver ou verificar o domínio por API: não existe /v1/dominios nesta API. Pelo painel, em Admin → Domínios; por API, na Mail API com uma chave de escopo mail:admin: GET https://api.mail.asender.net/orgs, GET …/orgs/{id}/domains e POST …/domains/{id}/verify. (/v1/dominios existe só na API de Páginas, para domínios de sites.)

{
  "Error": {
    "Type": "Sender",
    "Code": "RemetenteNaoVerificado",
    "Message": "O domínio empresa.com.br não está verificado nesta conta (SPF e DKIM). Verifique o domínio em Admin → Domínios (https://admin.asender.net/t/acc_123/dominios), ou use um remetente de um domínio já verificado. Nada foi enviado."
  },
  "RequestId": "req_…"
}
Mudança de contrato (2026-10-02). Até esta data, um Source de domínio não verificado era aceito com 202 e o e-mail saía com o remetente da plataforma (no-reply@…), com o seu endereço em Reply-To. Agora o pedido é recusado com 422 RemetenteNaoVerificado. Se você integrava mandando um Source não verificado, verifique o domínio em Admin → Domínios ou troque o Source por um endereço de domínio verificado. Veja o registro de mudanças.
Qual e-mail usar? Para código de verificação/2FA (OTP) e e-mail transacional, use POST https://api.asender.net/v1/emails/send (aqui): basta o domínio estar verificado. A Mail API (POST https://api.mail.asender.net/v1/emails) é para enviar a partir de uma caixa corporativa que já existe. | | api.asender.net/v1/emails/send | api.mail.asender.net/v1/emails | | --- | --- | --- | | corpo | Source, Destination.ToAddresses, Message (PascalCase) | from, to[], subject, text/html (minúsculas) | | remetente | qualquer endereço de domínio verificado | caixa existente em domínio verificado | | sem a condição do remetente | 422 RemetenteNaoVerificado | 422 CaixaRemetenteInexistente | | escopo | email:send | email:send | | envelope de erro | { "Error": {…}, "RequestId" } | { "error", "message" } |

Descubra remetentes válidos (GET /v1/senders)

Antes de enviar, pergunte o que a chave pode usar como from. Escopo email:send (o mesmo de quem envia); a conta é a da chave, não há parâmetro de conta.

curl 'https://api.asender.net/v1/senders' -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx'
{
  "Domains": [
    { "Name": "empresa.com.br", "Verified": true, "UsableFor": ["emails/send"], "DnsStatus": "ok" },
    { "Name": "novo.com.br", "Verified": false, "UsableFor": [], "DnsStatus": "pending" }
  ],
  "Mailboxes": [
    { "Address": "no-reply@empresa.com.br", "UsableFor": ["emails/send", "mail:/v1/emails"] },
    { "Address": "oi@novo.com.br", "UsableFor": ["mail:/v1/emails"] }
  ],
  "RequestId": "req_…"
}

Logs de requisições (GET /v1/logs)

O log responde "o que a minha integração mandou e o que a API respondeu?". Cada requisição feita à API pública com uma chave da sua conta vira um registro, inclusive as recusadas (400, 403, 429). Escopo logs:read; a conta é a da chave (não existe parâmetro de conta: um tenant_id na query é descartado). Teto próprio de 5 req/s por chave, somado ao limite geral da API.

O que é gravado. Método, caminho e padrão de rota, status, latência, chave (id e nome) e modo (live/test), Idempotency-Key, código de erro do envelope, ids de mensagem devolvidos pelo envio (message_ids), request_id e trace_id. O detalhe traz também os corpos da requisição e da resposta, depois da redação (abaixo).

Redação. Segredo não é gravado, em nenhum campo, em qualquer nível do JSON: tokens de API (sk_…/pk_…), JWT, Bearer …, Authorization, senhas, códigos de verificação (code, otp), PIN, CVV, números de cartão (com checagem de Luhn) e CPF/CNPJ viram [REDACTED]. O conteúdo das mensagens (html, text, subject, body, variáveis de template) vira um marcador [redacted len=<bytes> sha256=<16 hex>]: dá para saber que existiu e conferir se dois envios tinham o mesmo conteúdo, sem guardar o texto. Destinatários (to, cc, bcc, phone…) aparecem mascarados (c***@cliente.com.br). Na query só aparecem parâmetros conhecidos. Cada registro informa em redaction_version qual conjunto de regras valeu. Corpos redigidos acima de 16 KiB são cortados (request_body_truncated / response_body_truncated).

Retenção. Os registros ficam guardados por tempo indeterminado; não há rotina de expiração hoje. Um aviso antecipado precede qualquer mudança nisso.

Filtros (todos opcionais, combináveis): status_class (2xx,3xx,4xx,5xx), status, method, path (padrão de rota exato, ex. /v1/emails/send), api_key_id (vírgula para várias), error_code, idempotency_key, message_id, mode (live/test), since e until (RFC 3339). Sem janela, valem as últimas 24 h; uma janela maior que 31 dias é reduzida a 31 dias antes de until.

Paginação por cursor. A lista vem do mais novo para o mais antigo, limit de 1 a 100 (padrão 20). Enquanto has_more for true, repita a chamada com after=<next>; before=<cursor> volta para os mais novos (after e before juntos dão 400). O cursor é opaco e assinado para a sua conta: alterado ou de outra conta, dá 400. (Esta rota usa has_more/next em snake_case, não HasMore/NextCursor: o corpo vem do serviço de logs sem reescrita.)

# Falhas de envio das últimas 24 h
curl 'https://api.asender.net/v1/logs?status_class=4xx,5xx&path=/v1/emails/send&limit=20' \
  -H 'Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxx'

# Próxima página
curl 'https://api.asender.net/v1/logs?status_class=4xx,5xx&after=cur_eyJ0Ijoi…' \
  -H 'Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxx'
{
  "object": "list",
  "has_more": true,
  "next": "cur_eyJ0Ijoi…",
  "data": [
    {
      "id": "log_3f9a1c0b2d4e5f60", "object": "log",
      "created_at": "2026-10-07T12:00:01Z",
      "method": "POST", "path": "/v1/emails/send", "route": "/v1/emails/send",
      "status": 422, "duration_ms": 38, "mode": "test",
      "api_key": { "id": "key_9", "name": "producao" },
      "idempotency_key": "pedido-1042", "error_code": "RemetenteNaoVerificado",
      "message_ids": [], "request_id": "req_abc123"
    }
  ]
}

Do envio ao log, e de volta. O id de mensagem que o envio devolve entra em message_ids; para achar a requisição que criou uma mensagem, filtre por message_id. Para investigar uma falha, abra o detalhe — a lista não traz corpos:

curl 'https://api.asender.net/v1/logs?message_id=msg_01HXYZ' \
  -H 'Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxx'

curl 'https://api.asender.net/v1/logs/log_3f9a1c0b2d4e5f60' \
  -H 'Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxx'

request_body e response_body do detalhe vêm redigidos; null quando a requisição não tinha corpo.

Erros. 400 ValidationError (parâmetro inválido, cursor adulterado, after com before), 401, 403 InsufficientScope (chave sem logs:read; uma chave só com email:send não lê logs), 404 NotFound (id inexistente ou de outra conta, com o mesmo corpo), 429. Serviço de logs fora do ar: 503 ServiceUnavailable com Retry-After; o resto da API continua funcionando. Os 400/404 desta rota vêm no envelope do serviço de logs ({ "error": { "type", "code", "message" } }, chaves em minúsculas), diferente do { "Error": … } do restante da API.

No painel, os mesmos dados (mais séries e resumo por rota e por código de erro) ficam em GET /api/tenants/{id}/logs, …/logs/{logId}, …/logs/series e …/logs/resumo, para quem é owner ou admin da conta.

Lote de e-mail (POST /v1/emails/batch)

Até 500 mensagens independentes numa chamada, cada uma no mesmo formato de /v1/emails/send, dentro da lista Messages. Escopo email:send. Cada item é validado e enfileirado sozinho: um item com erro não derruba os outros, e o resultado vem por índice.

curl -X POST 'https://api.asender.net/v1/emails/batch' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: lote-2026-10-20' \
  -d '{
    "Messages": [
      { "Source": "contato@empresa.com.br",
        "Destination": { "ToAddresses": ["ana@exemplo.test"] },
        "Message": { "Subject": { "Data": "Olá, Ana" }, "Body": { "Text": { "Data": "Seu pedido saiu." } } } },
      { "Source": "contato@empresa.com.br",
        "Destination": { "ToAddresses": ["bia@exemplo.test"] },
        "Message": { "Subject": { "Data": "Olá, Bia" }, "Body": { "Text": { "Data": "Seu pedido saiu." } } } }
    ]
  }'

Resposta 202:

{
  "Results": [
    { "Index": 0, "Status": "queued", "MessageId": "…", "MessageIds": ["…"], "ReusedIdempotency": false },
    { "Index": 1, "Status": "rejected", "Error": { "Code": "ValidationError", "Details": { "Destination.ToAddresses": "must contain at least one address" } } }
  ],
  "Count": 2
}

WhatsApp (POST /v1/whatsapp/send)

Mesmo padrão do SMS: 202 quer dizer enfileirado, não entregue. Mande ou Body (texto livre) ou Template (slug de um template aprovado pela Meta e registrado na conta) com Variables posicionais.

# Abrir conversa: template aprovado
curl -X POST 'https://api.asender.net/v1/whatsapp/send' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: pedido-1042-aviso' \
  -d '{ "To": ["+5511999990000"], "Template": "promo_outubro", "Variables": { "1": "Ana", "2": "10%" } }'

SMS

POST /v1/sms/send responde 202 com enfileirado, não entregue. Corpo: To (lista de números em E.164), Body (até 1600 caracteres), From, Tags e IdempotencyKey opcionais. Escopo sms:send.

curl -X POST 'https://api.asender.net/v1/sms/send' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: codigo-1042' \
  -d '{ "To": ["+5511999990000"], "Body": "Seu código é 482910." }'

Resposta 202: { "MessageId", "MessageIds", "Status": "queued", "ReusedIdempotency", "RequestId" }.

Push e dispositivos

POST /v1/push/send responde 202 com enfileirado, não entregue. Escopo push:send.

curl -X POST 'https://api.asender.net/v1/push/send' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: aviso-1042' \
  -d '{ "DeviceTokens": ["<token do aparelho registrado>"], "Notification": { "Title": "Pedido enviado", "Body": "Chega amanhã." } }'

Registrar e listar aparelhos (do seu servidor, com a chave sk_…, escopo push:devices):

# Registrar (upsert: o mesmo token duas vezes não cria dois registros)
curl -X POST 'https://api.asender.net/v1/push/devices' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{ "Token": "<token FCM/APNs>", "Platform": "android", "UserRef": "cliente-77" }'

# Listar (paginação por cursor: Devices, NextCursor, HasMore; query limit e cursor)
curl 'https://api.asender.net/v1/push/devices?limit=50' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx'

Platform é ios, android ou web; para web mande Endpoint, P256dh e Auth (do PushSubscription.toJSON() do navegador) em vez de Token.

Registro direto do navegador (web push). Nunca ponha a chave sk_… no site. O site usa a chave pública pk_push_…, emitida no painel (tela de push, junto do manifest.json, do service worker e do snippet já preenchidos) e atrelada às origens do site que você declarou. Ela só abre uma rota:

curl -X POST 'https://api.asender.net/v1/push/site/devices?site_key=pk_push_xxxxxxxx' \
  -H 'Origin: https://www.seusite.com.br' \
  -H 'Content-Type: application/json' \
  -d '{ "Endpoint": "https://…", "P256dh": "…", "Auth": "…" }'
# 200 { "Status": "registered" }

Origem não declarada, chave adulterada ou plataforma diferente de web são recusadas. A rota tem teto por IP (30 por minuto) e por chave (1200 por minuto). A chave de site não é segredo, mas só vale para registrar inscrições. O guia do navegador mostra o snippet completo.

Canais não oficiais (Telegram e WhatsApp por sessão)

Três rotas de envio por canais que não são a API oficial da Meta. Todas respondem 202 com { "MessageId", "Status": "queued" } (enfileirado, não entregue), aceitam Idempotency-Key, recusam chave de servidor vinda de navegador (403 ServerKeyInBrowser) e respondem 503 com Retry-After se o serviço do canal estiver fora. Cada uma tem o seu escopo, para você liberar só o que a integração usa.

rotaescopocorporegra do canal
POST /v1/telegram/sendtelegram:sendChatId (texto ou inteiro), Text (até 4096), BotId opcional (tgb_…)o destinatário precisa ter aberto o bot e enviado /start; senão 422 ChatNaoIniciado
POST /v1/whatsweb/sendwhatsweb:sendTo (E.164, + e 8 a 15 dígitos), Text (até 4096), SessionId opcional (wws_…)só responde a chats que já existem; senão 422 ChatInexistente
POST /v1/telegramconta/sendtelegramconta:sendTo (@usuario, telefone ou id), Text (até 4096), SessionId opcional (tgc_…)só escreve a quem a conta já tem conversa; senão 422 DialogoInexistente
curl -X POST 'https://api.asender.net/v1/telegram/send' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: aviso-1042-tg' \
  -d '{ "ChatId": "123456789", "Text": "Seu pedido saiu." }'

Sem BotId/SessionId, a conta usa o bot ou a sessão configurados. Bot, sessão e número se conectam no painel da respectiva ferramenta. Erros: 400 ValidationError (campos em Details), 404 (bot ou sessão não encontrado), 409 IdempotencyConflict. Como são canais não oficiais, estão sujeitos às regras e aos bloqueios das plataformas; prefira o WhatsApp oficial para campanhas.

Conta, uso e identidade

# Dados da conta da chave (qualquer chave válida)
curl 'https://api.asender.net/v1/account' -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx'
# { "TenantId", "Name", "Plan", "Status", "ApiKeyId", "ApiKeyScope", "RequestId" }

# Consumo do mês (escopo account:read)
curl 'https://api.asender.net/v1/account/usage' -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx'
# { "Usage": { "Period", "EmailsSent", "SmsSent", "PushSent", "ApiCalls" }, "RequestId" }

GET /v1/me é só para token de app com o escopo openid: devolve quem autorizou o app e em que conta, nunca os dados de outra:

{ "user": { "id": "…", "email": "…", "name": "…" }, "account": { "id": "…", "name": "…" }, "scopes": ["leads:read"], "client_id": "…" }

email e name só aparecem se o token foi emitido com os escopos email e profile. Uma chave sk_… recebe 403 nesta rota.

Dados (leitura do que a conta tem)

As rotas de dados são de leitura (a única escrita é a de leads, abaixo) e servem a integrações de relatório, sincronização com BI/CRM externo e a apps de terceiro. Aceitam a chave sk_… com o escopo da rota e o token de app (neste caso o usuário que autorizou precisa ter acesso ao produto da conta; remover o acesso dele passa a valer em até 60 s).

escoporotasproduto exigido do usuário (token de app)
leads:readGET /v1/leads, /v1/leads/{id}CRM
leads:writePOST /v1/leads, PATCH /v1/leads/{id} (só chave, nunca token de app)—
forms:readGET /v1/forms, /v1/forms/{id}/submissionsPáginas
conversions:readGET /v1/conversionsCRM
campaigns:readGET /v1/campaigns, /{id}, /{id}/report, GET /v1/reports/email/overview, /timeseries, /top-emails, /domainsE-mail Marketing
messaging:readGET /v1/messaging/{sms,whatsapp,push}/messages, …/reports/overviewo produto do canal
analytics:readGET /v1/analytics/overview, /breakdown, /timeseries, /formsPáginas
conversations:readGET /v1/conversations, /v1/conversations/{id}/messagesAtendimento
journeys:readGET /v1/journeys, /v1/journeys/{id}CRM

Convenções (diferentes das rotas de mensageria acima):

curl -s 'https://api.asender.net/v1/leads?limit=200&updated_after=2026-10-01T00:00:00Z' \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx'
# {"data":[{"id":"ctt_…","email":"…","stage":"novo"}],"next_cursor":"…"}

Os campos de cada recurso, a sincronização incremental recomendada e os erros estão no guia Entrar com Asender (seção 9) e na referência OpenAPI. As conversões enviadas a Meta, Google e TikTok (GET /v1/conversions) são explicadas em Conversões e conectores.

Escrever leads

POST /v1/leads cria e PATCH /v1/leads/{id} atualiza um contato do CRM da conta da chave. Escopo leads:write, só chave. O corpo é fechado (campo desconhecido, como tenant_id, id ou os nomes em português, é 400):

campoobservação
email, phoneao menos um na criação; guardados cifrados; outro lead da conta com o mesmo e-mail/telefone → 409 Conflict
first_name, last_name, job_title, department, country (ISO-2 maiúsculo), state, city, language, linkedintexto; no PATCH, "" limpa os opcionais
stagenovo, contatado, qualificado, cliente, descartado
tagsaté 50; no PATCH a lista substitui a atual
custom_fieldsaté 60 chaves de texto; no PATCH mescla e "" remove a chave
curl -s https://api.asender.net/v1/leads \
  -H 'Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx' \
  -H 'Idempotency-Key: formulario-evento-8841' \
  -H 'Content-Type: application/json' \
  -d '{"email":"ana@exemplo.com","first_name":"Ana","tags":["evento"]}'
# 201 {"id":"ctt_…","email":"ana@exemplo.com","first_name":"Ana","stage":"novo",…}

Idempotency-Key (24 h por conta e rota): repetir a chave com o mesmo corpo devolve o mesmo resultado (Idempotent-Replayed: true) sem criar outro lead; a mesma chave com outro corpo é 422 IdempotencyKeyReused; chamada anterior ainda em curso, 409 IdempotencyInProgress (Retry-After). PATCH em lead inexistente ou de outra conta é 404 idêntico. A origem do lead fica registrada como api. Os mesmos recursos estão no servidor MCP (asender_leads_create, asender_leads_update).

Idempotência (Idempotency-Key)

Mande Idempotency-Key (1 a 128 caracteres de [A-Za-z0-9._:-]; o header vale mais que o campo IdempotencyKey do corpo) em todo envio que possa ser repetido por retry. Chave fora desse formato é recusada com 422 ValidationError (nunca é ignorada em silêncio).

você mandaresposta
chave nova202, ReusedIdempotency: false
a mesma chave e o mesmo corpo202 com os ids da mensagem original e ReusedIdempotency: true — nada é reenviado nem cobrado de novo
a mesma chave com corpo diferente409 IdempotencyConflict — o corpo novo não é enviado e a mensagem original não muda
{
  "Error": {
    "Type": "Sender",
    "Code": "IdempotencyConflict",
    "Message": "The Idempotency-Key was already used with a different request body. Send a new key for a new message, or resend the original payload to replay it."
  },
  "RequestId": "req_…"
}

O "mesmo corpo" ignora a ordem dos campos e o campo IdempotencyKey. No lote de e-mail a chave vale por item (<chave>:<índice>) e cada item traz ReusedIdempotency. A comparação de corpo vale hoje para SMS, push, WhatsApp e os canais não oficiais; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o 202 original sem comparar o corpo — use uma chave nova por mensagem nova.

/api/* — painel (sessão)

Em toda rota /api/tenants/{id}/… o {id} vem do cliente e é autorizado contra a pertinência do usuário antes de qualquer hop ao core; conta alheia responde 404.

Superfície interna (não pública). O controle de plataforma — suporte, moderação, risco e quota — é plano de controle, serviço-a-serviço na malha privada. Não é uma superfície de integração e não é documentado aqui; ele existe atrás do BFF, que é o que responde em api.asender.net.

Registro de mudanças

datamudançaquem é afetado
2026-10-02Quebra de contrato. POST /v1/emails/send e /v1/emails/batch recusam Source de domínio não verificado com 422 RemetenteNaoVerificado (antes: 202 e envio com o remetente da plataforma + Reply-To). Verificação fora → 503 VerificacaoIndisponivel. Mensagem cujo domínio perde a verificação depois do 202 fica failed, nunca sai com outro remetente.Integrações que mandavam Source de domínio não verificado.
2026-10-06Idempotência e push. Idempotency-Key repetida com corpo diferente passa a dar 409 IdempotencyConflict em SMS, push e WhatsApp (antes: 202 com o id da 1ª mensagem); ReusedIdempotency passa a vir true no replay de SMS e push (antes: sempre false). POST /v1/push/send recusa Topic com 400 ValidationError (envio por tópico nunca funcionou: respondia 422). JSON com campo desconhecido nomeia o campo na mensagem do 400.Integrações que reusavam a chave para corpos diferentes ou enviavam push por Topic.
2026-10-07Quebra de contrato: chaves sem escopo deixam de funcionar. Toda API key precisa de escopos do catálogo. Chave sem lista de escopos, full_access, send_only ou com emails:* recebe 403 InsufficientScope em toda rota (exceto GET /v1/account); a emissão de chave sem escopos, ou com valor fora do catálogo, responde 400 ValidationError com a lista válida.Quem usa chave emitida antes desta data sem escopo do catálogo: gere outra no painel de chaves, com os escopos mínimos.
2026-10-07Erros de rota em /v1/*. 404 e 405 agora vêm no envelope { "Error": { "Type", "Code", "Message" }, "RequestId" } (Code NotFound / MethodNotAllowed), com Allow no 405 e uma dica quando o caminho é de outra API (POST /v1/emails é da Mail API; /v1/dominios é da API de Páginas). Antes: 404 page not found em texto e 405 sem corpo.Clientes que tratavam o corpo do 404/405 como texto.
2026-10-07Novo: GET /v1/senders. Lista os domínios (com Verified e DnsStatus) e as caixas ativas que a chave pode usar como from; escopo email:send.Quem precisava descobrir remetentes válidos antes de enviar.
2026-10-07Novo: log de requisições da API. GET /v1/logs e GET /v1/logs/{id} mostram o que a sua integração mandou e o que a API respondeu (corpos redigidos, filtros, paginação por cursor, ligação com message_ids). Novo escopo logs:read; chaves existentes não o ganham — emita uma chave com o escopo para ler.Quem quer depurar integrações sem pedir logs ao suporte.
2026-10-20Guia completo. Escopos telegram:send, whatsweb:send, telegramconta:send e os de leitura de dados (leads:read, conversions:read, analytics:read, campaigns:read, messaging:read, conversations:read, journeys:read) documentados; rotas de canais não oficiais, GET /v1/me, /v1/push/site/devices, rotas de dados, token de app OAuth e o MCP (em implantação) acrescentados. Sem mudança de contrato.Quem integra: o catálogo agora tem 24 escopos.
2026-10-20Novo: escrita de leads. POST /v1/leads e PATCH /v1/leads/{id} (escopo leads:write, só chave, com Idempotency-Key); o MCP ganha asender_emails_send, asender_leads_create e asender_leads_update. Aditivo: nada existente muda.Quem integra: o catálogo passa a ter 28 escopos; app OAuth continua sem escrita.

Referência

Toda operação registrada no código, com o que faz, o que recebe e o que devolve.

bff-auth

GET /api/auth/2fa

Estado do segundo fator do usuário da sessão.

Onde é usadatela de segurança do painel.
Efeitosuma leitura no asender-auth. Responde o estado CONFIRMADO, e não "existe segredo": um setup interrompido deixa segredo gravado sem confirmação, e mostrar "2FA ligado" aí faria a pessoa acreditar numa proteção que o login não exige.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X GET 'https://api.asender.net/api/auth/2fa' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/auth/2fa

Segunda etapa do login — confirma o código do segundo fator.

Onde é usadatela /2fa, para onde o login manda quem tem segundo fator.
Efeitoscria sessão no asender-auth. # Por que esta rota é PÚBLICA Quem chega aqui ainda não tem sessão — é justamente o que ela está tentando obter. Exigir sessão tornaria o 2FA impossível de completar: a armadilha de aplicar a mesma guarda em toda rota "porque é mais seguro". O que protege é o par (TICKET de desafio, código): o ticket é assinado pelo Login e prova que a senha bateu neste fluxo (um user_id cru não abre mais nada — rodada 3, H-1), e o código vale 30 segundos. Sem esta rota, o login de quem tem 2FA ficava sem passo seguinte — a conta ficava inacessível pelo painel.

Respostas

códigosignificado
200OK
401`TwoFactorFailed` (código errado), `TwoFactorSuperseded` (código substituído por um mais novo — use o e-mail mais recente), `TwoFactorCodeExpired` (código vencido — peça outro) ou `TwoFactorExpired` (o ticket do login venceu — recomece o login).
429`TooManyAttempts` — freio do código; `Retry-After` (segundos) e `Details.RetryAfterSeconds` quando o prazo é conhecido.
502`InternalError` — o auth não respondeu.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/2fa' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/2fa/disable

Troca o app pelo e-mail (não desliga o 2º fator).

Onde é usadatela de segurança.
Efeitoso login deixa de exigir o segundo fator. Exige a SENHA, e não o código: aceitar o próprio TOTP para removê-lo faria o fator se autorizar sozinho.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X POST 'https://api.asender.net/api/auth/2fa/disable' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/2fa/resend

Reenvia o código de e-mail do 2º fator (método `email`).

Público como o /api/auth/2fa: a identidade vem do TICKET de desafio (corpo {TwoFactorToken}). 401 sem ticket; 429 no cap de emissões.

Onde é usadabotão "reenviar" da tela /2fa (método 'email').
Efeitosum e-mail (o guard do auth decide se sai fora de prd); rate-limited no auth (cap por janela). # PÚBLICA, pelo mesmo motivo do /api/auth/2fa Quem está no passo do código ainda não tem sessão. O que protege é o TICKET de desafio (prova a senha deste login) — sem ele, 401. O 429 do upstream é repassado como 429: quem pediu demais precisa saber que basta esperar.

Respostas

códigosignificado
200Código reenviado.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
429Muitos pedidos de código.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/2fa/resend' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/2fa/setup

Inicia a ativação do segundo fator.

Onde é usadatela de segurança.
Efeitosgrava o segredo PENDENTE de confirmação. # A senha é a invariante do piso 17, e ela para AQUI se faltar Para mexer num fator é preciso apresentar um fator diferente dele. O serviço recusa senha vazia, e o gateway recusa antes — não por desconfiança do serviço, mas porque um corpo sem senha é pedido malformado, e mandá-lo adiante gastaria uma viagem para receber a mesma recusa.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X POST 'https://api.asender.net/api/auth/2fa/setup' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/2fa/verify

Confirma a ativação do segundo fator.

Onde é usadatela de segurança, depois de ler o QR.
Efeitoso login passa a EXIGIR o segundo fator.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X POST 'https://api.asender.net/api/auth/2fa/verify' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/ativacao/estado

Passo da ativação obrigatória do 2º fator (conta gerenciada, primeiro acesso).

Pública: quem chega não tem sessão. Credencial = Ticket de ativação (o EnrollmentToken do login), que NÃO vale como sessão. 200 {Etapa, MustReset}.

Respostas

códigosignificado
200Etapa `senha` ou `fator`.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`ActivationExpired` — refazer o login.
409`NothingToActivate`.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/ativacao/estado' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/ativacao/senha

Troca a senha temporária (passo 1 da ativação).

Respostas

códigosignificado
200OK
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`ActivationExpired`.
409`NothingToActivate`.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/ativacao/senha' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/ativacao/totp/confirm

Confirma o código do app e devolve a sessão plena (passo 3).

Respostas

códigosignificado
200`{Token, UserId, ExpiresAt, RecoveryCodes}`.
401`TwoFactorFailed` ou `ActivationExpired`.
409`PasswordChangeRequired` ou `NothingToActivate`.
429Freio do 2º fator.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/ativacao/totp/confirm' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/ativacao/totp/setup

Gera o segredo/QR do app autenticador (passo 2). Exige a senha já trocada.

Respostas

códigosignificado
200`{Secret, OtpauthUrl}`.
401`ActivationExpired`.
409`PasswordChangeRequired` ou `NothingToActivate`.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/ativacao/totp/setup' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/login

Autentica e devolve o token de sessão.

Corpo aceito em snake_case ({"email","password"}) porque é o que o asender_auth_api define e o proxy repassa; a RESPOSTA é PascalCase.

Com 2FA habilitado a resposta é 200 com Token:"" e TwoFactorRequired:true — explícito, para o chamador não confundir com upstream quebrado.

Respostas

códigosignificado
200Sessão criada, ou 2FA pendente.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Conta bloqueada ou desabilitada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/login' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/logout

Revoga a sessão do Bearer apresentado. IDEMPOTENTE.

Revoga no asender_auth_api; a validação de sessão passou a consultar o estado, então o token deixa de valer (401 no /api/auth/me seguinte).

Esta rota fica FORA do grupo SessionAuth, de propósito — é o único caso em /api/*. Dentro do grupo, o segundo logout (retry, aba duplicada, refresh) morria em 401 no middleware, quebrando a idempotência de uma operação que descreve um ESTADO desejado ("estar fora"), não uma transição.

Consequência observável, e é por isso que a classe declarada é bearer-unverified e não session: o handler exige o header Authorization (sem ele, 401) mas não valida a sessão. Qualquer string não vazia como Bearer — inclusive uma API key ou lixo — recebe 200. Não abre nada (revogar um token que não existe é no-op no asender_auth_api), mas o 401 desta rota vem do HANDLER, não do middleware: quem for auditar a superfície não pode contá-la como autenticada.

Onde é usadachamado pelas rotas /api/auth/logout dos DOIS frontends (dashboard e backoffice), que depois apagam o próprio cookie HttpOnly.
Entradasheader Authorization: Bearer <jwt de sessão>.
Saídas200 {Status:"ok"} com a sessão revogada; 401 sem Bearer; 502 LogoutFailed quando o asender_auth_api não confirmou.
Efeitosmarca auth.sessions.revoked_at no asender-auth. DEFEITO QUE ISTO FECHA (mesma família do item 17 do _INTEGRACAO-pendente): o handler logava a falha em nível Warn e respondia **200 {"Status":"ok"} assim mesmo. A premissa embutida era "logout é best-effort, o cliente só precisa apagar o cookie" — errada pelo mesmo motivo que a do backoffice: com a revogação por sid no asender_auth_api, é a resposta desta rota que diz se a sessão morreu ou não. Afirmar ok sem revogar dá ao chamador (e à tela) a garantia de que a sessão acabou quando ela continua aceita, e ainda apaga o rastro: quem lê 200 não procura o Warn no log. Por que 502 e não 500: a falha é do upstream (asender-auth fora, ou erro de banco na revogação), não desta camada — mesmo tratamento dado a todo erro de upstream do BFF. Falta de credencial continua sendo 401 do próprio handler: esta rota fica FORA do grupo SessionAuth de propósito (ver server.go).

Respostas

códigosignificado
200Revogado, ou já estava — inclusive para um Bearer que nunca foi sessão.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/logout' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/auth/me

Principal da sessão.

TenantId vem do asender_auth_api e costuma vir vazio — a resolução real do tenant é feita por rota (resolveSessionTenant), a partir dos vínculos no core. Não use este campo como escopo.

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.

Exemplo

curl -X GET 'https://api.asender.net/api/auth/me' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/auth/me

Altera o NOME do usuário da sessão.

Só Name. Email no corpo → 400 UseEmailsEndpoint (achado F1 da "Minha conta"): o e-mail só muda por /api/me/emails*, com reautenticação e verificação. Campo desconhecido → 400.

Onde é usadatela de perfil do painel.
Efeitosescreve no asender_auth_api. # E-mail não muda por aqui (achado F1 do plano da "Minha conta") Este PATCH trocava o e-mail PRIMÁRIO sem reautenticação: sessão sequestrada → troca o e-mail → "esqueci a senha" chega ao atacante. O e-mail agora só muda pelo fluxo /api/me/emails* (reauth por fator ≠ senha, verificação do endereço novo, aviso a todos os verificados). Email no corpo → 400 UseEmailsEndpoint, antes de qualquer hop — inclusive junto com um Name válido (aceitar o nome e ignorar o e-mail em silêncio faria o painel achar que trocou).

Respostas

códigosignificado
200OK
400`UseEmailsEndpoint` (veio `Email`), `ValidationError` (Name vazio) ou campo desconhecido.
401credencial ausente ou inválida
502auth indisponível.

Exemplo

curl -X PATCH 'https://api.asender.net/api/auth/me' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/auth/me/emails

E-mails de recuperação do usuário logado.

O sub vem SEMPRE da sessão (anti-IDOR): não há como listar os e-mails de outro usuário. Repassa ao asender_auth_api /v1/users/{sub}/emails.

Respostas

códigosignificado
200OK (corpo repassado do asender_auth_api).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/auth/me/emails' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/auth/me/emails

Acrescenta um e-mail de recuperação ao usuário logado.

O sub vem da sessão. Corpo repassado ao asender_auth_api; JSON malformado é recusado AQUI com 400 (não vira 502).

Respostas

códigosignificado
200OK (status/corpo repassados do asender_auth_api).
400Corpo não é JSON válido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/me/emails' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/auth/me/emails/{id}

Remove um e-mail de recuperação do usuário logado.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK (status/corpo repassados do asender_auth_api).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X DELETE 'https://api.asender.net/api/auth/me/emails/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/auth/me/emails/{id}/primary

Promove um e-mail do usuário logado a primário.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK (status/corpo repassados do asender_auth_api).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/me/emails/id_AQUI/primary' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/me/emails/{id}/verify/resend

Reenvia a verificação de um e-mail do usuário logado.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK (status/corpo repassados do asender_auth_api).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/me/emails/id_AQUI/verify/resend' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/me/verify/resend

Reenvia o e-mail de verificação do usuário da sessão.

Onde é usadaaviso "confirme seu e-mail" do painel.
Efeitosum e-mail (o guard do auth decide se ele sai fora de prd). O 429 do upstream é REPASSADO como 429: quem pediu demais precisa saber que basta esperar, e um 502 aqui mandaria a pessoa procurar defeito onde não há.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X POST 'https://api.asender.net/api/auth/me/verify/resend' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/passkey/login/finish

Termina o login com chave de acesso e devolve a sessão (formato do /api/auth/2fa).

Respostas

códigosignificado
200`{Token, UserId, ExpiresAt}` — o authfront grava o cookie HttpOnly.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`PasskeyRecusada` (qualquer recusa da credencial — sem oráculo) ou `DesafioExpirado`.
429Teto por IP.
502auth indisponível ou sem token na resposta.
503`PasskeyIndisponivel`.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/passkey/login/finish' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/passkey/login/start

Começa o login SEM senha com chave de acesso (público, teto por IP do auth).

Corpo {origem} (o authfront põe a origem do browser, conferida contra o host). Sem e-mail: o autenticador escolhe a conta. Resposta do auth repassada: {desafio_id, opcoes}.

Onde é usadabotão "Entrar com chave de acesso" do authfront (via route handler server-side).

Respostas

códigosignificado
200`{desafio_id, opcoes}`.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
429Teto por IP.
502auth indisponível.
503`PasskeyIndisponivel`.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/passkey/login/start' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/password-reset

Inicia a recuperação de senha (resposta neutra, não vaza se a conta existe).

Onde é usadatela pública /forgot-password.
Efeitosum e-mail de recuperação (o guard do auth decide se sai fora de prd, piso 18). Nenhuma sessão é criada. # PÚBLICA e com resposta NEUTRA Quem pede recuperação não tem sessão — é o ponto. A resposta é sempre a mesma, exista ou não o e-mail: dizer "esse e-mail não existe" entregaria a base de usuários a quem testa endereços (enumeração). O token do link NUNCA volta no corpo — o AuthClient.PasswordResetRequest o descarta ainda no cliente; quem recebe o link é a caixa de e-mail. Vive no MESMO teto por IP das demais rotas públicas de auth (server.go), que fecha o brute-force/spam deste endpoint.

Respostas

códigosignificado
200aceito (mesma resposta exista ou não o e-mail)
400e-mail ausente ou malformado

Exemplo

curl -X POST 'https://api.asender.net/api/auth/password-reset' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/password-reset/confirm

Redefine a senha pelo token do link (+ código 2FA quando a conta tem segundo fator).

Onde é usadatela pública /reset-password, que recebe ?token= do link do e-mail.
Efeitosgrava a senha nova e DERRUBA todas as sessões abertas do usuário (lá no auth). Nenhuma sessão nova é criada — a pessoa faz login em seguida. # PÚBLICA, e os 401 são separados pelo Code Quem redefine a senha não tem sessão (esqueceu a senha). O auth responde 401 para TRÊS situações distintas — token inválido/expirado, 2FA exigido e 2FA incorreto — cada uma com um Code próprio no envelope. Separá-las é o que permite à tela pedir o código quando falta, sem transformar "código errado" em "link inválido". Senha fraca vem como 400 (mesma política do cadastro: mínimo 12 + HIBP) e sobe como 422 com a razão, para a pessoa saber o que corrigir.

Respostas

códigosignificado
200senha redefinida; sessões do usuário derrubadas
400token ou nova senha ausentes
401token inválido/expirado, 2FA exigido ou 2FA incorreto (distinguidos pelo Error.Code)
422nova senha não atende à política (mínimo 12 + HIBP)

Exemplo

curl -X POST 'https://api.asender.net/api/auth/password-reset/confirm' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/password-reset/send-otp

Dispara o código do 2º fator (canal e-mail) do reset em curso.

Pública como o resto do fluxo de reset: quem esqueceu a senha não tem sessão. Recebe o Token do reset em andamento e envia o OTP ao e-mail primário do dono do token. Resposta neutra — não confirma se o token vale. Disparo de e-mail (piso 18) e alvo de spam/amplificação, então no MESMO teto de rate-limit por IP das outras rotas de auth públicas.

Onde é usadatela pública /reset-password, ao montar com um ?token= válido e no botão "reenviar".
Efeitosum e-mail com o código (o guard do auth decide se sai fora de prd, piso 18). Nenhuma sessão é criada. # PÚBLICA, resposta NEUTRA, no MESMO teto por IP Como o resto do reset, é pública e responde sempre igual (não confirma se o token/conta existe — anti-enumeração). Vive no rate-limit por IP das rotas públicas de auth (server.go), que — somado ao cap de reenvio do próprio auth — impede que vire amplificador de e-mail (piso 18). Falha do upstream vira 502 (idêntico para qualquer token, não vaza enumeração) e vai ao log.

Respostas

códigosignificado
200Aceito (resposta neutra `{"Status":"sent"}`; não confirma o token).
400Token ausente.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/password-reset/send-otp' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/register

Cria o usuário e, quando pode, o tenant raiz dele.

O status distingue os dois desfechos, e isso é contrato.

201 = usuário E conta criados. 202 = usuário criado, conta não — com um Reason do conjunto fechado DomainClaimed | TenantNameTaken | ProvisioningFailed | Invited (cadastro por convite, sem equipe de propósito: o próximo passo é o aceite). O cliente ramifica pelo Reason e leva a pessoa ao onboarding; tratar 202 como sucesso liso produz conta ÓRFÃ (relato do dono, 2026-09-17): usuário autenticado que não pertence a conta nenhuma, invisível para qualquer admin.

DomainClaimed significa que o domínio do e-mail pertence a uma conta que já o verificou. Não nasce workspace paralelo: nasce um pedido de entrada, e AccessRequestId o identifica.

O campo Note em prosa saiu: texto serve para mostrar, não para ramificar.

Respostas

códigosignificado
201Usuário e conta criados.
202Usuário criado, conta NÃO. O `Reason` diz por quê e o cliente encaminha para o onboarding.
400`400 ValidationError` — JSON inválido, campo desconhecido, e-mail ou senha fora da regra. Com `ConviteToken`: `InviteInvalid` (convite inexistente, aceito ou expirado) ou `InviteEmailMismatch` (e-mail do cadastro diferente do convite). Nos dois, nenhum usuário é criado.
409`409 Conflict` — email já registrado, slug em uso.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/auth/register' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/auth/sessions

Dispositivos e sessões ativas do usuário.

Onde é usadatela de segurança do painel.
Efeitosuma leitura no asender-auth. O token repassado é o da REQUISIÇÃO, e é ele que define de quem é a lista. Não existe parâmetro de usuário: um ?user_id= seria a rota mais barata do sistema para descobrir de onde outra pessoa se conecta.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X GET 'https://api.asender.net/api/auth/sessions' \
  -H 'Authorization: Bearer SEU_TOKEN'
DELETE /api/auth/sessions/{id}

Revoga uma sessão específica.

Onde é usadabotão "remover" da tela de segurança.
Efeitosa sessão para de autenticar imediatamente. # Por que a sessão de suporte NÃO passa por aqui A sessão de suporte é somente leitura (ADR-0017), e o middleware de suporte já recusa escrita. Isto é uma escrita — e derrubar o dispositivo de um cliente durante uma sessão de suporte é exatamente o poder que o ADR tira de quem entra na conta alheia.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X DELETE 'https://api.asender.net/api/auth/sessions/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/auth/verify

Confirma o e-mail a partir do token do link.

Onde é usadatela /verify, com o token da URL.
Efeitoscarimba email_verified_at no auth. # Também é PÚBLICA Quem abre o link do e-mail pode estar em outro navegador, ou nem ter sessão. Exigir login para verificar o e-mail cria a dependência circular clássica — e a pessoa que mais precisa verificar é justamente a que ainda não conseguiu entrar.

Respostas

códigosignificado
200OK
401link inválido
409email_em_uso — o endereço já está verificado em outra conta (token intacto)

Exemplo

curl -X POST 'https://api.asender.net/api/auth/verify' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/auth/verify/resend

Reenvia o link de confirmação sem sessão (202 neutro, teto por IP).

Onde é usadatela /verify quando o link falhou e o navegador não tem sessão.
Efeitosno máximo um e-mail (guard do piso 18 no auth). # PÚBLICA, resposta NEUTRA, no MESMO teto por IP 202 idêntico exista ou não pendência para o endereço (o auth só envia se houver pendência viva). O 429 é por IP, igual para qualquer endereço.

Respostas

códigosignificado
202aceito (mesma resposta exista ou não pendência)
400e-mail ausente ou malformado
429teto por IP

Exemplo

curl -X POST 'https://api.asender.net/api/auth/verify/resend' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/invitations/{token}/preview

Prévia do convite pelo token do link — PÚBLICA, teto por IP.

Pública por desenho: quem abre o link do convite ainda não tem sessão. O token (CSPRNG) é a credencial — só o portador vê o e-mail do PRÓPRIO convite. Não devolve token, id da conta nem quem convidou. Está na allowlist publicRoutes do routes_test.go.

HasAccount diz se o e-mail do convite já tem conta (a tela oferece "Entrar" ou "Criar conta com este e-mail"). Só é consultado com Status: pending; nos outros estados, e quando o asender_auth_api não responde (degradação graciosa), vem null.

Onde é usadaa tela do link de convite dos fronts, ANTES de login/cadastro; registrada no grupo público com teto por IP em server.go.
Entradas{token} do caminho — o token do link.
Saídas200 {Email, TenantName, Role, Status, HasAccount}; 404 NotFound para token desconhecido; 502 quando o core não respondeu.
Efeitosuma leitura no core e, se pendente, uma no auth. Por que é pública: quem abre o link ainda não tem sessão. O token é CSPRNG e é a própria credencial do convite — só quem o recebeu por e-mail vê o e-mail do PRÓPRIO convite. Por isso a resposta não devolve o token, nem o id público da conta, nem quem convidou: só o necessário para a tela decidir o botão. HasAccount é complemento, não requisito (piso 10): auth fora → null, e a tela oferece os dois caminhos. Convite não pendente também leva null — não há decisão de botão a tomar, e não se consulta o auth à toa.

Parâmetros

nomeemobrigatóriodescrição
tokenpath sim

Respostas

códigosignificado
200Convite encontrado.
404`404 NotFound` — token desconhecido.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/invitations/token_AQUI/preview' \
  -H 'Authorization: Bearer SEU_TOKEN'

bff-configuracao

GET /api/flags

As feature flags avaliadas para a conta da sessão.

A conta vem do token verificado. Core fora = {Flags: null, Degradado: true}: a tela usa o padrão dela (piso 10).

Respostas

códigosignificado
200OK

Exemplo

curl -X GET 'https://api.asender.net/api/flags' \
  -H 'Authorization: Bearer SEU_TOKEN'

bff-dns

GET /api/tenants/{id}/dns/alteracoes

Histórico das alterações de DNS da conta.

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
zonaquery nãoId da zona.
paginaquery não
por_paginaquery não

Respostas

códigosignificado
200OK: `{alteracoes, total, pagina, por_pagina}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).
defaultErro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/dns/alteracoes' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/dns/conexoes

Lista as conexões Cloudflare da conta (nunca devolve o token).

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK: `{conexoes:[{id, provedor, rotulo, token_final, zonas, criado_por, criado_em}]}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/dns/conexoes' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/dns/conexoes

Conecta um token da Cloudflare à conta.

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store. Corpo {rotulo, token} (token de 20 a 512 caracteres). O serviço verifica o token na Cloudflare, lista as zonas que ele alcança, cifra e grava; o token nunca volta em resposta e nunca é logado (nem no BFF, nem no log de requisições da API: o campo token é redigido). Corpo até 64 KiB.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
201Criada: a conexão, sem o token.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).
defaultErro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/dns/conexoes' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/dns/conexoes/{cid}

Apaga uma conexão (o token cifrado é apagado).

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
cidpath simId da conexão.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200OK: `{apagada: true, id}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).
defaultErro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/dns/conexoes/cid_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/dns/zonas

Zonas de todas as conexões da conta.

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK: `{zonas:[{id, nome, conexao_id}], avisos:[{conexao_id, codigo}]}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/dns/zonas' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/dns/zonas/{zona}/registros

Registros de uma zona.

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
zonapath simId da zona (o `id` de `GET /v1/dns/zones`).
tipoquery não
nomequery não
paginaquery não
por_paginaquery não

Respostas

códigosignificado
200OK: `{registros, total, pagina, por_pagina}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).
defaultErro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/dns/zonas/zona_AQUI/registros' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/dns/zonas/{zona}/registros

Cria um registro (aceita `?seco=1`).

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store. Controles: ?seco=1 (plano sem alterar), X-Asender-Confirmar (nome do registro; MX/SPF/DMARC do apex) e Idempotency-Key, repassados ao serviço.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
zonapath simId da zona (o `id` de `GET /v1/dns/zones`).
secoquery nãoDevolve o plano sem tocar a Cloudflare.
X-Asender-Confirmarheader nãoNome completo do registro. **Obrigatório** no `DELETE` e em qualquer escrita em `MX`, SPF ou DMARC do apex (`428 ConfirmationRequired` sem ele). Um valor diferente do nome também é recusado.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200OK (plano com `seco`); sem `seco` o serviço responde 201 com `{registro}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).
defaultErro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/dns/zonas/zona_AQUI/registros' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/dns/zonas/{zona}/registros/{rid}

Apaga um registro (exige X-Asender-Confirmar).

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store. Exige X-Asender-Confirmar com o nome do registro (428 ConfirmacaoNecessaria sem ele); aceita ?seco=1 e Idempotency-Key.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
zonapath simId da zona (o `id` de `GET /v1/dns/zones`).
ridpath simId do registro (o `id` de `GET /v1/dns/zones/{zona}/records`).
secoquery nãoDevolve o plano sem tocar a Cloudflare.
X-Asender-Confirmarheader nãoNome completo do registro. **Obrigatório** no `DELETE` e em qualquer escrita em `MX`, SPF ou DMARC do apex (`428 ConfirmationRequired` sem ele). Um valor diferente do nome também é recusado.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200OK: `{apagado: registro}` ou o plano.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).
defaultErro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/dns/zonas/zona_AQUI/registros/rid_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/tenants/{id}/dns/zonas/{zona}/registros/{rid}

Altera parcialmente um registro (aceita `?seco=1`).

Painel de DNS (ADR-0131, A3). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin (deny-default; developer, viewer, suporte e papel indeterminado levam 403 AdminRole). Tenant = o {id} autorizado, ator = o usuário da sessão, origem = tela; nada disso vem do cliente. Status e corpo do serviço de DNS voltam como vieram (RFC 9457 com codigo). Cache-Control: no-store. Controles: ?seco=1, X-Asender-Confirmar e Idempotency-Key, repassados ao serviço.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
zonapath simId da zona (o `id` de `GET /v1/dns/zones`).
ridpath simId do registro (o `id` de `GET /v1/dns/zones/{zona}/records`).
secoquery nãoDevolve o plano sem tocar a Cloudflare.
X-Asender-Confirmarheader nãoNome completo do registro. **Obrigatório** no `DELETE` e em qualquer escrita em `MX`, SPF ou DMARC do apex (`428 ConfirmationRequired` sem ele). Um valor diferente do nome também é recusado.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200OK: `{registro}` ou o plano.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`403 AdminRole`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503dns_api fora do ar (`ServiceUnavailable`, com `Retry-After`).
defaultErro RFC 9457 (`application/problem+json`) do serviço de DNS, repassado com o `codigo` estável (ZonaNaoEncontrada, ForaDePrd, RegistroProtegido, ConfirmacaoNecessaria, LimiteExcedido, ...).

Exemplo

curl -X PATCH 'https://api.asender.net/api/tenants/id_AQUI/dns/zonas/zona_AQUI/registros/rid_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

bff-me

GET /api/me

Minha conta: usuário, fatores e reauth vigente (repasse de GET /v1/me).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/me' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/me

Troca o nome (repasse de PATCH /v1/me).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X PATCH 'https://api.asender.net/api/me' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/codigos-recuperacao

Gera 10 códigos de recuperação (reauth totp|email_otp).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{codigos:[10]}` no formato XXXX-XXXX-XXXX (auth MeCodigos), mostrados uma vez; os anteriores deixam de valer.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/codigos-recuperacao' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/me/convites

Convites pendentes/expirados para os e-mails VERIFICADOS do usuário.

Onde é usadaseção Convites da "Minha conta" (auth_front /conta/convites).

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
503`IdentityUnavailable` — auth fora (nunca lista vazia mentindo); `InvitationsUnavailable` — core fora.

Exemplo

curl -X GET 'https://api.asender.net/api/me/convites' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/me/convites/{id}/aceitar

Aceita convite por public_id (idempotente; herda acessos).

Onde é usadacartão do convite. Idempotente (duplo clique = mesmo member).
Efeitospertinência no core + herança de acessos no auth (falha da herança não desfaz o aceite — mesma regra do link) + linha de auditoria no log.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200Idempotente. `user_id` é o da SESSÃO e os e-mails são os verificados do auth — nada vem do corpo. Depois do aceite, a mesma herança de acessos do link (owner/admin herdam as ferramentas).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404Inexistente, de outro e-mail, id fora do formato `cvt_…` ou usuário sem e-mail verificado (mesma resposta).
409`Declined` ou `AlreadyAccepted`.
410`Expired` ou `Revoked`.
429`Throttling` — teto de 30 decisões de convite (aceitar+recusar) por usuário por hora, com `Retry-After`.
502core indisponível.
503`IdentityUnavailable` — auth fora.

Exemplo

curl -X POST 'https://api.asender.net/api/me/convites/id_AQUI/aceitar' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/convites/{id}/recusar

Recusa convite (idempotente).

Onde é usadacartão do convite. Idempotente.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
204Recusado (idempotente).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404Inexistente, de outro e-mail ou id fora do formato (mesma resposta).
409`AlreadyAccepted`.
429`Throttling` — mesmo teto do aceitar (30/h por usuário, somados).
502core indisponível.
503`IdentityUnavailable` — auth fora.

Exemplo

curl -X POST 'https://api.asender.net/api/me/convites/id_AQUI/recusar' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/me/convites/contagem

Quantos convites pendentes (badge). Falha → `{pendentes:null}` 200.

Onde é usadabadge do AppHeader e faixa do hub (Frente E), em polling de 60s. QUALQUER falha (auth, core, sessão) vira 200 {pendentes:null}: o badge some e nada mais quebra (piso 10). Duas idas leves (auth + COUNT indexado).

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.

Exemplo

curl -X GET 'https://api.asender.net/api/me/convites/contagem' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/me/emails

E-mails da conta.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/me/emails' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/me/emails

Adiciona e-mail — 202 neutro sempre (reauth totp se tiver, senão email_otp).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
202Neutro: `{ok:true}` exista ou não o endereço.
400`ValidationError` (endereço inválido) ou `InvalidRequest` (campo desconhecido).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
429`TooManyAttempts` — 10 e-mails adicionados por dia.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/emails' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/me/emails/{id}

Remove e-mail (reauth totp se tiver, senão email_otp).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X DELETE 'https://api.asender.net/api/me/emails/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/me/emails/{id}/primario

Torna o e-mail primário (mesma reauth).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/emails/id_AQUI/primario' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/emails/{id}/reenviar

Reenvia a verificação.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
204Link reenviado (ou nada a fazer: já verificado).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
422`EnderecoNaoRecebe` — o servidor do destinatário recusou o endereço de vez.
429`TooManyAttempts` — cap de reenvio por janela.
502auth indisponível, ou `EnvioRecusado` (recusa permanente por política do destino).

Exemplo

curl -X POST 'https://api.asender.net/api/me/emails/id_AQUI/reenviar' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/me/eventos

Trilha de segurança paginada (só `antes` e `limite` atravessam).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Parâmetros

nomeemobrigatóriodescrição
antesquery não
limitequery não

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
400`InvalidRequest` — cursor `antes` ou `limite` inválido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/me/eventos' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/me/fatores

Inventário de fatores + remoções pendentes (leitura).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{fatores:{senha,email_otp,totp,codigos_restantes,passkeys}, remocoes_pendentes:[{id,fator,executar_em}]}` (auth MeFatores; array vazio quando não há).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404Rota fora da allowlist do contrato.
502auth indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/me/fatores' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/me/passkeys

Chaves de acesso (passkeys) da conta (leitura; sem chave pública nem credential id).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{passkeys:[{id,nome,criado_em,usado_em,sincronizada,rp_id}]}` (auth MePasskeys).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`SessaoNecessaria` (credencial sem sessão do auth).
404Rota fora da allowlist do contrato.
502auth indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/me/passkeys' \
  -H 'Authorization: Bearer SEU_TOKEN'
DELETE /api/me/passkeys/{id}

Remove uma chave de acesso (reauth email_otp|totp|codigo_recuperacao; último forte agenda 24h).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{removida:true}` — saiu na hora.
202`{id, fator:"passkey", agendado_para, passkey_id}` — último fator forte: remoção em 24h, cancelável.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` ou `ImpersonationReadOnly`.
404Inexistente ou de outra pessoa (indistinguíveis), ou rota fora da allowlist.
409`MinimoDeFatores`.
502auth indisponível.

Exemplo

curl -X DELETE 'https://api.asender.net/api/me/passkeys/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/me/passkeys/{id}

Renomeia uma chave de acesso (sem reauth).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{passkey}` (repassado).
400`ValidationError`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`SessaoNecessaria` ou `ImpersonationReadOnly`.
404Inexistente ou de outra pessoa (indistinguíveis), ou rota fora da allowlist.
502auth indisponível.

Exemplo

curl -X PATCH 'https://api.asender.net/api/me/passkeys/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/passkeys/registro/finish

Grava a chave de acesso (resposta do autenticador).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
201`{passkey}` (repassado).
400`DesafioExpirado`, `PasskeyRecusada` ou `ValidationError`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` ou `ImpersonationReadOnly`.
404Rota fora da allowlist do contrato.
409`PasskeyJaRegistrada`.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/passkeys/registro/finish' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/passkeys/registro/start

Começa o cadastro de uma chave de acesso (reauth email_otp|senha|totp|passkey).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{desafio_id, opcoes}` para navigator.credentials.create (repassado).
400`OrigemRecusada` / corpo inválido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Details.metodos_aceitos`/`metodos`) ou `ImpersonationReadOnly`.
404Rota fora da allowlist do contrato.
502auth indisponível.
503`PasskeyIndisponivel`.

Exemplo

curl -X POST 'https://api.asender.net/api/me/passkeys/registro/start' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/reauth/confirm

Confirma a reautenticação na sessão (repasse; 429 com Retry-After).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/reauth/confirm' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/reauth/start

Inicia a reautenticação (repasse).

Corpo {"metodo": "...", "email_id": "<uuid opcional>"} — email_id só para email_otp: o e-mail VERIFICADO que recebe o código (conferido no auth: dono + verificado; outro → 400 MetodoIndisponivel). Resposta 202 (repassada): {metodo, destinos?: [{email_id, mascarado, primario}], enviado_para?} — destinos/enviado_para só para email_otp; primário primeiro, o 1º é o padrão.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
202Prova preparada; `destinos` e `enviado_para` para `email_otp`; `desafio_id` e `opcoes` (WebAuthn, base64url) para `passkey` (formato no description).
400`MetodoIndisponivel` (método que a conta não tem, ou `email_id` alheio/pendente/inexistente).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
429`TooManyAttempts` — cap de códigos por janela.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/reauth/start' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/me/remocoes/{id}

Cancela a remoção agendada.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X DELETE 'https://api.asender.net/api/me/remocoes/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/me/senha

Troca a senha (reauth senha atual|email_otp|totp|passkey|codigo_recuperacao — P4).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/senha' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/me/sessoes

Sessões vivas do usuário.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/me/sessoes' \
  -H 'Authorization: Bearer SEU_TOKEN'
DELETE /api/me/sessoes/{id}

Revoga uma sessão própria.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X DELETE 'https://api.asender.net/api/me/sessoes/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/me/sessoes/encerrar-outras

Encerra as outras sessões (qualquer reauth).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200Corpo do contrato §4.2 do auth, repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/sessoes/encerrar-outras' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/me/totp

Agenda a remoção do TOTP em 24h (reauth email_otp|codigo_recuperacao).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
202`{id, fator, agendado_para}` (auth MeRemocaoAgendada); idempotente.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
409`NotEnabled` ou `MinimoDeFatores` (ficaria com menos de 2 fatores).
502auth indisponível.

Exemplo

curl -X DELETE 'https://api.asender.net/api/me/totp' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/me/totp/confirm

Liga o TOTP (reauth email_otp|senha).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{ok, codigos_recuperacao}` (auth MeTOTPConfirmado): 10 códigos XXXX-XXXX-XXXX na 1ª ativação, vazio se já havia códigos vivos.
400`InvalidCode`, `TOTPNaoIniciado` ou `InvalidRequest`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
409`AlreadyEnabled`.
429`TooManyAttempts` (freio por usuário, o mesmo do login).
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/totp/confirm' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/me/totp/setup

Gera TOTP pendente (reauth email_otp|senha).

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Respostas

códigosignificado
200`{otpauth_url, secret}` (auth MeTOTPSetup), repassado sem reinterpretação.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ReauthRequired` (com `Error.Details.metodos_aceitos`, `Error.Details.metodos` — cada método da política com `disponivel` e o `motivo` do indisponível, ex. `EmailNaoVerificado` (P5) — e, quando `email_otp` é aceito, `Error.Details.destinos` [{email_id, mascarado, primario}]), `ReauthIndisponivel`, `SessaoNecessaria` (leitura de segurança — e-mails, fatores, sessões, trilha — com credencial sem sessão do auth) ou `ImpersonationReadOnly` (sessão de suporte não escreve).
404Rota fora da allowlist do contrato, ou recurso inexistente/alheio.
409`AlreadyEnabled` (app já ativo).
502auth indisponível.

Exemplo

curl -X POST 'https://api.asender.net/api/me/totp/setup' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

bff-messaging

GET /api/campaigns

Campanhas do tenant, com status e progresso.

Produto marketing (exige entitlement). Tenant resolvido da sessão. A query aceita SÓ limit (allowlist) — parâmetro fora da lista é 422 nomeando-o, nunca listagem sem filtro em silêncio.

Onde é usadadashboard de marketing.
Entradaslimit (allowlist). Parâmetro fora da lista é 422 nomeando-o, nunca listagem sem filtro em silêncio.
Saídas200 com {Campaigns:[...]} (array, nunca null).

Parâmetros

nomeemobrigatóriodescrição
limitquery não

Respostas

códigosignificado
200OK — `{Campaigns:[...]}` (array, nunca null).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/campaigns' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/campaigns

Cria uma campanha em rascunho (draft).

NÃO dispara nada — a campanha nasce em draft. O envio real é do worker (piso 18), no horário do schedule.

Remetente obrigatório (2026-10-02): FromAddress vazio numa campanha de e-mail é 422 RemetenteObrigatorio (antes virava o remetente padrão e a campanha saía com todos os lotes recusados). O seletor GET /api/campaigns/remetentes lista os domínios aceitos.

Remetente: FromAddress de e-mail só de domínio VERIFICADO da conta (SPF+DKIM, no e-mail corporativo). Recusa repassada INTACTA do asender_mkt_api: 422 RemetenteNaoVerificado (mensagem para o usuário, com o domínio) e 503 VerificacaoIndisponivel — o front mostra aviso com o atalho para Admin → Domínios.

Onde é usadawizard "nova campanha" da tela de campanhas.
Saídas201 com {Campaign}; 422 em validação (campo ruim ou template/lista inexistente). NÃO dispara nada — campanha nasce em draft.
Efeitosescreve em messages.campaigns.

Respostas

códigosignificado
201Criada — `{Campaign}` em draft.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
503`VerificacaoIndisponivel` — o domínio do remetente não pôde ser conferido agora; nada foi gravado.

Exemplo

curl -X POST 'https://api.asender.net/api/campaigns' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/campaigns/{id}

Detalhe de uma campanha, com contadores.

Campanha de outro tenant também é 404 — não confirma existência alheia. Pausada pelo scheduler, traz PauseReason: remetente_nao_verificado (o domínio do From perdeu a verificação antes do disparo; nada saiu com outro remetente) ou a frase de dependência ausente.

Onde é usadatela de detalhe/progresso da campanha.
Saídas200 com {Campaign}; 404 quando o id não existe NO TENANT — campanha de outro tenant também é 404, para não confirmar existência alheia (§22.8).

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK — `{Campaign}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/campaigns/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/campaigns/{id}

Edita uma campanha (só enquanto em draft ou paused).

A garantia "só em draft/paused" é do repo do asender_mkt_api, atômica no WHERE. A pausada é editável para o dono resolver o motivo (ex.: trocar o remetente cujo domínio perdeu a verificação) antes de reagendar.

Onde é usadaedição na tela de campanhas.
Saídas200 com {Campaign}; 404 se não existir; 422 se não estiver em draft ou se algum campo/ref for inválido.
Efeitosescreve em messages.campaigns.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK — `{Campaign}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422Fora de draft, campo/ref inválido, ou `RemetenteNaoVerificado` (domínio do From não verificado nesta conta).
503`VerificacaoIndisponivel` — o domínio do remetente não pôde ser conferido agora; nada foi gravado.

Exemplo

curl -X PATCH 'https://api.asender.net/api/campaigns/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/campaigns/{id}/cancel

Cancela a campanha (draft/scheduled/paused→cancelled). Sem corpo.

Onde é usadabotão "cancelar" da tela de campanhas.
Saídas200 com {Campaign}; 404 se não existir; 422 se o status atual não permitir cancelamento (ex.: já enviada).
Efeitosescreve em messages.campaigns.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK — `{Campaign}` em cancelled.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422Status atual não permite cancelamento (ex.: já enviada).

Exemplo

curl -X POST 'https://api.asender.net/api/campaigns/id_AQUI/cancel' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/campaigns/{id}/schedule

Agenda a campanha (draft/paused→scheduled). NÃO envia.

Piso 18: só marca a intenção — quem entrega no horário é o worker, com o guard de efeito externo. A borda valida a FORMA de ScheduledAt (RFC 3339) aqui, sem hop; o asender_mkt_api valida a REGRA (data futura, lista/ template presentes).

Onde é usadabotão "agendar" da tela de campanhas.
EntradasScheduledAt em RFC 3339 (ex.: 2026-09-10T14:00:00Z). A borda valida a FORMA aqui (data não-parseável é 422 sem hop); o asender_mkt_api é quem valida a REGRA (data futura, lista/template presentes).
Saídas200 com {Campaign}; 404 se não existir; 422 em data malformada, passada/ausente ou campanha fora de draft.
Efeitosescreve em messages.campaigns.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK — `{Campaign}` em scheduled.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422Data malformada/passada/ausente, campanha fora de draft/paused, ou `RemetenteNaoVerificado` — o domínio do From perdeu a verificação desde o rascunho (fica como está). Reagendar a `paused` é retomá-la: `PauseReason` é limpo e os lotes já aceitos não saem de novo.
503`VerificacaoIndisponivel` — o domínio do remetente não pôde ser conferido agora.

Exemplo

curl -X POST 'https://api.asender.net/api/campaigns/id_AQUI/schedule' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/campaigns/remetentes

Domínios verificados que podem assinar o remetente da campanha (seletor do wizard).

Desde 2026-10-02 o remetente da campanha de e-mail é OBRIGATÓRIO e só de domínio verificado (SPF e DKIM). Esta rota alimenta o seletor: os domínios verificados da conta da SESSÃO (nunca de outra conta), lidos no e-mail corporativo. Lista vazia é válida — a tela mostra LinkDominios para verificar um domínio.

Onde é usadawizard de campanha do asender_mkt_front.
Saídas200 {Dominios:[...], LinkDominios} (lista vazia é válida); 503 VerificacaoIndisponivel repassado intacto com a fonte fora.
Efeitosuma chamada ao asender_mkt_api (que consulta o asender_mail_api).

Respostas

códigosignificado
200Os domínios verificados.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503`503 VerificacaoIndisponivel` — a verificação do domínio do remetente não respondeu. Fail-closed: nada foi enfileirado. Repita depois do `Retry-After` (com a mesma `Idempotency-Key`).

Exemplo

curl -X GET 'https://api.asender.net/api/campaigns/remetentes' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/contacts

Audiência do tenant.

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404).

Onde é usadatela de audiência do dashboard.
Entradasq, list_id, tag, cursor, limit (allowlist).
Saídas200 com {Contacts, NextCursor, HasMore}.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
qquery não
list_idquery não
tagquery nãoFiltra contatos que têm esta tag.
cursorquery não
limitquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/contacts' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/contacts

Cria um contato.

Subscribed ausente vira true — opt-in é o default do cadastro manual.

Onde é usadaformulário de novo contato da tela de audiência.
Saídas201 com {Contact}; 422 em validação (email/telefone inválidos).
Efeitosescreve em messages.contacts.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).

Respostas

códigosignificado
201Criado.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X POST 'https://api.asender.net/api/contacts' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/contacts/{id}

Remove o contato do tenant.

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404). A semântica de exclusão é do mkt_api; contato de outro tenant responde 404.

Saídas204; 404 fora do tenant.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath sim

Respostas

códigosignificado
204Removido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X DELETE 'https://api.asender.net/api/contacts/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/contacts/{id}

A ficha de UM contato da audiência, com o consentimento por canal.

Repassa GET /v1/contacts/{id} do mkt_api (produto marketing). Contato de outro tenant → o mesmo 404 de inexistente.

Onde é usadaa ficha /audience/contacts/{id} do asender_mkt_front, aberta pela busca global (hub e cabeçalho do Marketing); registrada em server.go no grupo exigirMarketing.
Saídas200 {Contact}; 401 sem sessão; 404 se o contato não é do tenant (o mesmo 404 de inexistente); 502 com o upstream fora.
Efeitosnenhum.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath sim

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/contacts/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/contacts/{id}/lists

Listas a que o contato pertence.

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404).

Saídas200 {Lists}; 404 fora do tenant.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath sim

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/contacts/id_AQUI/lists' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/contacts/bulk

Importa até 5000 contatos, com upsert pela identidade natural.

Import parcial é resultado legítimo: itens ruins voltam em Errors com o índice de origem, e o lote não é invalidado por causa deles. Lote vazio ou acima do teto é 422.

Onde é usadaimportador CSV da tela de audiência.
Saídas200 com {Created, Updated, Errors, Contacts, Suppressed, AddedToList, Tagged}; 422 quando a lista vem vazia ou acima do teto.
Efeitosescreve em messages.contacts.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).

Respostas

códigosignificado
200Lote processado.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X POST 'https://api.asender.net/api/contacts/bulk' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/contacts/tags

Remove tags de contatos (DELETE com corpo; até 500 ids e 10 tags).

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404).

Onde é usadaseleção múltipla da tela de audiência.
Saídas200 {Applied|Removed, NotFound}; 422 em corpo vazio/acima do teto.
Efeitosescreve nas tags dos contatos (no mkt_api).

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).

Respostas

códigosignificado
200OK
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X DELETE 'https://api.asender.net/api/contacts/tags' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/contacts/tags

Aplica tags a contatos (até 500 ids e 10 tags).

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404). NotFound traz os ids que não existem no tenant.

Onde é usadaseleção múltipla da tela de audiência.
Saídas200 {Applied|Removed, NotFound}; 422 em corpo vazio/acima do teto.
Efeitosescreve nas tags dos contatos (no mkt_api).

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).

Respostas

códigosignificado
200OK
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X POST 'https://api.asender.net/api/contacts/tags' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/lists

Listas de contatos com contagem de membros.

Quem pode ler (S128): quem acessa o produto marketing OU qualquer uma das ferramentas de despacho — emailmarketing, sns, zapify, push —, decidido pelo PDP do asender_auth_api (/v1/authz/decide), produto a produto nesta ordem; a primeira permissão basta. É a fonte do seletor de público das telas de e-mail, SMS, WhatsApp e push. Nenhum permitido, conta suspensa ou PDP fora sem decisão em cache ⇒ 404 (deny-default; 404 e não 403 para não virar oráculo). Só a LEITURA vale para sns/zapify/push. O resto da audiência (criar lista, membros, contatos, tags) exige marketing OU emailmarketing.

Onde é usadatela de audiência.
Saídas200 com {Lists}.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
cursorquery não
limitquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/lists' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/lists

Cria uma lista de contatos.

O upstream faz upsert por slug; este BFF responde sempre 201. Exige marketing OU emailmarketing (a LEITURA em GET /api/lists aceita também sns/zapify/push; a escrita, não).

Onde é usadatela de audiência.
Saídas201 com {List}; 422 em validação.
Efeitosescreve em messages.contact_lists.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).

Respostas

códigosignificado
201Criada (ou atualizada pelo slug).
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X POST 'https://api.asender.net/api/lists' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/lists/{id}

Remove a lista (os contatos permanecem).

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404).

Saídas204; 404 fora do tenant.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath sim

Respostas

códigosignificado
204Removida.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X DELETE 'https://api.asender.net/api/lists/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/lists/{id}/contacts

Membros de uma lista, paginados por cursor.

Repassa a paginação do serviço de mensageria sem cortar nada: enquanto HasMore for true, chame de novo com cursor=NextCursor até a última página. limit fora de 1..100 é 422 (nunca reduzido em silêncio). Substitui o uso de GET /api/contacts?list_id= com limit=500, que perdia os membros além do 500º. Lista de outro tenant responde 404.

Onde é usadaexpansão de lista da tela de audiência e do composer de envio do asender_mkt_front, via /api/proxy; registrada em server.go no grupo exigirMarketing.
Entradas{id} (public id da lista); query cursor, limit (allowlist; limit fora de 1..100 é 422 — nunca cortado sem aviso).
Saídas200 {Contacts, NextCursor, HasMore}; 401 sem sessão; 404 se a lista não é do tenant; 422 em query inválida; 502 com o upstream fora.
Efeitosnenhum (só leitura).

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath simPublic id da lista (`lst_<hex>`).
cursorquery não`NextCursor` da página anterior; omita na primeira.
limitquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/lists/id_AQUI/contacts' \
  -H 'Authorization: Bearer SEU_TOKEN'
DELETE /api/lists/{id}/members

Tira contatos de uma lista (DELETE com corpo; até 500 ids).

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404). NotFound traz os ids que não pertencem à lista no tenant.

Saídas200 {Removed, NotFound}; 404 lista fora do tenant; 422 corpo vazio ou acima de 500 ids.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath simPublic id da lista (`lst_<hex>`).

Respostas

códigosignificado
200OK
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X DELETE 'https://api.asender.net/api/lists/id_AQUI/members' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/lists/{id}/members

Anexa contatos a uma lista.

NotFound traz os ids que não existem no tenant — o servidor diz o que NÃO pôde fazer, em vez de descartar em silêncio. Idempotente.

Onde é usadatela de audiência (seleção múltipla).
Saídas200 com {Added}; 404 se a lista não é do tenant; 422 se a lista de ids vem vazia.
Efeitosescreve em messages.contact_list_members.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath simPublic id da lista (`lst_<hex>`).

Respostas

códigosignificado
200OK
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X POST 'https://api.asender.net/api/lists/id_AQUI/members' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/messages

Histórico de envios do tenant corrente.

Onde é usadatela de histórico do dashboard.
EntradasChannel/Status/Q/Cursor/Limit são aceitos em snake_case (channel, status, q, cursor, limit) e repassados por allowlist — nenhum outro parâmetro atravessa.
Saídas200 com {Messages, NextCursor, HasMore}; 422 em query malformada.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
channelquery não
statusquery não
qquery não
cursorquery não
limitquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/messages' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/messages/{id}

Detalhe do envio com timeline.

Onde é usadatela de detalhe do envio.
Saídas200 com {Message, Events}; 404 quando o id não existe NO TENANT — mensagem de outro tenant também é 404, para não confirmar existência (§22.8).

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
idpath simPublic id da mensagem (`msg_<hex>`).

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/messages/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/messages/send

Composer do dashboard — envia em qualquer canal.

Corpo em PascalCase, campo a campo espelhando o contrato interno. Campo desconhecido é 400 (DisallowUnknownFields), não silêncio.

Onde é usadacomposer da tela de envio do dashboard.
Saídas202 com {Messages, ReusedIdempotency}; 422 quando o asender_mkt_api recusa a validação (canal inválido, destinatário vazio, corpo ausente).
Efeitosescreve mensagens no banco de mensageria.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).

Respostas

códigosignificado
202Enfileirado.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422`422 RemetenteNaoVerificado` — o remetente (`Source`/`From`) de e-mail não é de domínio verificado nesta conta (inclui domínio verificado em OUTRA conta). `Message` para o usuário: o domínio e o link para Admin → Domínios. Nada é enfileirado (decisão de 2026-10-02; antes o e-mail saía com o remetente da plataforma). `422 ValidationError` — demais recusas do upstream (template sem variável, todos suprimidos, portão de qualidade…).
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503`503 VerificacaoIndisponivel` — a verificação do domínio do remetente não respondeu. Fail-closed: nada foi enfileirado. Repita depois do `Retry-After` (com a mesma `Idempotency-Key`).

Exemplo

curl -X POST 'https://api.asender.net/api/messages/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/push/devices

Devices de push do tenant, para o seletor da tela de disparo.

Fonte do seletor de dispositivos em /t/{conta}/send/push. Antes de existir, o caminho respondia 404 e o seletor ficava vazio mesmo com devices no banco — a tela levava o usuário a concluir que a conta não tinha dispositivo nenhum.

Onde é usadadashboard, tela /send/push.
Entradascursor e limit, na mesma allowlist dos outros List.
Saídas200 com {Devices, NextCursor, HasMore} (PascalCase, §51). Antes desta rota o caminho respondia 404 e a tela não tinha como listar: dava para disparar digitando o token à mão, mas não escolher um device existente.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
cursorquery não
limitquery não

Respostas

códigosignificado
200Página de devices, em PascalCase.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/push/devices' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/push/site-config

Pacote de integração de Web Push para o site do cliente.

Devolve, num JSON só, tudo que o site precisa para receber Web Push: chave pública VAPID do tenant, manifest.json, o service worker e o snippet de inscrição — os três já preenchidos com os valores desta instalação. Gerar no servidor evita o chamado clássico do integrador que esqueceu de substituir um <SUA_CHAVE_AQUI>.

O snippet leva só credencial PÚBLICA (M15, ADR-0057). A chave de site SiteKey (pk_push_…) é assinada para (tenant da sessão, origems pedidas) e só é aceita em POST /v1/push/site/devices, a partir dessas origens. A API key de servidor nunca entra no snippet. Mesmo (tenant, origens) gera sempre a mesma chave; trocar de domínio = gerar de novo.

Onde é usadatela de push do dashboard (botão de copiar/baixar cada arquivo).
Entradasorigem — repetível, as origens do site (1 a 10).
Saídas200 com {PublicKey, ApiBase, SiteKey, Origens, Manifest, ServiceWorker, Snippet}; 400 sem origem ou com origem inválida.
Efeitospode criar o par VAPID do tenant na primeira chamada (no core). ## A chave do snippet é PÚBLICA (M15) O snippet antigo mandava colar a API key de SERVIDOR num JS público. Agora ele leva uma chave de site (pk_push_…) que só registra inscrição de Web Push e só a partir das origens pedidas aqui — ver internal/sitekey. Sem origem declarada não há chave: uma chave sem allowlist valeria para qualquer página. ## Por que o servidor gera, em vez de documentar Os três arquivos dependem de dois valores que variam por instalação: a chave pública do tenant e a base da API. Documentação com <SUA_CHAVE_AQUI> produz exatamente um tipo de chamado — o do integrador que esqueceu de substituir, e cujo sintoma é "não chega notificação", sem erro em lugar nenhum. Gerando aqui, o que o cliente cola já está correto.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
origemquery simOrigem do site (`https://www.loja.com.br`), repetível, 1 a 10. Sem curinga, sem `null`, sem caminho. Canonizada (minúscula, sem porta padrão, sem barra final) e gravada DENTRO da chave de site.

Respostas

códigosignificado
200Artefatos prontos para o site.
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
503Segredo de assinatura da chave de site ausente.

Exemplo

curl -X GET 'https://api.asender.net/api/push/site-config' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tags

Tags do tenant com a contagem de contatos.

Exige marketing OU emailmarketing (PDP, nesta ordem; negado => 404).

Onde é usadafiltro/gestão de tags da ferramenta emailmarketing.
Entradaslimit (allowlist).
Saídas200 {Tags:[{Name,ContactCount}]}.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
limitquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/tags' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/templates

Templates do tenant.

Onde é usadadashboard.
Saídas200 com {Templates}.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
limitquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/templates' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/templates

Cria ou atualiza um template pelo par (tenant, slug).

Onde é usadaeditor de templates do dashboard.
Saídas200 com {Template}; 422 em validação do asender_messages.
Efeitosescreve em messages.templates.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).

Respostas

códigosignificado
200Gravado (upsert — 200 tanto na criação quanto na atualização).
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X POST 'https://api.asender.net/api/templates' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

bff-plataforma

GET /api/tenants/{id}/atividade

A trilha de ATIVIDADE da própria conta (admin/owner), ADR-0114.

Mesma leitura de /api/admin/atividade, mas o tenant_id enviado aos serviços é SEMPRE o {id} autorizado pela sessão; conta/tenant_id na query são ignorados. Conta alheia 404; papel sem governança (viewer/developer) 403.

Onde é usadaaba "Atividade" da tela /auditoria do admin.asender.
Efeitossó leitura.

Parâmetros

nomeemobrigatóriodescrição
idpath sim
atorquery não
servicoquery não
recurso_tipoquery não
recurso_idquery não
acaoquery não
dequery nãoRFC3339 ou AAAA-MM-DD.
atequery nãoRFC3339 ou AAAA-MM-DD (dia inclusivo).
limitequery não
cursorquery não
formatoquery nãocsv exporta (anexo; células que começam com = + - @ ganham aspa simples).

Respostas

códigosignificado
200OK
400filtro ou cursor inválido
403papel sem governança da conta
404conta alheia

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/atividade' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/supressao-global/consulta

A conta consulta se um e-mail está em supressão global (só leitura).

Onde é usadaselo "Supressão global" dos fronts de conta (mail e e-mail marketing).

Parâmetros

nomeemobrigatóriodescrição
idpath sim
emailquery sim

Respostas

códigosignificado
200suprimido, desde quando, origem e teste_disponivel_em
400e-mail inválido
404conta alheia

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/supressao-global/consulta' \
  -H 'Authorization: Bearer SEU_TOKEN'

bff-public

GET /api/public/planos

Catálogo único de planos — vitrine pública (ADR-0075).

Anônima por desenho: é o que a LP (www.asender.net) e o hub (/contratar) mostram — nome, descrição, preço, CTA e limites de cada plano publicado. Sem tenant, sem sessão, sem segredo. Está na allowlist publicRoutes do routes_test.go.

Fonte única: core.planos + core.plano_limites, lidas do core em GET /v1/planos. Corpo em snake_case (contrato ADR-0075), não o PascalCase do resto do /api — a mesma forma do core, tipada (campo inesperado do core não vaza).

  • preco_centavos: null = sob consulta (nunca 0).
  • limites traz todo domínio de core.plano_limites do plano; dia/mes

null = sem limite naquela escala.

Cache: memória do BFF por PLANOS_CACHE_TTL (60 s) e Cache-Control: public, max-age=60. Stale-on-error: com o core fora, serve a última cópia boa (warn no log com RequestId); só a primeira leitura sem cópia responde 503 (Cache-Control: no-store).

Teto por IP próprio: PUBLIC_RATE_LIMIT_PER_IP (120) por PUBLIC_RATE_LIMIT_WINDOW (1 min).

Onde é usadaLP (www, server-side com ISR) e hub /contratar. Anônima.

Respostas

códigosignificado
200Planos publicados, em `ordem`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` — o core não respondeu e não há cópia em cache. A LP cai no fallback estático dela.

Exemplo

curl -X GET 'https://api.asender.net/api/public/planos' \
  -H 'Authorization: Bearer SEU_TOKEN'

bff-reports

GET /api/reports/overview

Funil do período com quebra por canal.

Os três canais SEMPRE aparecem em ByChannel, zerados quando não houve tráfego. As chaves de ByChannel (email/sms/push) ficam em minúsculas porque são DADO, não nome de campo.

Taxas em [0,1], arredondadas a 4 casas, grampeadas no teto (a base pode ter bounced > sent, porque bounce não exige sent_at).

Onde é usadadashboard de relatórios (cards do topo).
Entradasfrom/to em ISO-8601 UTC; ausentes = últimos 30 dias (default resolvido pelo asender_mkt_api, não duplicado aqui).
Saídas200 com {Totals, Rates, ByChannel} — as chaves de ByChannel seguem sendo email/sms/push, porque ali são dado e não nome de campo.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
fromquery nãoISO-8601 (`YYYY-MM-DD` ou RFC 3339). Default: 30 dias antes de `to`.
toquery nãoISO-8601. Default: agora. **Fim EXCLUSIVO**; data pura cobre o dia inteiro.

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/reports/overview' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/reports/timeseries

Série temporal por bucket e canal.

Um ponto por (bucket, canal), buckets vazios inclusos. O campo continua se chamando Day mesmo com interval=hour — é o início do bucket; a granularidade vem em Period.Interval.

Este BFF aceita hour|day|week|month, mas o asender_mkt_api só implementa day|hour: week/month passam a validação daqui e são recusados com 422 pelo upstream.

Onde é usadadashboard de relatórios (gráfico principal).
Entradasfrom, to, channel (email|sms|push), interval (hour|day|week|month; default day). Valor fora da allowlist é 422 — filtro descartado em silêncio mente para quem consulta.
Saídas200 com {Points:[...]}.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
fromquery nãoISO-8601 (`YYYY-MM-DD` ou RFC 3339). Default: 30 dias antes de `to`.
toquery nãoISO-8601. Default: agora. **Fim EXCLUSIVO**; data pura cobre o dia inteiro.
channelquery não
intervalquery não`week` e `month` são aceitos aqui e recusados pelo upstream (422).

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/reports/timeseries' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/reports/top-templates

Ranking de templates por volume, com taxas.

limit aceito de 1 a 100 nesta borda; o teto REAL aplicado pelo repositório do asender_mkt_api é 50.

Onde é usadadashboard de relatórios (tabela lateral).
Entradasfrom, to, limit (1..100).
Saídas200 com {Templates:[...]}.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
fromquery nãoISO-8601 (`YYYY-MM-DD` ou RFC 3339). Default: 30 dias antes de `to`.
toquery nãoISO-8601. Default: agora. **Fim EXCLUSIVO**; data pura cobre o dia inteiro.
limitquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/reports/top-templates' \
  -H 'Authorization: Bearer SEU_TOKEN'

bff-resumo

GET /api/busca

Busca federada do hub: um termo procurado em todas as ferramentas da pessoa na conta.

O BFF pergunta ao PDP (/v1/authz/eu) quais ferramentas a pessoa acessa na conta (resolvida por pertinência; conta alheia → 404) e chama, em PARALELO e com prazo curto por ferramenta (BUSCA_PRAZO, default 1,5 s), as buscas que cada uma já faz — sempre com a credencial da PRÓPRIA pessoa: o Bearer da sessão (crm: /v1/contatos?q=, /v1/empresas?q=; pages: /v1/paginas filtrada, /v1/links?busca=; sns, zapify, push e emailmarketing: GET /v1/busca?q=&limit= → {templates, campaigns}; ferramentas novas: o endpoint comum GET /v1/busca?q=&limite= → {itens:[…]}) ou o caminho de serviço com o usuário verificado (mail: /mailboxes/minhas + /mailboxes/{id}/messages?todas=1&q=, até 3 caixas, com o PEP e as concessões do usuário no mail_api; marketing: /v1/contacts?q= e /v1/campaigns filtradas, o mesmo caminho de /api/contacts). Ferramenta lenta ou fora vem como {indisponivel: true} e o resto responde (piso 10); ferramenta que RECUSA (403/404) some; ferramenta sem resultado não aparece. Até 5 itens por fonte e 10 por ferramenta. caminho é sempre relativo à ferramenta, com o marcador {tenant} (o BFF descarta o resto).

Onde é usadaa busca do cabeçalho do hub (asender_plataforma_front, components/busca-do-app.tsx).
Erros400 termo; 401 sem sessão; 404 conta alheia (resolveSessionTenant — nunca 403, que seria oráculo); 502 se o PDP não responde (sem ele não há como saber o que a pessoa pode ver: nega).

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
qquery sim

Respostas

códigosignificado
200OK
400`ValidationError` — q com menos de 2 ou mais de 100 caracteres, ou com caractere de controle.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`NoTenant` — o usuário ainda não pertence a conta nenhuma.
404Conta alheia no `X-Asender-Tenant` (mesma resposta de inexistente).
429`Throttling` — teto por principal.
502core ou PDP (auth) indisponível — sem eles não há como saber o que a pessoa vê.

Exemplo

curl -X GET 'https://api.asender.net/api/busca' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/resumo

Home do hub: o resumo de cada ferramenta que a pessoa acessa na conta.

O BFF pergunta ao PDP (/v1/authz/eu) quais ferramentas a pessoa acessa na conta (resolvida por pertinência; Ferramenta que falha, expira ou responde fora do contrato vem como {ferramenta, nome, subdominio, indisponivel: true} — o resto responde normalmente. Se a ferramenta RECUSA o acesso (403 membro sem o produto, 404), o bloco fica ausente. atendimento não vira bloco: quem tem CRM e o produto atendimento no PDP recebe até 3 indicadores do chat DENTRO do bloco do CRM (modulo: "chat", link para as conversas do CRM; falha do atendimento só tira o chat, ADR-0080). Links de indicador são sempre caminhos relativos à ferramenta (o BFF descarta os demais).

Onde é usadaa home do hub (asender_plataforma_front, /t/<conta>).
Erros400 período inválido; 401 sem sessão; 404 conta alheia (via resolveSessionTenant — nunca 403, que seria oráculo); 502 se o PDP não responde (sem ele não há como saber o que a pessoa pode ver: nega).

Parâmetros

nomeemobrigatóriodescrição
periodoquery não

Respostas

códigosignificado
200OK
400`ValidationError` — periodo fora de 7d|30d|90d.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`NoTenant` — o usuário ainda não pertence a conta nenhuma.
404Conta alheia no `X-Asender-Tenant` (mesma resposta de inexistente).
429`Throttling` — teto por principal.
502core ou PDP (auth) indisponível — sem eles não há como saber o que a pessoa vê.

Exemplo

curl -X GET 'https://api.asender.net/api/resumo' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/resumo/marketing

Home do marketing: o `/v1/resumo` do asender_mkt_api, repassado sem reinterpretação.

Rota do produto marketing (PEP: sem acesso à ferramenta → recusa do PEP). O corpo segue o contrato comum de resumo em snake/minúsculo, sem PascalMap: {ferramenta, periodo{de,ate,chave}, indicadores[{chave,rotulo,valor,anterior,formato,link}], series[{chave,rotulo,tipo,pontos[{data,valor}]}]}.

Onde é usadarota dentro do PEP do produto marketing (server_resumo.go) — quem não acessa o marketing nem chega aqui.
Erros400 período; 404 conta alheia; os do mkt_api propagados por writeUpstreamError.

Parâmetros

nomeemobrigatóriodescrição
periodoquery não

Respostas

códigosignificado
200O payload do `/v1/resumo` do marketing, verbatim.
400`ValidationError` — periodo fora de 7d|30d|90d.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`NoTenant`.
404Conta alheia, ou sem acesso ao produto `marketing` (PEP).
429`Throttling` — teto por principal.
502mkt_api ou core indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/resumo/marketing' \
  -H 'Authorization: Bearer SEU_TOKEN'

bff-suporte

GET /api/suporte/chamados

Chamados do usuário (`escopo=usuario`) ou da conta (`escopo=conta`, exige governança).

A conta vem da pertinência (X-Asender-Tenant, slug ou id, só vale se o usuário pertence a ela; alheia → 404). Cada chamado sai com solicitante {id, nome} e conta {id, nome, slug}. Parâmetros fora da allowlist são descartados. Sessão impersonada: escopo usuario → lista vazia; escopo conta → 403 SemGovernanca.

Onde é usadaa busca do cabeçalho do hub (asender_plataforma_front, components/busca-do-app.tsx).
Erros400 termo; 401 sem sessão; 404 conta alheia (resolveSessionTenant — nunca 403, que seria oráculo); 502 se o PDP não responde (sem ele não há como saber o que a pessoa pode ver: nega).

Parâmetros

nomeemobrigatóriodescrição
escopoquery não
estadoquery não
categoriaquery não
produtoquery não
qquery não
limitequery não
antesquery nãoCursor opaco (`proximo` da página anterior).

Respostas

códigosignificado
200`{chamados: Chamado[], proximo?}`.
400`ValidationError` — filtro ou cursor inválido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`SemGovernanca` (escopo conta sem ser admin) ou `NoTenant`.
404Conta alheia.
502atendimento indisponível

Exemplo

curl -X GET 'https://api.asender.net/api/suporte/chamados' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/suporte/chamados

Abre um chamado.

Corpo estrito: campo desconhecido (tenant_id, solicitante_*, autor, estado, prioridade…) → 400 no BFF, sem tocar no atendimento. Qualquer membro da conta pode abrir (inclusive viewer). Idempotency-Key opcional ([A-Za-z0-9._:-]{1,128}); replay do mesmo usuário volta 200. Sessão impersonada → 403 ImpersonationReadOnly.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader não

Respostas

códigosignificado
200Replay da mesma `Idempotency-Key`.
201Chamado criado (`Location` aponta para `/api/suporte/chamados/{protocolo}`).
400`ValidationError` — campo desconhecido ou JSON inválido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ImpersonationReadOnly` ou `NoTenant`.
404Conta alheia.
409`LimiteDeChamadosAbertos` ou `IdempotencyConflict`.
422`ValidationError` do atendimento.
429`Throttling`.
502atendimento indisponível

Exemplo

curl -X POST 'https://api.asender.net/api/suporte/chamados' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/suporte/chamados/{protocolo}

Um chamado do usuário (ou da conta, para quem governa).

Chamado inexistente, de outro usuário ou de outra conta → 404 indistinguível. Protocolo fora do formato CH-AAAA-NNNNNN → 404 sem chamar o atendimento.

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim

Respostas

códigosignificado
200Chamado com `solicitante` e `conta`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
502atendimento indisponível

Exemplo

curl -X GET 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PUT /api/suporte/chamados/{protocolo}/avaliacao

Avalia o atendimento (nota 1..5), idempotente por ciclo.

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim

Respostas

códigosignificado
200Avaliação registrada.
400`ValidationError` — campo desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ImpersonationReadOnly`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`AindaNaoResolvido`.
422`ValidationError` — nota fora de 1..5.
502atendimento indisponível

Exemplo

curl -X PUT 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI/avaliacao' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/suporte/chamados/{protocolo}/fechar

Fecha o chamado.

Corpo {} (ou ausente). Já fechado → 409.

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim

Respostas

códigosignificado
200Chamado atualizado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ImpersonationReadOnly`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`ChamadoFechado`.
502atendimento indisponível

Exemplo

curl -X POST 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI/fechar' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/suporte/chamados/{protocolo}/itens

Linha do tempo pública do chamado (mensagens e eventos).

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim
limitequery não
antesquery não

Respostas

códigosignificado
200`{itens: (mensagem|evento)[], proximo?}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
502atendimento indisponível

Exemplo

curl -X GET 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI/itens' \
  -H 'Authorization: Bearer SEU_TOKEN'
PUT /api/suporte/chamados/{protocolo}/leitura

Marca as respostas da equipe como lidas.

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim

Respostas

códigosignificado
204Marcado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ImpersonationReadOnly`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
502atendimento indisponível

Exemplo

curl -X PUT 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI/leitura' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/suporte/chamados/{protocolo}/mensagens

Responde ao chamado (JSON ou multipart com até 3 anexos).

application/json ({corpo}) ou multipart/form-data (campo corpo + até 3 anexo, cada um ≤ 5 MiB). O multipart é reescrito em streaming só com esses campos; outro campo → 400. A resposta do cliente reabre pendente/resolvido. Chamado fechado → 409 ChamadoFechado (use /reabrir).

Onde é usadao campo de resposta da tela do chamado.

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim

Respostas

códigosignificado
201`{id, criada_em, estado}`.
400`ValidationError` — campo não permitido ou anexos demais.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ImpersonationReadOnly`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`ChamadoFechado`.
413`PayloadTooLarge`.
415`TipoDeAnexoNaoAceito`.
429`Throttling`.
502atendimento indisponível

Exemplo

curl -X POST 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI/mensagens' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/suporte/chamados/{protocolo}/mensagens/{msgId}/anexos/{anexoId}

Baixa um anexo (streaming).

Sempre Content-Disposition: attachment, X-Content-Type-Options: nosniff e Cache-Control: private, no-store. Anexo de protocolo alheio → 404. Em verificação de antivírus → 409 AnexoEmVerificacao (+ Retry-After); bloqueado → 403 AnexoBloqueado.

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim
msgIdpath sim
anexoIdpath sim

Respostas

códigosignificado
200Os bytes do anexo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`AnexoBloqueado`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`AnexoEmVerificacao` (com `Retry-After`).
502atendimento indisponível

Exemplo

curl -X GET 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI/mensagens/msgId_AQUI/anexos/anexoId_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/suporte/chamados/{protocolo}/reabrir

Reabre o chamado dentro da janela.

Fora da janela → 409 JanelaDeReaberturaExpirada com Details.relacionado_a_sugerido (abra um novo chamado com relacionado_a).

Parâmetros

nomeemobrigatóriodescrição
protocolopath sim

Respostas

códigosignificado
200Chamado atualizado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`ImpersonationReadOnly`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`JanelaDeReaberturaExpirada`.
502atendimento indisponível

Exemplo

curl -X POST 'https://api.asender.net/api/suporte/chamados/protocolo_AQUI/reabrir' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/suporte/chamados/contagem

Indicadores do menu (aguardando você, respostas não lidas, abertos).

Parâmetros

nomeemobrigatóriodescrição
escopoquery não

Respostas

códigosignificado
200`{aguardando_voce, respostas_nao_lidas, abertos}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`SemGovernanca`.
404Conta alheia.
502atendimento indisponível

Exemplo

curl -X GET 'https://api.asender.net/api/suporte/chamados/contagem' \
  -H 'Authorization: Bearer SEU_TOKEN'

bff-tenants

POST /api/contratacao

Auto-contratação de plano (self-service); registra pagamento pendente.

TRANSVERSAL (fora do grupo marketing): é a rota que tira o usuário do dead-end do hub. Concede a governança admin:owner da conta na hora (é grátis) e registra a INTENÇÃO de pagar — o acesso PAGO só é liberado quando o pagamento é aprovado no fluxo de billing manual (MVP).

Corpo NÃO-estrito: campos extras (um user_id malicioso, por ex.) são IGNORADOS — o dono vem sempre da sessão verificada (anti-IDOR). O tenant é resolvido pela sessão (header X-Asender-Tenant quando o usuário pertence à conta), nunca do corpo. Conta com OUTRO dono é recusada antes de qualquer escrita.

Onde é usadaa tela /contratar do dashboard (self-service).
Saídas200 {Status:"pendente",PagamentoId,Produtos,Plano}; 422 validação; 403 conta com outro dono; 502 quando um upstream falha.

Respostas

códigosignificado
200Pagamento pendente registrado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
422Produto/plano inválido, ou nenhum produto escolhido.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/contratacao' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/enderecos/consulta

Endereço de um CEP/zipcode (widget CampoEndereco dos fronts).

Sem conta no caminho: o endereço de um CEP é dado público — a guarda é a sessão + o rate limit por principal. País/CEP fora do formato voltam 422 sem hop. 200/404/422/503 do core passam como vieram; qualquer outra resposta (ou core fora) vira 502. Falha nunca bloqueia o formulário: o widget segue com os campos manuais.

Onde é usadaGET /api/enderecos/consulta?pais=BR&cep=01310100.
Efeitosuma chamada ao core (que tem cache de 30 dias).

Parâmetros

nomeemobrigatóriodescrição
paisquery sim
cepquery sim

Respostas

códigosignificado
200Achado (sem número/complemento).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404CEP não encontrado ou país sem cobertura.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503Provedores fora; campos seguem manuais.

Exemplo

curl -X GET 'https://api.asender.net/api/enderecos/consulta' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/invitations/accept

Aceita um convite e vincula o usuário à conta.

O usuário vem da sessão; quem autoriza é o token do convite (validado no core). A rota não exige pertinência à conta — quem aceita ainda não é membro. O status e o corpo do core são repassados.

Herança de acessos: com o aceite em 200, o BFF pede ao asender_auth_api (POST /v1/tenants/{id}/acessos/herdar) que dono e admin da conta herdem o maior papel de cada ferramenta assinada; developer/viewer não herdam. Falha na herança não desfaz o aceite (continua 200): vai para o log com o request id, e o acesso pode ser concedido na tela Equipe ou religando a ferramenta na Assinatura.

Onde é usadatela de convite.
Efeitosuma escrita no core. # Esta rota NÃO passa por contaAutorizada E não pode: quem aceita ainda NÃO pertence à conta — exigir pertinência aqui tornaria o convite impossível de aceitar. Quem autoriza é o TOKEN, que o core valida (existente, não expirado, não usado), e o usuário vem da sessão.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X POST 'https://api.asender.net/api/invitations/accept' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/tenants

Vínculos de tenant do usuário logado.

Onde é usadadashboard (topbar/tenant-switcher, settings/account) e backoffice (useTenantForest).
Saídas200 com {Tenants:[{Id,Name,Slug,Status,...}]}; 502 se o core está fora. DEFEITO QUE ISTO FECHA (T3 §3.5): a rota devolvia o payload do core CRU (tenants[].id), enquanto TODO o resto de /api/* — inclusive a rota vizinha /api/tenants/tree — responde PascalCase (API_DESIGN.md §51). Quem seguia o contrato lia Tenants e recebia undefined; quem "fazia funcionar" passava a ler tenants e ficava preso ao formato interno do core.

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants

Cria um tenant RAIZ pertencente ao usuário logado.

Allowlist tipada de dois campos. parent_id, monthly_quota, status, plan, owner_user_id e id são IGNORADOS e registrados — em nível ERROR, como tentativa de escalonamento de privilégio. O owner_user_id vem sempre do principal verificado.

Ignorar (em vez de 422) preserva chamadores antigos que mandam owner_user_id; ignorar aqui não é engolir: fica no log com o usuário que enviou.

Para pendurar um tenant sob outro use POST /api/tenants/{id}/children (autoriza o pai) ou PATCH /api/tenants/{id}/parent (autoriza os dois lados). É o único caminho autorizado para mexer na árvore.

Onde é usadafluxo de cadastro (AuthHandler.Register faz o equivalente internamente) e recuperação manual quando o provisionamento automático falhou.
Entradas{Name, Slug} — allowlist explícita, ver tenantCreateInput.
Saídas201 com {Tenant:{Id,...}} em PascalCase; 400 JSON inválido OU campo fora da allowlist; 422 nome vazio; o status 4xx do core preservado; 502 quando o core está fora.
Efeitosescreve no asender-core. O owner_user_id vem SEMPRE do principal verificado, nunca do corpo.

Respostas

códigosignificado
201Tenant criado. **O objeto vem NO TOPO, sem a chave `Tenant`** — o `asender_plataforma_api` responde o tenant sem chave de recurso nesta rota e o BFF só pascaliza o que recebeu. É inconsistente com `POST /api/tenants/{id}/children` e com `POST /api/auth/register`, que devolvem `{"Tenant":{...}}`. Documentado como está no ar.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/tenants/{id}/api-keys

Lista as API keys da conta.

O {id} vem do cliente e é autorizado contra a pertinência do usuário (authorizeTenantParam) antes de qualquer hop; conta alheia responde 404, nunca 403. Espelha /v1/tenants/{id}/api-keys do core — o dashboard não fala /v1 direto porque o segredo de serviço não vive no front.

Onde é usadaGET /api/tenants/{id}/api-keys.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK (corpo repassado do asender_plataforma_api).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/api-keys' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/api-keys

Emite uma API key na conta (o texto puro volta UMA vez).

O user_id vem SEMPRE da sessão, nunca do corpo. A chave em texto puro é devolvida uma única vez, no corpo do core; não é persistida nem logada.

Onde é usadaPOST /api/tenants/{id}/api-keys.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
201Chave criada (corpo do core
400`ValidationError`: `scopes` ausente, vazio ou com valor fora do catálogo; a mensagem lista os escopos válidos.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422name ausente.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/api-keys' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/api-keys/{keyId}

Revoga uma API key da conta.

Onde é usadaDELETE /api/tenants/{id}/api-keys/{keyId}.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
keyIdpath sim

Respostas

códigosignificado
204Revogada.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/api-keys/keyId_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/channels/whatsapp/credentials

Credencial de WhatsApp da conta, MASCARADA.

O {id} vem do cliente e é autorizado por pertinência antes de qualquer hop (authorizeTenantParam); conta alheia responde 404, nunca 403.

O access_token NUNCA volta em claro: o core devolve mascarado.

Onde é usadaGET /api/tenants/{id}/channels/whatsapp/credentials.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK (corpo repassado do core, com o token mascarado).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422Id de conta vazio no caminho.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/credentials' \
  -H 'Authorization: Bearer SEU_TOKEN'
PUT /api/tenants/{id}/channels/whatsapp/credentials

Cadastra/substitui a credencial de WhatsApp da conta.

Exige papel de ESCRITA na conta (authorizeTenantParam recusa viewer com 403 ReadOnlyRole); conta alheia continua respondendo 404.

Corpo ESTRITO: campo desconhecido ou JSON malformado é recusado aqui com

  1. O core cifra o access_token; o eco da resposta vem mascarado.
Onde é usadaPUT /api/tenants/{id}/channels/whatsapp/credentials.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK (corpo repassado do core, com o token mascarado).
400Corpo não é JSON válido ou traz campo desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Papel de leitura (`viewer`) não altera a conta.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422phone_id ou access_token ausente/vazio.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X PUT 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/credentials' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/tenants/{id}/channels/whatsapp/numbers

Números de WhatsApp da conta (vários por conta; o padrão primeiro).

Espelho de sessão de /v1/tenants/{id}/channels/whatsapp/numbers do core. O {id} é autorizado por pertinência antes de qualquer hop; conta alheia responde 404. O token só aparece mascarado.

Onde é usadaa tela de canais do admin e o seletor de remetente do zapify.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK — `{numbers: [...]}` repassado do core.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/numbers' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/channels/whatsapp/numbers

Adiciona um número de WhatsApp à conta.

Papel de ESCRITA. Corpo ESTRITO. Sem access_token, o core reaproveita o token de outro número da mesma WABA na conta. 409 NumeroEmUso quando o número já está em alguma conta (nunca diz qual).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
201Criado (eco do core, token mascarado).
400Corpo inválido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Papel de leitura.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`NumeroEmUso`.
422phone_id ausente.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/numbers' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/channels/whatsapp/numbers/{numero}

Remove um número — pede confirmação se houver conversas abertas.

Sem confirm=true, o BFF pergunta ao atendimento (com o Bearer da sessão) quantas conversas abertas entraram pelo número. Havendo alguma — ou não dando para saber — responde 409 ConversasAbertas com open_conversations (null = desconhecido) e NÃO remove. O histórico das conversas nunca é apagado; só as respostas por este número param.

Onde é usada"Remover" na tela de canais.

Parâmetros

nomeemobrigatóriodescrição
confirmquery não

Respostas

códigosignificado
204Removido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`ConversasAbertas` (confirme) ou `PadraoComOutros`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/numbers/numero_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/tenants/{id}/channels/whatsapp/numbers/{numero}

Edita um número (nome, WABA, token, ativo).

Parcial (campo ausente não muda). apply_token_to_waba leva o token novo aos irmãos da WABA. Pausar o padrão com outro ativo é 409 PadraoComOutros.

Respostas

códigosignificado
200OK (eco do core, token mascarado).
400Corpo inválido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`PadraoComOutros`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X PATCH 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/numbers/numero_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/tenants/{id}/channels/whatsapp/numbers/{numero}/default

Define o número como remetente padrão.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
numeropath sim

Respostas

códigosignificado
200OK (o novo padrão).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`NumeroInativo`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/numbers/numero_AQUI/default' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/tenants/{id}/channels/whatsapp/numbers/{numero}/test

Testar conexão do número com a Meta (simulado fora de produção).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
numeropath sim

Respostas

códigosignificado
200`{ok, simulated, display_number?, verified_name?, detail?}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
503Validação indisponível / Graph API fora.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/channels/whatsapp/numbers/numero_AQUI/test' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/tenants/{id}/children

Cria uma sub-conta sob um tenant administrado pelo usuário.

Autoriza {id} por vínculo direto ou herdado (ser membro de um ancestral manda na subárvore). MonthlyQuota ausente/nulo = herda do ancestral mais próximo com valor. O usuário da SESSÃO vira o owner da sub-conta (nunca um campo do corpo): ela nasce com governança e as ferramentas padrão, e aparece no lançador e no admin.

Onde é usadatela de hierarquia de tenants.
Entradas{Name, Slug, MonthlyQuota} — MonthlyQuota nulo/ausente significa herdar do ancestral mais próximo com valor.
Saídas201 com {Tenant}; 404 se {id} não é acessível; 422 quando o core recusa (slug duplicado, ciclo detectado pelo trigger de closure).
Efeitosescreve no asender-core (conta + closure + membro owner = usuário da sessão + evento platform.tenant.created, numa transação lá).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
201Sub-conta criada.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Papel efetivo na conta não governa a árvore (`GovernanceRole`): só `owner`/`admin` (direto ou do ancestral concedente) criam, movem ou mudam cota de sub-conta — `developer`/`viewer` não (2026-10-04).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`409 Conflict` — email já registrado, slug em uso.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/children' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/tenants/{id}/envios-api

Destinatários dos envios feitos pela API (endereço completo, estado e motivo).

Painel (sessão). O {id} vem do cliente e é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin leem (deny-default; developer, viewer e papel indeterminado levam 403). Lê AO VIVO o serviço dono das mensagens, escopado no {id} validado — o log de requisições guarda destinatários mascarados; este é o dado operacional. Inclui os suprimidos (status: suppressed) com reason e message. Sem ids: página por status, q (endereço), cursor, limit. Com ids (1 a 50 ids de mensagem, nenhum outro parâmetro): só os que existem na conta.

Onde é usadaAPI pública do cliente.
Entradasstatus, q, cursor, limit (allowlist). channel do cliente é IGNORADO: nesta rota o canal é do contrato, não do parâmetro.
Saídas200 com {Messages, NextCursor, HasMore}; 422 em query malformada.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
statusquery nãoEstado da mensagem (ex.: `suppressed`, `sent`, `bounced`).
qquery nãoBusca no endereço do destinatário ou no assunto.
cursorquery nãoInteiro não negativo (`next_cursor` da página anterior).
limitquery não
idsquery nãoAté 50 ids de mensagem separados por vírgula.

Respostas

códigosignificado
200OK.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422Parâmetro desconhecido ou inválido.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502Serviço de mensagens indisponível.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/envios-api' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/importaveis

As outras contas que o usuário governa e pode importar para a árvore de {id}.

Contas de que o usuário é membro DIRETO com papel owner/admin, fora da árvore de {id} (ela, a descendência e os ancestrais — já estão nela ou seria ciclo). Cada uma com o pai atual (PaiId/PaiNome) e, quando o usuário não governa esse pai, Bloqueio: ParentNotGoverned (a tela mostra desabilitada e explica). Exige governar {id}.

Onde é usadaseletor flutuante do diálogo "Importar conta existente" (admin).
Saídas200 {Contas:[...]} em ordem de nome; 403/404 como as mutações.
Efeitosleituras no core (pertinência, descendência, ancestrais por conta).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200A lista, em ordem de nome.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`GovernanceRole`: o usuário não governa `{id}`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/importaveis' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/invitations

Convites pendentes da conta.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/invitations' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/invitations

Convida alguém para a conta.

Onde é usadatela de equipe.
Efeitosuma escrita no core (e, quando houver envio, um e-mail).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/invitations' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/invitations/{token}

Cancela um convite pendente.

Onde é usadatela de equipe.
Efeitosuma escrita no core.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
tokenpath sim

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/invitations/token_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/logo/{variante}

Imagem do logo da empresa (lançador e cabeçalho dos fronts).

O {id} é autorizado contra a pertinência do usuário (authorizeTenantParam) antes de o core ser perguntado; conta alheia responde 404. Os bytes são o PNG que o core recodificou (migration 031); a resposta leva Content-Type: image/png FIXO, nosniff, CSP sandbox e Cache-Control: private — imutável por um ano quando ?v= é a versão atual (a lista GET /api/tenants traz LogoQuadrado/LogoHorizontal). Só leitura: enviar/remover é governança no admin.asender.

Onde é usadaGET /api/tenants/{id}/logo/{variante}. Cache: private (é imagem atrás de sessão — um cache compartilhado não pode guardá-la). Com ?v= igual à versão atual, a URL é imutável e vale por um ano; sem ?v= (ou com versão velha), uma hora.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
variantepath sim
vquery não

Respostas

códigosignificado
200PNG
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/logo/variante_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/logs

Log de requisições da API da conta (painel, sem corpos).

Painel (sessão). O {id} vem do cliente e é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin leem o log da conta (deny-default; developer, viewer e papel indeterminado levam 403). O tenant enviado ao serviço de logs é o {id} já validado. Mesmos filtros e corpo de GET /v1/logs.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
limitquery nãoItens por página.
afterquery nãoCursor opaco (`next` da página anterior): devolve os registros mais antigos. Adulterado ou de outra conta → 400. Não combine com `before`.
beforequery nãoCursor opaco: devolve os mais novos. `after` e `before` juntos → 400.
status_classquery nãoLista separada por vírgula de `2xx`, `3xx`, `4xx`, `5xx`.
statusquery nãoStatus HTTP exato.
methodquery nãoVerbo HTTP.
pathquery nãoPadrão de rota exato (não o caminho com ids).
api_key_idquery nãoId de uma ou mais chaves da conta, separados por vírgula.
error_codequery nãoCódigo do envelope de erro devolvido.
idempotency_keyquery nãoChave de idempotência efetiva da requisição.
message_idquery nãoId de mensagem devolvido pelo envio (liga o log ao envio).
modequery nãoModo da chave usada.
sincequery nãoRFC 3339. Padrão: agora menos 24 h.
untilquery nãoRFC 3339. Padrão: agora. Janela maior que 31 dias é reduzida a 31 dias antes de `until`.

Respostas

códigosignificado
200OK (corpo repassado do serviço de logs, em snake_case).
400`400 ValidationError` — parâmetro inválido, cursor adulterado ou de outra conta, `after` com `before`. Corpo repassado do serviço de logs (`LogsErrorEnvelope`).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` — o serviço de logs não respondeu. Repita depois do `Retry-After`; o resto da API segue funcionando.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/logs' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/logs/{logId}

Detalhe de uma requisição (corpos redigidos).

Painel (sessão). O {id} vem do cliente e é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin leem o log da conta (deny-default; developer, viewer e papel indeterminado levam 403). O tenant enviado ao serviço de logs é o {id} já validado. Mesmo corpo de GET /v1/logs/{id}; id de outra conta = 404 idêntico ao inexistente.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
logIdpath sim

Respostas

códigosignificado
200OK (corpo repassado do serviço de logs, em snake_case).
400`400 ValidationError` — parâmetro inválido, cursor adulterado ou de outra conta, `after` com `before`. Corpo repassado do serviço de logs (`LogsErrorEnvelope`).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` — o serviço de logs não respondeu. Repita depois do `Retry-After`; o resto da API segue funcionando.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/logs/logId_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/logs/resumo

Resumo agregado da conta: total, taxa de erro, p50/p95, por rota e por código.

Painel (sessão). O {id} vem do cliente e é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin leem o log da conta (deny-default; developer, viewer e papel indeterminado levam 403). O tenant enviado ao serviço de logs é o {id} já validado. p95_ms é estimado pelos buckets fixos do histograma.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
sincequery não
untilquery não
modequery não

Respostas

códigosignificado
200OK (corpo repassado do serviço de logs, em snake_case).
400`400 ValidationError` — parâmetro inválido, cursor adulterado ou de outra conta, `after` com `before`. Corpo repassado do serviço de logs (`LogsErrorEnvelope`).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` — o serviço de logs não respondeu. Repita depois do `Retry-After`; o resto da API segue funcionando.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/logs/resumo' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/logs/series

Série temporal de requisições por classe de status.

Painel (sessão). O {id} vem do cliente e é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e por papel: só owner e admin leem o log da conta (deny-default; developer, viewer e papel indeterminado levam 403). O tenant enviado ao serviço de logs é o {id} já validado. Lê o rollup (não as partições): pontos ordenados por t, buckets vazios com zeros.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
bucketquery nãoLargura do ponto.
sincequery não
untilquery não
modequery não

Respostas

códigosignificado
200OK (corpo repassado do serviço de logs, em snake_case).
400`400 ValidationError` — parâmetro inválido, cursor adulterado ou de outra conta, `after` com `before`. Corpo repassado do serviço de logs (`LogsErrorEnvelope`).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 AdminRole` — o usuário pertence à conta mas não é `owner` nem `admin`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` — o serviço de logs não respondeu. Repita depois do `Retry-After`; o resto da API segue funcionando.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/logs/series' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/members

Membros da conta.

Onde é usadatela de equipe.
Efeitosuma leitura no core.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/members' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/members

Vincula um usuário à conta.

Onde é usadatela de equipe.
Efeitosuma escrita no core.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/members' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/members/{userId}

Desvincula um usuário da conta.

Onde é usadatela de equipe.
Efeitosuma escrita no core.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
userIdpath sim

Respostas

códigosignificado
200OK
401credencial ausente ou inválida

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/members/userId_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/movimento/previa

Prévia de mover/importar uma conta, sem escrever nada.

?ParentId=acc_x (ausente = promover a raiz). A MESMA autorização dos três lados do PATCH — a prévia não é oráculo sobre conta alheia. Devolve, do core: a conta, o pai atual, o destino, NoOp (já está lá), Ciclo, ContasMovidas e a cota efetiva antes/depois. Usada pelo passo de confirmação de "Importar conta existente" (admin).

Onde é usadapasso "confirmar" do diálogo "Importar conta existente" do admin. Segurança: a MESMA autorização do PATCH — a prévia não pode virar oráculo sobre contas que o usuário não governa.
Saídas200 {Preview:{...}}; 403/404/422 como no PATCH.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
ParentIdquery não

Respostas

códigosignificado
200A prévia.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`GovernanceRole` ou `ParentNotGoverned` (ver o PATCH /parent).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/movimento/previa' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/oauth-apps

Lista os apps OAuth ("Entrar com Asender") da conta.

Portal "Aplicativos" (ADR-0130, D4). O {id} é autorizado por pertinência (authorizeTenantParam; conta alheia responde 404) e o BFF repassa o Bearer da sessão ao auth (GET /v1/oauth/meus-apps?conta={id}), que confere owner/admin no core (membro sem papel: 403 PapelInsuficiente). Resposta Cache-Control: no-store.

Onde é usadaa busca do cabeçalho do hub (asender_plataforma_front, components/busca-do-app.tsx).
Erros400 termo; 401 sem sessão; 404 conta alheia (resolveSessionTenant — nunca 403, que seria oráculo); 502 se o PDP não responde (sem ele não há como saber o que a pessoa pode ver: nega).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200OK: `{apps:[app]}` (corpo do auth).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Membro sem papel owner/admin (`PapelInsuficiente`).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/oauth-apps

Cria um app OAuth na conta (o client_secret volta UMA vez).

O campo conta do corpo é SEMPRE sobrescrito pelo {id} da rota. Corpo até 64 KiB. Para app confidencial a resposta 201 traz client_secret uma única vez (Cache-Control: no-store); nada é guardado nem logado no BFF. Erros do auth intactos: 409 LimiteDeApps (10 por conta), 429 MuitasCriacoes (com Retry-After), 422 validação.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
201Criado: `{app, client_secret?, aviso}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem papel owner/admin.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`LimiteDeApps`.
413Corpo acima de 64 KiB.
422Validação do auth.
429`MuitasCriacoes` (Retry-After).

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/oauth-apps/{app}

Exclui (desativa) o app e revoga grants e refresh tokens.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
204Excluído.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/oauth-apps/{app}

Detalhe de um app OAuth da conta.

App de outra conta responde 404 (o BFF confere app.conta == {id}).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
200OK: `{app}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem papel owner/admin.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/tenants/{id}/oauth-apps/{app}

Edita campos do app (nome, site_url, logo_url, redirect_uris, escopos).

Ampliar escopos de app publicado marca revisao_pendente. Corpo até 64 KiB.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
200OK: `{app}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422Validação do auth.

Exemplo

curl -X PATCH 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /api/tenants/{id}/oauth-apps/{app}/revisao

Pede a revisão do app pela equipe.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
200OK: `{app}` em `em_revisao`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`EstadoInvalido`.
422`RevisaoIncompleta` (site_url vazio).

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI/revisao' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/tenants/{id}/oauth-apps/{app}/revisoes

Histórico de revisões do app.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
200OK: `{revisoes:[…]}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI/revisoes' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/oauth-apps/{app}/segredo

Gira o client_secret (o novo volta UMA vez).

Resposta no-store; 409 AppPublico para app sem segredo.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
200OK: `{client_id, client_secret, aviso}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409`AppPublico`.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI/segredo' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/tenants/{id}/oauth-apps/{app}/testadores

Lista os testadores do app (limite 25).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
200OK: `{testadores:[…], limite}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI/testadores' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/tenants/{id}/oauth-apps/{app}/testadores

Convida um testador (por `email` ou `user_id`).

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim

Respostas

códigosignificado
201Convidado: `{testador}`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`UsuarioNaoEncontrado` ou conta/app inexistente.
409`LimiteDeTestadores`.

Exemplo

curl -X POST 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI/testadores' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/tenants/{id}/oauth-apps/{app}/testadores/{usr}

Remove um testador do app.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
apppath sim
usrpath sim

Respostas

códigosignificado
204Removido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.

Exemplo

curl -X DELETE 'https://api.asender.net/api/tenants/id_AQUI/oauth-apps/app_AQUI/testadores/usr_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /api/tenants/{id}/parent

Move o tenant para outro pai, importa uma conta como sub-conta, ou promove a raiz.

Autoriza os TRÊS lados (ADR-0098): {id} (quem entrega), o pai ATUAL de {id} quando ele não é raiz (quem perde — sem isto o admin direto de uma sub-conta de agência a arrancaria da árvore) e o ParentId de destino (quem recebe — sem isto o tenant iria parar na árvore de outro cliente, que passaria a vê-lo em rollup). Todos exigem papel EFETIVO owner/admin, direto ou herdado pela árvore.

Corpo com EXATAMENTE um campo ParentId. null promove a raiz; campo ausente é 422 (um {} acidental não pode promover uma sub-conta). Ciclo → 422 (o trigger de closure do core levanta check_violation), nunca 500. Já estar sob o destino → 200 com Moved:false, nada gravado.

O core grava a trilha (core.tenant_movimentos, ator = usuário da sessão, origem sessao) e o evento platform.tenant.moved.

Onde é usadatela "Contas e sub-contas" do admin (mover e "Importar conta existente"). Segurança: autoriza os TRÊS lados (ver o doc do arquivo).
Saídas200 com {Tenant, Moved, PaiAnterior} (Moved:false = já estava lá, nada gravado); 404 se a conta ou o destino não é acessível; 403 GovernanceRole/ParentNotGoverned; 409 movimento concorrente; 422 ciclo, auto-pai ou destino inativo.
Efeitosreescreve a closure no core e grava a trilha (ator = usuário da sessão, origem sessao) + evento platform.tenant.moved.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200Movido (a closure foi reescrita) ou já estava lá (`Moved:false`).
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`GovernanceRole`/`ReadOnlyRole`: o papel efetivo na conta ou no destino não governa a árvore (só `owner`/`admin`). `ParentNotGoverned`: a conta está sob uma conta que o usuário não governa — só quem governa o pai atual a tira de lá.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
409Outro movimento mudou o pai da conta entre a leitura e a escrita (`Conflict`); nada foi gravado — recarregue.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X PATCH 'https://api.asender.net/api/tenants/id_AQUI/parent' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
PATCH /api/tenants/{id}/quota

Define ou limpa a quota mensal do tenant.

Corpo com EXATAMENTE um campo MonthlyQuota. null volta a herdar do ancestral mais próximo. Campo ausente é 422 — um nome digitado errado não pode limpar a quota em silêncio.

Onde é usadatela de hierarquia de tenants.
Entradas{MonthlyQuota} — número define o teto; null volta a herdar do ancestral. A chave é OBRIGATÓRIA: aceitar corpo sem ela faria um nome de campo digitado errado limpar a quota em silêncio.
Saídas200 com {Tenant}; 404 se {id} não é acessível; 422 em valor inválido.
Efeitosescreve no asender-core.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.

Respostas

códigosignificado
200Quota gravada; `EffectiveQuota` já recalculada.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Papel efetivo na conta não governa a árvore (`GovernanceRole`): só `owner`/`admin` (direto ou do ancestral concedente) criam, movem ou mudam cota de sub-conta — `developer`/`viewer` não (2026-10-04).
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X PATCH 'https://api.asender.net/api/tenants/id_AQUI/quota' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /api/tenants/{id}/suppressions

Supressões da conta e as globais que afetam seus envios (ADR-0153 D7).

Sessão. {id} autorizado por pertinência (alheia = 404) e papel owner/admin (deny-default; demais 403, sem hop ao core). Só state, origin, q, cursor, limit viajam (valor até 256 caracteres, 400 acima). corpo e status do core voltam como vieram.

Onde é usadaaba "Atividade" da tela /auditoria do admin.asender.
Efeitossó leitura.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
statequery não
originquery não
qquery não
cursorquery não
limitquery não

Respostas

códigosignificado
200`{itens: SupressaoConta[], proximo_cursor?, globais: SupressaoEntrada[]}` (globais só sem cursor).
400valor de filtro longo demais
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`AdminRole` — não é owner nem admin.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
502core indisponível

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/suppressions' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/{id}/suppressions/{sid}

Detalhe de uma supressão da conta com a trilha (owner/admin).

Onde é usadadetalhe na tela de supressões da conta.
Efeitosuma leitura ao core com o ator.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id do tenant (`acc_<hex>`). O BIGSERIAL interno nunca aparece na fronteira.
sidpath sim

Respostas

códigosignificado
200`{entrada, eventos[]}`.
400sid inválido
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`AdminRole`.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
502core indisponível

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/id_AQUI/suppressions/sid_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /api/tenants/tree

Subárvore do tenant corrente, em pré-ordem.

Onde é usadadashboard (settings/tenants) e backoffice.
Entradasdepth — inteiro positivo; valor não numérico é 422, não "sem limite" silencioso.
Saídas200 com {TenantId, Tree:[...]}; 404 se o tenant pedido não é acessível.

Parâmetros

nomeemobrigatóriodescrição
X-Asender-Tenantheader nãoTenant que o dashboard está olhando. **Nunca é usado como veio:** só vale depois de provada a pertinência do usuário (vínculo direto ou a um ancestral). Fora do escopo → **404**, não 403. Ausente → o menor id entre os tenants do usuário, em ordem lexicográfica (escolha estável, e a razão de o login novo cair numa conta vazia).
depthquery não

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 NoTenant` — sessão válida de um usuário sem NENHUM vínculo de tenant.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/api/tenants/tree' \
  -H 'Authorization: Bearer SEU_TOKEN'

contacts

PATCH /api/contacts/{id} sem schema na spec

altera parcialmente um contato do tenant da SESSÃO — hoje o caminho do editor de consentimento por canal.

Onde é usadatela de audiência do hub, ao revogar ou restaurar um canal.
Saídas200 com {Contact}; 404 quando o contato não é do tenant; 422 em validação (canal fora dos quatro, corpo vazio).
Efeitosescreve em messages.contacts e no consentimento por canal. # O tenant vem da SESSÃO, e o id vai na ROTA resolveSessionTenant resolve a conta do token verificado; o corpo nunca escolhe em quem se escreve (piso 3). É o que faz PATCH /api/contacts/<id do vizinho> ser 404 em vez de edição alheia — a checagem é do asender_mkt_api, com o tenant que ESTE handler passou. # Por que map[string]any e não uma struct PATCH é PARCIAL. Uma struct com campos zero-value não distingue "não mandou" de "mandou vazio", e aqui a diferença decide se o consentimento de um canal muda ou fica como está. Repassar o corpo cru deixa a decisão com quem tem a regra — o asender_mkt_api — em vez de duplicá-la no BFF, que é como as duas cópias do filtro de descadastro divergiram antes (ver o contrato da audiência).

Exemplo

curl -X PATCH 'https://api.asender.net/api/contacts/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

mcp

GET /.well-known/oauth-protected-resource

Protected Resource Metadata (RFC 9728) da API REST `https://api.asender.net`.

Documento público para clientes OAuth descobrirem o servidor de autorização (auth.asender.net), os escopos e a documentação. É o alvo do resource_metadata do WWW-Authenticate dos 401 de /v1. Cache de 5 minutos; CORS liberado (Access-Control-Allow-Origin: *, preflight OPTIONS → 204).

Onde é usadaserver.registrarMCP (raiz e /mcp).

Respostas

códigosignificado
200OK

Exemplo

curl -X GET 'https://api.asender.net/.well-known/oauth-protected-resource' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /.well-known/oauth-protected-resource/mcp

Protected Resource Metadata (RFC 9728) do servidor MCP `https://api.asender.net/mcp`.

Igual ao da raiz, com resource = MCP_RECURSO. 404 quando o MCP está desligado (MCP_ATIVO=false).

Onde é usadaserver.registrarMCP (raiz e /mcp).

Respostas

códigosignificado
200OK
404MCP desligado.

Exemplo

curl -X GET 'https://api.asender.net/.well-known/oauth-protected-resource/mcp' \
  -H 'Authorization: Bearer SEU_TOKEN'
DELETE /mcp

Não há sessão para encerrar (servidor stateless).

Onde é usadaserver.routes.

Respostas

códigosignificado
405Method Not Allowed (`Allow: POST, OPTIONS`).

Exemplo

curl -X DELETE 'https://api.asender.net/mcp' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /mcp

Não há stream SSE (servidor stateless).

Onde é usadaserver.routes.

Respostas

códigosignificado
405Method Not Allowed (`Allow: POST, OPTIONS`).

Exemplo

curl -X GET 'https://api.asender.net/mcp' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /mcp

Servidor MCP remoto (Streamable HTTP stateless, JSON-RPC 2.0, somente leitura).

Um POST por mensagem JSON-RPC (sem sessão, sem SSE, sem lote). Métodos: initialize, notifications/initialized (→ 202), ping, tools/list, tools/call. Versões aceitas em MCP-Protocol-Version: 2025-11-25, 2025-06-18, 2025-03-26 (ausente = 2025-03-26).

Autenticação: Authorization: Bearer com JWT de app OAuth cujo aud contém https://api.asender.net/mcp ou https://api.asender.net, ou chave sk_*. O escopo é por ferramenta: tools/list só mostra as que o token/chave pode usar e tools/call sem escopo devolve isError. Cada ferramenta roda o mesmo handler da rota REST (mesmo escopo, produto e PDP do usuário). Falha de negócio (400/403/404/503 da rota) volta como resultado com isError: true; só erro de protocolo usa códigos JSON-RPC (-32700, -32600, -32601, -32602).

Origin presente e fora da lista (API, AS, loopback, MCP_ORIGENS_PERMITIDAS) → 403. Corpo ≤ 1 MiB. 401 leva WWW-Authenticate: Bearer [error="invalid_token", ]resource_metadata="…/oauth-protected-resource/mcp".

Onde é usadaserver.routes (depois de auth e rate limit).

Respostas

códigosignificado
200Resposta JSON-RPC (`result` ou `error`).
202Notificação ou resposta do cliente aceita
400JSON inválido
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Origin não permitido.
413Corpo acima de 1 MiB.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.

Exemplo

curl -X POST 'https://api.asender.net/mcp' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

me

GET /api/me/senhas-app sem schema na spec

allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Exemplo

curl -X GET 'https://api.asender.net/api/me/senhas-app' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /api/me/senhas-app sem schema na spec

allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Exemplo

curl -X POST 'https://api.asender.net/api/me/senhas-app' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /api/me/senhas-app/{id} sem schema na spec

allowlist → corpo → auth `/v1/me/<resto>` → status e corpo como vieram (inclusive o 403 `ReauthRequired` com `metodos_aceitos`, que a tela precisa ler intacto) + `Retry-After` do 429.

Onde é usadagrupo SessionAuth+Impersonacao do BFF (server/server_me.go). Erros próprios: 404 rota fora do contrato; 400 corpo inválido; 401 sem token; 502 auth fora.

Exemplo

curl -X DELETE 'https://api.asender.net/api/me/senhas-app/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'

meta

GET /

Identidade do serviço.

Respostas

códigosignificado
200OK

Exemplo

curl -X GET 'https://api.asender.net/' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /version

Versão e commit do binário em execução.

Onde é usadarota pública, usada por ops e por quem investiga incidente. Pública porque a primeira pergunta de todo incidente é "que versão está no ar?", e ela tem de ser respondível sem credencial — quem investiga muitas vezes ainda não tem uma. O que sai é só isso: nunca configuração, nunca endereço interno, que é topologia e pertence ao console de plataforma.

Respostas

códigosignificado
200OK

Exemplo

curl -X GET 'https://api.asender.net/version' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-account

GET /v1/account

Conta da API key usada (ou do app OAuth, com `ClientId`), mais o contexto da credencial.

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/v1/account' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/account/usage

Contadores de consumo do tenant no período corrente.

Onde é usadasuperfície pública /v1, consumida por integrações de cliente.
Saídas200 com {Usage:{Period,EmailsSent,SmsSent,PushSent,ApiCalls}}; 404 se o tenant da API key não existe mais no core; 502 se o core está fora. DEFEITO QUE ISTO TAMBÉM FECHA (item 7 do briefing Y2, metade da borda): o core respondia 404 quando não havia linha em core.usage_counters — e o seed não cria nenhuma —, e este handler traduzia QUALQUER erro do upstream em 502. Ou seja: a rota respondia "serviço indisponível" para todo tenant que ainda não enviou nada, mandando o alerta para o time errado. O 404 do core agora só significa "tenant inexistente" (a correção principal está em asender_plataforma_api/internal/service/usage.Get) e é traduzido como 404, não 502. DEFEITO QUE ISTO FECHA (T3 §3.6): o payload do core saía CRU sob Usage ({"Usage":{"sent":42}}), fora do contrato PascalCase da API pública (API_DESIGN.md §51). O cliente que segue o contrato lê Usage.Sent e recebe undefined — mesma classe de divergência de fronteira do GET /api/tenants.

Respostas

códigosignificado
200Consumo do período. Tenant sem nenhum envio devolve os contadores em zero, com `UpdatedAt: null` — ausência de linha é consumo zero, não ausência de recurso.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502Core fora **ou** tenant sem contador de uso (ver `x-asender-divergence`).

Exemplo

curl -X GET 'https://api.asender.net/v1/account/usage' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/me

Quem autorizou o app OAuth, em que conta e com que escopos (só token OAuth, escopo openid).

Onde é usadaGET /v1/me, depois do PublicAuth + dados.ExigirProduto("").
Erros401 sem principal OAuth; 502 se o core falha.

Respostas

códigosignificado
200OK (JSON em snake_case, sem RequestId)
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem o escopo openid, chave sk_* (rota só de OAuth) ou usuário sem acesso à conta.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/v1/me' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-analytics

GET /v1/analytics/breakdown

Janela agrupada por uma dimensão.

Escopo analytics:read. dimension: page (padrão), source, medium, campaign, country, device ou form. Valor não preenchido sai como (none). Janela de até 92 dias.

Saídas200 {from,to,dimension,data,next_cursor:null}.

Parâmetros

nomeemobrigatóriodescrição
dimensionquery nãoDimensão de agrupamento.
limitquery nãoItens por página.
fromquery nãoInício INCLUSIVO da janela: RFC 3339 ou `YYYY-MM-DD` (= 00:00:00Z). Padrão: `to` menos 30 dias.
toquery nãoFim EXCLUSIVO da janela (mesmo formato). Padrão: agora.
page_idquery nãoFiltra por página.
form_idquery nãoFiltra por formulário.
sourcequery nãoFiltra por origem (utm_source).
mediumquery nãoFiltra por meio (utm_medium).
campaignquery nãoFiltra por campanha (utm_campaign).
countryquery nãoFiltra por país.
devicequery nãoFiltra por dispositivo.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/analytics/breakdown' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/analytics/forms

Resumo de formulários ou funil de um formulário.

Escopo analytics:read. Sem form_id: um item por formulário (visualizações, inícios, envios e taxa), janela de até 92 dias. Com form_id: o funil completo do formulário (série diária, por origem/dispositivo/página, abandono por campo), janela de até 400 dias. Formulário de outra conta ou inexistente: relatório zerado (a existência não é confirmada).

Saídas200; 400; 401; 503 com Retry-After.

Parâmetros

nomeemobrigatóriodescrição
limitquery nãoItens por página.
fromquery nãoInício INCLUSIVO da janela: RFC 3339 ou `YYYY-MM-DD` (= 00:00:00Z). Padrão: `to` menos 30 dias.
toquery nãoFim EXCLUSIVO da janela (mesmo formato). Padrão: agora.
page_idquery nãoFiltra por página.
form_idquery nãoFiltra por formulário.
sourcequery nãoFiltra por origem (utm_source).
mediumquery nãoFiltra por meio (utm_medium).
campaignquery nãoFiltra por campanha (utm_campaign).
countryquery nãoFiltra por país.
devicequery nãoFiltra por dispositivo.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/analytics/forms' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/analytics/overview

Totais de tráfego e conversão da janela.

Escopo analytics:read. Visitas (sessões), visualizações de página, conversões (sessões com envio de formulário), taxa e o funil de formulários. Janela de até 92 dias. JSON snake_case; o tenant é sempre o da credencial.

Onde é usadaapps e chaves com analytics:read.
Saídas200 {from,to,totals}; 400 janela/filtro inválido; 401; 503 com Retry-After se o runtime estiver fora.
Efeitosuma leitura no runtime.

Parâmetros

nomeemobrigatóriodescrição
fromquery nãoInício INCLUSIVO da janela: RFC 3339 ou `YYYY-MM-DD` (= 00:00:00Z). Padrão: `to` menos 30 dias.
toquery nãoFim EXCLUSIVO da janela (mesmo formato). Padrão: agora.
page_idquery nãoFiltra por página.
form_idquery nãoFiltra por formulário.
sourcequery nãoFiltra por origem (utm_source).
mediumquery nãoFiltra por meio (utm_medium).
campaignquery nãoFiltra por campanha (utm_campaign).
countryquery nãoFiltra por país.
devicequery nãoFiltra por dispositivo.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/analytics/overview' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/analytics/timeseries

Série diária (America/Sao_Paulo).

Escopo analytics:read. Um ponto por dia com as mesmas contagens do overview; a soma da série fecha com o total. Janela de até 92 dias.

Saídas200 {from,to,interval,timezone,data,next_cursor:null}; erros como o overview.

Parâmetros

nomeemobrigatóriodescrição
fromquery nãoInício INCLUSIVO da janela: RFC 3339 ou `YYYY-MM-DD` (= 00:00:00Z). Padrão: `to` menos 30 dias.
toquery nãoFim EXCLUSIVO da janela (mesmo formato). Padrão: agora.
page_idquery nãoFiltra por página.
form_idquery nãoFiltra por formulário.
sourcequery nãoFiltra por origem (utm_source).
mediumquery nãoFiltra por meio (utm_medium).
campaignquery nãoFiltra por campanha (utm_campaign).
countryquery nãoFiltra por país.
devicequery nãoFiltra por dispositivo.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/analytics/timeseries' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-campaigns

GET /v1/campaigns

Campanhas de e-mail da conta (incremental).

Escopo campaigns:read, produto E-mail Marketing. Ordem (updated_at, id) crescente; updated_after (exclusivo) ou next_cursor para sincronizar. snake_case; envelope {data, next_cursor}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Saídas200 {data, next_cursor}; 400; 503 (canal não cortado); 502.

Parâmetros

nomeemobrigatóriodescrição
limitquery nãoItens por página.
cursorquery nãoCursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400.
updated_afterquery nãoRFC 3339, exclusivo.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/campaigns' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/campaigns/{id}

Uma campanha.

Escopo campaigns:read. Objeto da campanha, sem envelope. Inexistente ou de outra conta → 404 idêntico. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Saídas200 com o objeto da campanha (recurso único, sem envelope); 404 (inclusive de outra conta).

Parâmetros

nomeemobrigatóriodescrição
idpath simId da campanha (`cmp_…`).

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
404`404 NotFound` — o recurso não existe **ou é de outra conta** (mesma resposta).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/campaigns/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/campaigns/{id}/report

Contadores de entrega e engajamento de uma campanha.

Escopo campaigns:read. Soma dos contadores dos e-mails da campanha: {data: {...}}. Inexistente ou de outra conta → 404 idêntico. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Saídas200 {data:{campaign_id,totals}}.

Parâmetros

nomeemobrigatóriodescrição
idpath simId da campanha (`cmp_…`).

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
404`404 NotFound` — o recurso não existe **ou é de outra conta** (mesma resposta).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/campaigns/id_AQUI/report' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/reports/email/domains

Desempenho por domínio/provedor de destino.

Escopo campaigns:read. Entregas, aberturas e bounces por provedor: {data: {...}}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Onde é usadaoverview, breakdown, timeseries e forms (lista).

Parâmetros

nomeemobrigatóriodescrição
fromquery nãoInício: RFC 3339 ou `YYYY-MM-DD`. Use `period` OU `from`+`to`.
toquery nãoFim: mesmo formato. Vai junto de `from`.
periodquery nãoAtalho: os N dias terminando hoje. Padrão `30d`.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/reports/email/domains' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/reports/email/overview

Panorama de e-mail da conta.

Escopo campaigns:read. Totais da janela em days (não aceita from/to): {data: {...}}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Onde é usadaoverview, breakdown, timeseries e forms (lista).

Parâmetros

nomeemobrigatóriodescrição
daysquery nãoJanela em dias terminando agora.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/reports/email/overview' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/reports/email/timeseries

Série temporal de e-mail.

Escopo campaigns:read. Pontos por granularity (hour aceita janela curta), com os totais e o período anterior: {data: {...}}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Onde é usadaoverview, breakdown, timeseries e forms (lista).

Parâmetros

nomeemobrigatóriodescrição
fromquery nãoInício: RFC 3339 ou `YYYY-MM-DD`. Use `period` OU `from`+`to`.
toquery nãoFim: mesmo formato. Vai junto de `from`.
periodquery nãoAtalho: os N dias terminando hoje. Padrão `30d`.
granularityquery nãoPadrão `day`.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/reports/email/timeseries' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/reports/email/top-emails

E-mails com melhor (ou maior) desempenho.

Escopo campaigns:read. Ranking por order (volume, open_rate, click_rate); min_delivered descarta amostras pequenas: {data: {...}}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Onde é usadaoverview, breakdown, timeseries e forms (lista).

Parâmetros

nomeemobrigatóriodescrição
fromquery nãoInício: RFC 3339 ou `YYYY-MM-DD`. Use `period` OU `from`+`to`.
toquery nãoFim: mesmo formato. Vai junto de `from`.
periodquery nãoAtalho: os N dias terminando hoje. Padrão `30d`.
orderquery não
limitquery nãoQuantidade de linhas.
min_deliveredquery nãoMínimo de entregues para entrar no ranking.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/reports/email/top-emails' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-conversations

GET /v1/conversations

Conversas da conta (incremental).

Escopo conversations:read. Ordenadas por atividade ascendente; updated_after traz o que mudou — inclusive conversa que só recebeu mensagem. Releia com ~5 minutos de sobreposição e deduplique por id (uma transação concorrente pode confirmar depois do carimbo já lido). Não saem chamados de suporte da plataforma, notas internas nem anexos (só attachment_count). lead_id é o id público do lead: o MESMO de GET /v1/leads (id) e de /v1/conversions (lead_id).

Onde é usadaa busca do cabeçalho do hub (asender_plataforma_front, components/busca-do-app.tsx).
Erros400 termo; 401 sem sessão; 404 conta alheia (resolveSessionTenant — nunca 403, que seria oráculo); 502 se o PDP não responde (sem ele não há como saber o que a pessoa pode ver: nega).

Parâmetros

nomeemobrigatóriodescrição
updated_afterquery nãoRFC 3339; estritamente depois.
limitquery nãoItens por página.
cursorquery nãoCursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/conversations' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/conversations/{id}/messages

Mensagens de uma conversa.

Escopo conversations:read. Em ordem de chegada, com cursor. Só mensagens públicas de contato, atendente, bot, aparelho e API (sem notas internas nem mensagens de sistema). Conversa inexistente ou de outra conta: 404 idêntico.

Saídas200 {data,next_cursor}; 400; 401; 404; 503 com Retry-After.

Parâmetros

nomeemobrigatóriodescrição
idpath simId da conversa (`cnv_…`).
created_afterquery nãoRFC 3339; estritamente depois.
limitquery nãoItens por página.
cursorquery nãoCursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
404`404 NotFound` — o recurso não existe **ou é de outra conta** (mesma resposta).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/conversations/id_AQUI/messages' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-data

GET /v1/conversions

Conversões enviadas (ou não) às plataformas de anúncios, e o desfecho.

Escopo conversions:read. Fatos imutáveis do CRM — conversão sent à plataforma (Meta CAPI, Google Ads, TikTok), ignored (com o motivo, ex.: sem consentimento) e result da entrega — em ordem de registro. Use created_after (exclusivo) ou next_cursor. Sem payload cru, token ou hash; lead_id é o id público do lead.

Saídas200 envelope; 400 parâmetro/cursor inválido; 503 se o CRM não responde.

Parâmetros

nomeemobrigatóriodescrição
limitquery não
cursorquery não
created_afterquery não

Respostas

códigosignificado
200OK
400`InvalidParameter`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503CRM indisponível.

Exemplo

curl -X GET 'https://api.asender.net/v1/conversions' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/forms

Formulários da conta e o schema dos seus campos.

Escopo forms:read. Ordem por id; cursor opaco. Não saem origens CORS, destinos (URLs/segredos de webhook) nem o snippet.

Saídas200 envelope; 400 parâmetro inválido; 503 se o pages_api não responde.

Parâmetros

nomeemobrigatóriodescrição
limitquery não
cursorquery não

Respostas

códigosignificado
200OK
400`InvalidParameter`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503Pages indisponível.

Exemplo

curl -X GET 'https://api.asender.net/v1/forms' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/forms/{id}/submissions

Envios de um formulário, do mais antigo ao mais novo.

Escopo forms:read. Ordem (created_at, id) crescente; use created_after (exclusivo) ou next_cursor para sincronizar. O corpo do formulário vem em claro em fields (é o lead); IP, user agent, score e roteamento não saem. Cada página grava a trilha de acesso a dado pessoal da conta. Formulário inexistente ou de outra conta → 404 idêntico.

Saídas200 envelope; 400 parâmetro/cursor inválido; 404 formulário inexistente ou de outra conta; 503 se pages_api ou runtime não respondem.

Parâmetros

nomeemobrigatóriodescrição
idpath sim
limitquery não
cursorquery não
created_afterquery nãoRFC 3339, exclusivo. Ignorado se `cursor` vier.

Respostas

códigosignificado
200OK
400`InvalidParameter`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
404Formulário não encontrado.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503Pages ou runtime indisponível.

Exemplo

curl -X GET 'https://api.asender.net/v1/forms/id_AQUI/submissions' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/leads

Leads da conta, em ordem de atualização (sincronização incremental).

Contatos do CRM da conta do token, do mais antigo ao mais novo por (updated_at, id). Para sincronizar, guarde o maior updated_at recebido e envie-o em updated_after (exclusivo), ou siga next_cursor até null. Paginação por keyset estável: linhas com o mesmo instante não se repetem nem se perdem. Contatos apagados ou anonimizados deixam de aparecer.

Escopo leads:read. Formato: snake_case (diferente do PascalCase das rotas legadas de envio). Campos expostos: ver Lead; não saem CPF, endereço, IP, dono interno nem índices. Origem do CRM fora do ar: 503 com Retry-After.

Saídas200 envelope; 400 parâmetro/cursor inválido; 503 se o CRM não responde.

Parâmetros

nomeemobrigatóriodescrição
limitquery não
cursorquery nãoCursor opaco (`next_cursor`). Inválido → 400.
updated_afterquery nãoRFC 3339, exclusivo. Ignorado se `cursor` vier.

Respostas

códigosignificado
200OK
400`InvalidParameter`.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503CRM indisponível (`ServiceUnavailable`, `Retry-After`).

Exemplo

curl -X GET 'https://api.asender.net/v1/leads' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /v1/leads

Cria um lead na conta.

Cria um contato no CRM da conta da chave (origem api). Escopo leads:write, só chave sk_* (app OAuth nunca escreve leads → 403). A conta vem da chave; o corpo não aceita id, tenant_id, dono, empresa, consentimento, origem/UTM nem dados pessoais fortes — campo desconhecido é 400 (inclusive os nomes em português).

Identidade. Informe email e/ou phone (ao menos um). E-mail e telefone são guardados cifrados e deduplicados por índice cego: outro lead da conta com o mesmo e-mail ou telefone → 409 Conflict.

Idempotência. Envie Idempotency-Key (24 h, por conta e rota): repetir a mesma chave com o mesmo corpo devolve o mesmo resultado (mesmo status e corpo, com o cabeçalho Idempotent-Replayed: true) sem criar outro lead; a mesma chave com outro corpo é 422 IdempotencyKeyReused; enquanto a primeira chamada ainda roda, 409 IdempotencyInProgress (com Retry-After). Falha nossa (5xx) não consome a chave. Chave malformada é recusada com 422, nunca ignorada.

Saídas201 com o lead; 400 corpo inválido; 409 Conflict (já existe lead com esse e-mail/telefone) ou IdempotencyInProgress; 422 IdempotencyKeyReused; 403 token OAuth; 413; 503 se o CRM não responde.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência da escrita de lead (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `422`). Vale 24 h por conta e rota: a repetição com o mesmo corpo devolve o resultado original (`Idempotent-Replayed: true`); com outro corpo, `422 IdempotencyKeyReused`.

Respostas

códigosignificado
201Lead criado (ou resultado guardado, se for repetição da mesma `Idempotency-Key`).
400`ValidationError`: campo desconhecido, tipo errado, e-mail/telefone/etapa/país inválido ou nenhum identificador.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
409`Conflict` (já existe lead com esse e-mail/telefone) ou `IdempotencyInProgress` (`Retry-After`).
413`PayloadTooLarge` (corpo acima de 256 KiB).
422`IdempotencyKeyReused` ou `Idempotency-Key` malformada (`ValidationError`).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503CRM indisponível (`ServiceUnavailable`, `Retry-After`).

Exemplo

curl -X POST 'https://api.asender.net/v1/leads' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /v1/leads/{id}

Um lead pelo id público.

Escopo leads:read. Id inexistente ou de outra conta → 404 idêntico (nunca 403, para não confirmar a existência).

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
404Lead não encontrado.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503CRM indisponível.

Exemplo

curl -X GET 'https://api.asender.net/v1/leads/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /v1/leads/{id}

Atualiza parcialmente um lead.

Altera só os campos enviados (envie ao menos um). Escopo leads:write, só chave sk_*. Texto opcional (last_name, job_title…) com "" limpa o valor; tags presente substitui a lista (vazia limpa); custom_fields mescla (valor "" remove a chave). Id inexistente ou de outra conta → 404 idêntico (nunca 403). Mesmas regras de corpo fechado, validação e Idempotency-Key do POST /v1/leads.

Parâmetros

nomeemobrigatóriodescrição
idpath sim
Idempotency-Keyheader nãoChave de idempotência da escrita de lead (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `422`). Vale 24 h por conta e rota: a repetição com o mesmo corpo devolve o resultado original (`Idempotent-Replayed: true`); com outro corpo, `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200Lead atualizado (ou resultado guardado, se for repetição da mesma `Idempotency-Key`).
400`ValidationError` (corpo vazio, campo desconhecido ou valor inválido).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
404Lead não encontrado (inexistente ou de outra conta).
409`IdempotencyInProgress` (`Retry-After`).
413`PayloadTooLarge`.
422`IdempotencyKeyReused` ou `Idempotency-Key` malformada.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503CRM indisponível.

Exemplo

curl -X PATCH 'https://api.asender.net/v1/leads/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-dns

GET /v1/dns/changes

Histórico das alterações de DNS da conta.

Escopo dns:read (só chave). Histórico append-only de toda tentativa de escrita (ok, failed ou dry_run), do mais novo ao mais antigo, com before/after, o ator (actor: id da chave ou do usuário) e a origem (source: tela, api ou mcp). Parâmetro de consulta desconhecido é 400.

Parâmetros

nomeemobrigatóriodescrição
zonequery nãoId da zona (filtra o histórico).
pagequery não
per_pagequery não

Respostas

códigosignificado
200Alterações e total.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem o escopo (`InsufficientScope`), chave de app OAuth, conta suspensa, escrita fora de produção (`NonProductionBlocked`) ou o token Cloudflare sem permissão na zona (`ProviderPermissionDenied`). `Error.Details.Code` traz o código estável do serviço de DNS.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503Serviço de DNS fora do ar ou a Cloudflare indisponível (`ServiceUnavailable`/`ProviderUnavailable`); tente de novo depois de `Retry-After`.

Exemplo

curl -X GET 'https://api.asender.net/v1/dns/changes' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/dns/zones

Lista as zonas de DNS da conta.

Escopo dns:read (só chave; token OAuth recebe 403). Devolve as zonas de todas as conexões Cloudflare da conta (a conexão — o token da Cloudflare — é colada no painel; não há rota de conexão nesta API). Se a Cloudflare falhar para uma conexão, o cache aparece e a conexão vem em warnings. Sem parâmetros de consulta (parâmetro desconhecido é 400). Resposta Cache-Control: no-store.

Respostas

códigosignificado
200Zonas e avisos, em inglês e snake_case.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem o escopo (`InsufficientScope`), chave de app OAuth, conta suspensa, escrita fora de produção (`NonProductionBlocked`) ou o token Cloudflare sem permissão na zona (`ProviderPermissionDenied`). `Error.Details.Code` traz o código estável do serviço de DNS.
404Zona, registro ou conexão inexistente **ou de outra conta** (`NotFound`, idêntico nos dois casos).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503Serviço de DNS fora do ar ou a Cloudflare indisponível (`ServiceUnavailable`/`ProviderUnavailable`); tente de novo depois de `Retry-After`.

Exemplo

curl -X GET 'https://api.asender.net/v1/dns/zones' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/dns/zones/{zona}/records

Lista os registros de uma zona.

Escopo dns:read (só chave). A Cloudflare é lida por inteiro; page/per_page recortam o resultado. Zona inexistente ou de outra conta: 404 idêntico. Parâmetro de consulta desconhecido (inclusive tenant_id) é 400: filtro digitado errado nunca vira "sem filtro".

Parâmetros

nomeemobrigatóriodescrição
typequery nãoTipo do registro: A, AAAA, CNAME, TXT, MX, NS…
namequery nãoNome completo (FQDN) ou relativo à zona.
pagequery não
per_pagequery não

Respostas

códigosignificado
200Registros e total.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem o escopo (`InsufficientScope`), chave de app OAuth, conta suspensa, escrita fora de produção (`NonProductionBlocked`) ou o token Cloudflare sem permissão na zona (`ProviderPermissionDenied`). `Error.Details.Code` traz o código estável do serviço de DNS.
404Zona, registro ou conexão inexistente **ou de outra conta** (`NotFound`, idêntico nos dois casos).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503Serviço de DNS fora do ar ou a Cloudflare indisponível (`ServiceUnavailable`/`ProviderUnavailable`); tente de novo depois de `Retry-After`.

Exemplo

curl -X GET 'https://api.asender.net/v1/dns/zones/zona_AQUI/records' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /v1/dns/zones/{zona}/records

Cria um registro de DNS na zona.

Escopo dns:write (só chave). Tipos aceitos: A, AAAA, CNAME, TXT, MX, NS. ttl é 1 (automático) ou de 60 a 86400 (padrão 1); priority é obrigatório só no MX. NS no apex é recusado (422 ProtectedRecord).

Controles de escrita.

  • dry_run=1 (ou true) devolve o plano (dry_run: true, action, before, after,

requires_confirmation, confirm) sem tocar a Cloudflare; fica no histórico como dry_run. Qualquer outro valor que não seja 1/true/0/false é 400, para que dry_run=yes nunca execute a escrita de verdade.

  • X-Asender-Confirmar: <nome do registro> é obrigatório para MX, SPF e DMARC do apex

(428 ConfirmationRequired sem ele). O valor é o nome completo do registro.

  • Idempotency-Key (1 a 128 caracteres de [A-Za-z0-9._:-], 24 h por conta): repetir a

mesma chave com o mesmo corpo devolve o resultado original; a mesma chave com outro corpo é 422 IdempotencyKeyReused.

Ambiente e ritmo. Fora de produção toda escrita responde 403 NonProductionBlocked (salvo zonas liberadas por configuração). Leitura 60/min e escrita 10/min por conta (429 RateLimited com Retry-After). Todo registro criado leva o comentário asender:<id da chave> e a tentativa entra no histórico (GET /v1/dns/changes).

Parâmetros

nomeemobrigatóriodescrição
dry_runquery não`1`/`true`: devolve o plano sem tocar a Cloudflare. Outro valor é `400`.
X-Asender-Confirmarheader nãoNome completo do registro. **Obrigatório** no `DELETE` e em qualquer escrita em `MX`, SPF ou DMARC do apex (`428 ConfirmationRequired` sem ele). Um valor diferente do nome também é recusado.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200Com `dry_run`, o plano (nada foi alterado).
201Registro criado.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem o escopo (`InsufficientScope`), chave de app OAuth, conta suspensa, escrita fora de produção (`NonProductionBlocked`) ou o token Cloudflare sem permissão na zona (`ProviderPermissionDenied`). `Error.Details.Code` traz o código estável do serviço de DNS.
404Zona, registro ou conexão inexistente **ou de outra conta** (`NotFound`, idêntico nos dois casos).
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422Recusado pelas regras do DNS: `ProtectedRecord` (NS no apex), `ProviderRejected` (a Cloudflare não aceitou), `IdempotencyKeyReused`. `Error.Details` traz `Code` e `Detail`.
428`ConfirmationRequired`: envie `X-Asender-Confirmar` com o nome do registro (ou use `dry_run=1` para pré-visualizar).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503Serviço de DNS fora do ar ou a Cloudflare indisponível (`ServiceUnavailable`/`ProviderUnavailable`); tente de novo depois de `Retry-After`.

Exemplo

curl -X POST 'https://api.asender.net/v1/dns/zones/zona_AQUI/records' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /v1/dns/zones/{zona}/records/{rid}

Apaga um registro de DNS.

Escopo dns:write (só chave). Exige X-Asender-Confirmar com o nome completo do registro (428 ConfirmationRequired sem ele); use dry_run=1 antes para ver o que seria apagado. A resposta traz o registro como era (deleted) e o histórico guarda o before, para recriá-lo. NS no apex não pode ser apagado (422 ProtectedRecord).

Parâmetros

nomeemobrigatóriodescrição
dry_runquery não`1`/`true`: devolve o plano sem tocar a Cloudflare. Outro valor é `400`.
X-Asender-Confirmarheader nãoNome completo do registro. **Obrigatório** no `DELETE` e em qualquer escrita em `MX`, SPF ou DMARC do apex (`428 ConfirmationRequired` sem ele). Um valor diferente do nome também é recusado.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200Apagado (`deleted`), ou o plano quando há `dry_run`.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem o escopo (`InsufficientScope`), chave de app OAuth, conta suspensa, escrita fora de produção (`NonProductionBlocked`) ou o token Cloudflare sem permissão na zona (`ProviderPermissionDenied`). `Error.Details.Code` traz o código estável do serviço de DNS.
404Zona, registro ou conexão inexistente **ou de outra conta** (`NotFound`, idêntico nos dois casos).
422Recusado pelas regras do DNS: `ProtectedRecord` (NS no apex), `ProviderRejected` (a Cloudflare não aceitou), `IdempotencyKeyReused`. `Error.Details` traz `Code` e `Detail`.
428`ConfirmationRequired`: envie `X-Asender-Confirmar` com o nome do registro (ou use `dry_run=1` para pré-visualizar).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503Serviço de DNS fora do ar ou a Cloudflare indisponível (`ServiceUnavailable`/`ProviderUnavailable`); tente de novo depois de `Retry-After`.

Exemplo

curl -X DELETE 'https://api.asender.net/v1/dns/zones/zona_AQUI/records/rid_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /v1/dns/zones/{zona}/records/{rid}

Altera um registro de DNS (parcial).

Escopo dns:write (só chave). Envie ao menos um de content, ttl, priority, proxied, comment; type e name são imutáveis (apague e recrie). Os controles de escrita (dry_run, X-Asender-Confirmar, Idempotency-Key), o ritmo e o bloqueio fora de produção são os de POST /v1/dns/zones/{zona}/records. Mudar MX, SPF ou DMARC do apex exige X-Asender-Confirmar com o nome do registro (428 sem ele).

Parâmetros

nomeemobrigatóriodescrição
dry_runquery não`1`/`true`: devolve o plano sem tocar a Cloudflare. Outro valor é `400`.
X-Asender-Confirmarheader nãoNome completo do registro. **Obrigatório** no `DELETE` e em qualquer escrita em `MX`, SPF ou DMARC do apex (`428 ConfirmationRequired` sem ele). Um valor diferente do nome também é recusado.
Idempotency-Keyheader nãoChave de idempotência da escrita de DNS (1 a 128 caracteres de `[A-Za-z0-9._:-]`; malformada é `400`). Vale 24 h por conta: a mesma chave com o mesmo pedido devolve o resultado original sem tocar a Cloudflare de novo; a mesma chave com outro pedido é `422 IdempotencyKeyReused`.

Respostas

códigosignificado
200Registro alterado (`record`), ou o plano quando há `dry_run`.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403Sem o escopo (`InsufficientScope`), chave de app OAuth, conta suspensa, escrita fora de produção (`NonProductionBlocked`) ou o token Cloudflare sem permissão na zona (`ProviderPermissionDenied`). `Error.Details.Code` traz o código estável do serviço de DNS.
404Zona, registro ou conexão inexistente **ou de outra conta** (`NotFound`, idêntico nos dois casos).
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
422Recusado pelas regras do DNS: `ProtectedRecord` (NS no apex), `ProviderRejected` (a Cloudflare não aceitou), `IdempotencyKeyReused`. `Error.Details` traz `Code` e `Detail`.
428`ConfirmationRequired`: envie `X-Asender-Confirmar` com o nome do registro (ou use `dry_run=1` para pré-visualizar).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503Serviço de DNS fora do ar ou a Cloudflare indisponível (`ServiceUnavailable`/`ProviderUnavailable`); tente de novo depois de `Retry-After`.

Exemplo

curl -X PATCH 'https://api.asender.net/v1/dns/zones/zona_AQUI/records/rid_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-email

GET /v1/emails

Histórico de emails do tenant da API key.

channel é FIXO em email nesta rota — mandar ?channel=sms é 422 (parâmetro fora da allowlist), não filtro silencioso.

Onde é usadaAPI pública do cliente.
Entradasstatus, q, cursor, limit (allowlist). channel do cliente é IGNORADO: nesta rota o canal é do contrato, não do parâmetro.
Saídas200 com {Messages, NextCursor, HasMore}; 422 em query malformada.

Parâmetros

nomeemobrigatóriodescrição
statusquery não
qquery nãoBusca. `%` e `_` são escapados no repositório.
cursorquery não
limitquery não
created_afterquery nãoRFC3339, estrito (>). Sincronização incremental: a lista passa a sair em ordem **ascendente** e `cursor` anda para frente. Inválido → 422; conta cujo canal ainda não migrou → 501.

Respostas

códigosignificado
200Página de mensagens.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/v1/emails' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/emails/{id}

Um email com corpo, metadata e timeline de eventos.

Mensagem de outro tenant → 404 (não confirma existência). Mensagem deste tenant em outro canal também → 404: nesta rota só email existe.

Onde é usadaAPI pública do cliente.
Saídas200 com {Message, Events}; 404 quando o id não é do tenant (o asender_mkt_api já responde 404 nesse caso, sem confirmar existência) ou quando o id aponta para outro canal — nesta rota só email existe. DEFEITO QUE ESTE HANDLER FECHA (T4 LOW-10 / item 6 do briefing Y2 — oráculo de enumeração): os dois 404 tinham mensagens DIFERENTES. Id inexistente devolvia "Resource not found." (vindo do writeUpstreamError) e id existente no próprio tenant, mas de outro canal, devolvia "Email not found.". Status igual, corpo distinto: bastava comparar a string para descobrir, sem autorização para aquele recurso, QUAIS ids existem — e ainda de brinde o canal deles. O escopo do vazamento era o próprio tenant (id de outro tenant já respondia "Resource not found."), o que mantém a severidade baixa, mas o §22.8 exige que existente-mas-negado e inexistente sejam INDISTINGUÍVEIS. Agora as duas saídas usam a mesma constante.

Parâmetros

nomeemobrigatóriodescrição
idpath simPublic id da mensagem (`msg_<hex>`).

Respostas

códigosignificado
200Detalhe.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.

Exemplo

curl -X GET 'https://api.asender.net/v1/emails/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /v1/emails/batch

Até 500 emails independentes numa chamada.

Resultado parcial é legítimo e explícito. Falha de um item não derruba os outros: cada entrada de Results traz MessageId ou Error, sempre com o Index de origem.

Idempotency-Key vale por item (<chave>:<índice>); cada item traz ReusedIdempotency, e um item que reusa a chave com outro corpo falha com Error.Code IdempotencyConflict.

Item com Source de domínio não verificado (2026-10-02): Status: failed com Error.Code RemetenteNaoVerificado e a Message que diz o domínio e o link de Domínios (ou VerificacaoIndisponivel com a verificação fora). Outras recusas do upstream continuam genéricas (ValidationError/InternalError, sem o motivo interno).

Onde é usadaAPI pública do cliente.
Saídas202 com {Results, Count}; cada item traz MessageId ou Error.
Efeitosescrita no serviço de mensageria por item aceito.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Lote processado (com ou sem itens rejeitados).
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.

Exemplo

curl -X POST 'https://api.asender.net/v1/emails/batch' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /v1/emails/send

Enfileira um email (payload compatível com SES SendEmail).

Persiste no asender_mkt_api (mensagem + outbox no MESMO commit) e o publisher de outbox entrega ao NATS. Não publica direto no broker — seria dual-write.

Serviço de mensageria indisponível → 202 accepted (não 502). O pedido é persistido em disco pela API e reenviado em ordem com a mesma Idempotency-Key (a sua, ou uma gerada antes da 1ª tentativa); veja Idempotency-Key em Parâmetros. 4xx de validação seguem síncronos.

Cc e Bcc entram como destinatários independentes: o serviço materializa uma mensagem por destinatário, que é o comportamento correto de cópia oculta.

Source só de domínio verificado (quebra de contrato, 2026-10-02). O Source tem de ser de domínio VERIFICADO nesta conta (SPF e DKIM, em Admin → Domínios). Senão: 422 RemetenteNaoVerificado (inclusive domínio verificado em OUTRA conta), com Message que nomeia o domínio e traz o link de Domínios; verificação fora → 503 VerificacaoIndisponivel com Retry-After. Nada é enfileirado nem cobrado. Antes desta data o pedido era aceito (202) e o e-mail saía com o remetente da plataforma e o Source em Reply-To. Se o domínio perde a verificação DEPOIS do 202, a mensagem fica failed (RemetenteNaoVerificado) — nunca sai com outro remetente.

Destinatário suprimido é explícito (ADR-0134, 2026-10-21). O Asender não envia a destinatário na supressão global (TLD fora da lista da IANA, domínio de teste, caixa inexistente, duas recusas em 30 dias, entrada manual…). Lote com destinatários enviáveis E suprimidos: 202 com Suppressed[] (Email, Reason, Message) — só os enviáveis ganham mensagem em MessageIds. Todos suprimidos: 422 SuppressedRecipient com a Message humana. Depois, GET /v1/emails/{id} de uma mensagem suprimida traz Status: suppressed, SuppressionReason e ErrorMessage humano; suprimido não é falha. Motivos em SuppressionReason.

Onde é usadaAPI pública do cliente (autenticada por API key).
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency, Suppressed?}; 400 em validação local; 422 quando o asender_mkt_api recusa — com o código RemetenteNaoVerificado quando o Source não é de domínio verificado da conta (2026-10-02: antes o e-mail saía com o remetente da plataforma); 503 VerificacaoIndisponivel quando a verificação está fora; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Aceito e persistido; a entrega é assíncrona. Destinatários suprimidos vêm em `Suppressed[]`. Com o serviço de mensageria indisponível, `Status: accepted` (spool durável; replay com a mesma `Idempotency-Key`).
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
409`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo DIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo original para obter o replay (`ReusedIdempotency: true`).
422`422 SuppressedRecipient` — TODOS os destinatários estão suprimidos; nada foi enfileirado. `Message` traz o(s) texto(s) humano(s) e `Details.Suppressed[]` a lista `{Email, Reason, Message}`. `422 RemetenteNaoVerificado` — o remetente não é de domínio verificado nesta conta (ver `RemetenteOuInvalido`). `422 ValidationError` — demais recusas do upstream.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.
503`503 VerificacaoIndisponivel` — a verificação do domínio do remetente não respondeu. Fail-closed: nada foi enfileirado. Repita depois do `Retry-After` (com a mesma `Idempotency-Key`).

Exemplo

curl -X POST 'https://api.asender.net/v1/emails/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
GET /v1/senders

Remetentes que a chave pode usar como `from` (domínios e caixas da conta).

Descubra os remetentes válidos antes de enviar. O tenant é o da API key; não há parâmetro de conta (query ou header do cliente é ignorado). Fonte: o asender_mail_api, que é quem verifica domínios e guarda as caixas.

  • Domains — todos os domínios da conta. Verified = ativo com SPF

e DKIM ok; só o verificado traz UsableFor: ["emails/send"]. Domínio não verificado é listado com Verified: false e UsableFor: [], para você ver o que falta. DnsStatus vem da verificação automática de DNS: ok, pending (ainda esperando o DNS propagar), outdated (um registro mudou depois de verificado) ou unknown (sem leitura).

  • Mailboxes — caixas ativas. Em domínio verificado: UsableFor

["emails/send", "mail:/v1/emails"]; em domínio não verificado só ["mail:/v1/emails"]. Em POST /v1/emails/send basta o domínio estar verificado; a caixa só é exigida na Mail API (POST /v1/emails).

Escopo: email:send. Mail API fora do ar: 503 SendersUnavailable com Retry-After (nunca uma lista parcial).

Onde é usadasuperfície pública /v1, escopo email:send (RouteScopes).
Saídas200; 401 sem tenant no contexto; 503 com Retry-After se o mail falhar.
Efeitosleituras no mail_api; falha vai ao log com o request id.

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 SendersUnavailable` — o diretório de remetentes (Mail API) não respondeu. Repita depois do `Retry-After`.

Exemplo

curl -X GET 'https://api.asender.net/v1/senders' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-journeys

GET /v1/journeys

Jornadas da conta (incremental).

Escopo journeys:read. Estrutura (gatilho, tipo/ação/espera de cada passo) e estatísticas de fluxo (entries, in_progress, exits). Os parâmetros dos passos (corpo de e-mail, URL de webhook) não saem. updated_after filtra pela última alteração da jornada.

Onde é usadaa busca do cabeçalho do hub (asender_plataforma_front, components/busca-do-app.tsx).
Erros400 termo; 401 sem sessão; 404 conta alheia (resolveSessionTenant — nunca 403, que seria oráculo); 502 se o PDP não responde (sem ele não há como saber o que a pessoa pode ver: nega).

Parâmetros

nomeemobrigatóriodescrição
updated_afterquery nãoRFC 3339; estritamente depois.
limitquery nãoItens por página.
cursorquery nãoCursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/journeys' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/journeys/{id}

Uma jornada.

Escopo journeys:read. Inexistente ou de outra conta: 404 idêntico.

Parâmetros

nomeemobrigatóriodescrição
idpath simId da jornada.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
404`404 NotFound` — o recurso não existe **ou é de outra conta** (mesma resposta).
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/journeys/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-logs

GET /v1/logs

Log das requisições feitas à API com as chaves da conta (sem corpos).

Lista, do mais novo para o mais antigo, as requisições que a sua conta fez à API pública — inclusive as recusadas (400, 401 com chave da conta, 403, 429). A listagem não traz os corpos; use GET /v1/logs/{id}.

O tenant é o da API key; não há parâmetro de conta (tenant_id na query é descartado). Paginação por cursor: repita a chamada com after=<next> enquanto has_more for true.

Escopo: logs:read. Teto próprio de 5 req/s por chave, além do geral. Serviço de logs fora do ar: 503 ServiceUnavailable com Retry-After; o resto da API não é afetado.

Formato: o corpo é repassado do serviço de logs como veio, em snake_case (diferente do PascalCase das demais rotas /v1), e os erros 400/404 vêm no envelope {"error":{"type","code","message"}} (ver LogsErrorEnvelope).

Onde é usadaAPI pública do cliente.
Entradasstatus, q, cursor, limit (allowlist). channel do cliente é IGNORADO: nesta rota o canal é do contrato, não do parâmetro.
Saídas200 com {Messages, NextCursor, HasMore}; 422 em query malformada.

Parâmetros

nomeemobrigatóriodescrição
limitquery nãoItens por página.
afterquery nãoCursor opaco (`next` da página anterior): devolve os registros mais antigos. Adulterado ou de outra conta → 400. Não combine com `before`.
beforequery nãoCursor opaco: devolve os mais novos. `after` e `before` juntos → 400.
status_classquery nãoLista separada por vírgula de `2xx`, `3xx`, `4xx`, `5xx`.
statusquery nãoStatus HTTP exato.
methodquery nãoVerbo HTTP.
pathquery nãoPadrão de rota exato (não o caminho com ids).
api_key_idquery nãoId de uma ou mais chaves da conta, separados por vírgula.
error_codequery nãoCódigo do envelope de erro devolvido.
idempotency_keyquery nãoChave de idempotência efetiva da requisição.
message_idquery nãoId de mensagem devolvido pelo envio (liga o log ao envio).
modequery nãoModo da chave usada.
sincequery nãoRFC 3339. Padrão: agora menos 24 h.
untilquery nãoRFC 3339. Padrão: agora. Janela maior que 31 dias é reduzida a 31 dias antes de `until`.

Respostas

códigosignificado
200OK
400`400 ValidationError` — parâmetro inválido, cursor adulterado ou de outra conta, `after` com `before`. Corpo repassado do serviço de logs (`LogsErrorEnvelope`).
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 InsufficientScope` (chave sem `logs:read`) ou `403 TenantSuspended`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` — o serviço de logs não respondeu. Repita depois do `Retry-After`; o resto da API segue funcionando.

Exemplo

curl -X GET 'https://api.asender.net/v1/logs' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/logs/{id}

Detalhe de uma requisição, com os corpos já redigidos.

Traz request_body e response_body depois da redação: tokens de API, JWT, senhas, cabeçalhos de autorização, códigos de verificação (OTP), cartões e CPF não são gravados; destinatários aparecem mascarados. Corpos acima de 16 KiB são cortados (*_truncated: true). redaction_version diz qual conjunto de regras valeu quando o log foi gravado.

Id de outra conta e id inexistente respondem o mesmo 404, com o mesmo corpo. Escopo: logs:read; mesmo teto de 5 req/s.

Parâmetros

nomeemobrigatóriodescrição
idpath sim

Respostas

códigosignificado
200OK
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 InsufficientScope` (chave sem `logs:read`) ou `403 TenantSuspended`.
404`404 NotFound` — id inexistente **ou de outra conta** (corpo idêntico, sem oráculo de existência). Corpo repassado do serviço de logs.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`503 ServiceUnavailable` — o serviço de logs não respondeu. Repita depois do `Retry-After`; o resto da API segue funcionando.

Exemplo

curl -X GET 'https://api.asender.net/v1/logs/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-messaging

GET /v1/messaging/push/messages

Mensagens de push enviadas pela conta (incremental).

Escopo messaging:read, produto push. Em ordem de criação crescente: use created_after (exclusivo) ou next_cursor para sincronizar. Campos: id, to, status, queued_at, created_at, title. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Parâmetros

nomeemobrigatóriodescrição
limitquery nãoItens por página.
cursorquery nãoCursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400.
created_afterquery nãoRFC 3339, exclusivo. Sem ele a lista começa na mensagem mais antiga.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/messaging/push/messages' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/messaging/push/reports/overview

Panorama de push da conta.

Escopo messaging:read, produto push. Totais por status na janela days: {data: {...}}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Parâmetros

nomeemobrigatóriodescrição
daysquery nãoJanela em dias terminando agora.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/messaging/push/reports/overview' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/messaging/sms/messages

Mensagens de SMS enviadas pela conta (incremental).

Escopo messaging:read, produto sns. Em ordem de criação crescente: use created_after (exclusivo) ou next_cursor para sincronizar. Campos: id, to, status, queued_at, created_at, segments. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Parâmetros

nomeemobrigatóriodescrição
limitquery nãoItens por página.
cursorquery nãoCursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400.
created_afterquery nãoRFC 3339, exclusivo. Sem ele a lista começa na mensagem mais antiga.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/messaging/sms/messages' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/messaging/sms/reports/overview

Panorama de SMS da conta.

Escopo messaging:read, produto sns. Totais por status na janela days: {data: {...}}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Parâmetros

nomeemobrigatóriodescrição
daysquery nãoJanela em dias terminando agora.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/messaging/sms/reports/overview' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/messaging/whatsapp/messages

Mensagens de WhatsApp enviadas pela conta (incremental).

Escopo messaging:read, produto zapify. Em ordem de criação crescente: use created_after (exclusivo) ou next_cursor para sincronizar. Campos: id, to, status, queued_at, created_at. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Parâmetros

nomeemobrigatóriodescrição
limitquery nãoItens por página.
cursorquery nãoCursor opaco (`next_cursor` da página anterior). Estável: não pula nem repete. Inválido → 400.
created_afterquery nãoRFC 3339, exclusivo. Sem ele a lista começa na mensagem mais antiga.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/messaging/whatsapp/messages' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/messaging/whatsapp/reports/overview

Panorama de WhatsApp da conta.

Escopo messaging:read, produto zapify. Totais por status na janela days: {data: {...}}. Conta cujo canal ainda não migrou para a ferramenta dedicada → 503 (ServiceUnavailable, Retry-After). Parâmetro de query fora da lista → 400 (nada é ignorado em silêncio).

Parâmetros

nomeemobrigatóriodescrição
daysquery nãoJanela em dias terminando agora.

Respostas

códigosignificado
200OK
400`400 InvalidParameter` — janela, filtro, `limit`, `cursor`, `dimension` ou parâmetro desconhecido.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403` — credencial sem o escopo da rota (`InsufficientScope`), conta suspensa (`TenantSuspended`) ou, para app OAuth, o usuário que autorizou o app não tem mais acesso à conta ou ao produto (`Forbidden`). Chave `sk_*` recebe o envelope legado (`ErrorEnvelope`); app OAuth recebe `DataError`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 UpstreamError` — a ferramenta de origem respondeu com erro; o corpo dela nunca é repassado.
503`503 ServiceUnavailable` com `Retry-After` — a fonte dos dados está fora; o resto da API não é afetado.

Exemplo

curl -X GET 'https://api.asender.net/v1/messaging/whatsapp/reports/overview' \
  -H 'Authorization: Bearer SEU_TOKEN'

public-push

GET /v1/push/devices

Lista os devices de push da conta da API key.

Contraparte de leitura do registro: quem envia token precisa poder conferir o que está registrado. O tenant vem da API key, NUNCA da query — aceitar tenant_id do cliente aqui seria vazamento cross-tenant.

Onde é usadaSDK/integração do cliente e diagnóstico de entrega.
Entradascursor, limit.
Saídas200 com {Devices, NextCursor, HasMore}; 422 em query malformada. O tenant vem do Principal, NUNCA da query: aceitar tenant_id do cliente aqui seria vazamento cross-tenant direto.

Parâmetros

nomeemobrigatóriodescrição
cursorquery não
limitquery não

Respostas

códigosignificado
200Página de devices, em PascalCase.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.

Exemplo

curl -X GET 'https://api.asender.net/v1/push/devices' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /v1/push/devices

Registra (upsert por token) um device de push.

Idempotente pelo par (tenant, token). Responde 200 sempre, mesmo quando o asender_mkt_api respondeu 201 no primeiro registro.

Só servidor-a-servidor. Pedido com Origin ou Sec-Fetch-* (sinal de navegador) é recusado com 403 ServerKeyInBrowser e logado com o id da chave: a API key de servidor não pode estar num site. Do navegador, use POST /v1/push/site/devices com a chave de site (M15, ADR-0057).

Onde é usadaSDK do cliente, na primeira abertura do app / renovação do token.
Saídas200 com {Device} (upsert: o mesmo token duas vezes não cria dois registros); 400 em validação local; 422 quando o upstream recusa a plataforma.
Efeitosescrita no serviço de mensageria. # Chave de servidor vinda do navegador é recusada (M15) O snippet antigo mandava colar a API key de SERVIDOR no site. Pedido com Origin/Sec-Fetch-* aqui significa exatamente isso: a chave está num JavaScript público. Recusa com 403 ServerKeyInBrowser e loga em Warn com o id da chave, para o dono ser avisado e rotacioná-la. Sem janela de compatibilidade: esta rota nunca respondeu CORS, então nenhum site conseguiu usá-la do navegador com sucesso — não há base instalada a preservar (ADR 0057). O caminho do navegador é POST /v1/push/site/devices, com a chave de site.

Respostas

códigosignificado
200Device registrado ou reativado.
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`TenantSuspended` (tenant não-`active`) ou `ServerKeyInBrowser` (API key de servidor usada a partir de navegador).
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.

Exemplo

curl -X POST 'https://api.asender.net/v1/push/devices' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /v1/push/send

Enfileira um push para aparelhos registrados (DeviceTokens).

O destino é sempre uma lista de DeviceTokens de aparelhos registrados em POST /v1/push/devices (pelo menos um). Envio por tópico não é suportado: o campo Topic é recusado com 400 ValidationError (Error.Details.Topic), junto ou sem DeviceTokens. Sem tokens, 400 com Details.DeviceTokens.

202 quer dizer enfileirado, não entregue. Se nenhum dos tokens é um aparelho ativo da conta (desregistrado ou descadastrado), a resposta é 422 ValidationError e nada é cobrado.

Onde é usadaAPI pública do cliente (autenticada por API key).
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency}; 400 em validação local; 422 quando o asender_mkt_api recusa; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Aceito e persistido; a entrega é assíncrona. Com o serviço dono indisponível, `Status: accepted` (spool durável, replay com a mesma `Idempotency-Key`).
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
409`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo DIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo original para obter o replay (`ReusedIdempotency: true`).
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/v1/push/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /v1/push/site/devices

Registra a inscrição de Web Push do visitante, a partir do navegador.

Rota do snippet de push colado no site do cliente (M15, ADR-0057). Autenticada pela chave PÚBLICA de site (?site_key=pk_push_…, emitida por GET /api/push/site-config), nunca por API key de servidor — que é recusada aqui (401), assim como a chave de site é recusada em toda outra rota. Exige Origin declarado na chave (403 OriginNotAllowed, sem CORS). O OPTIONS (preflight) faz a mesma checagem de chave e origem e responde 204 com CORS da origem aprovada. Teto por (chave, IP) e por chave (PUSH_SITE_RATE_LIMIT_*). O tenant vem da chave assinada e é conferido active no core a cada registro. Upsert por endpoint no asender_mkt_api; a resposta não devolve o registro interno.

Onde é usadasnippet de push colado no site do cliente (asenderSubscribePush).
Saídas200 {Status: "registered"}; 400 validação; 401 principal ausente ou tenant inexistente; 403 tenant suspenso; 5xx de upstream mapeado.
Efeitosescrita em messages.push_devices. A resposta NÃO devolve o device: quem chama é um visitante anônimo, e o registro interno (ids, contato, tenant) não é assunto dele.

Parâmetros

nomeemobrigatóriodescrição
site_keyquery sim
Originheader sim

Respostas

códigosignificado
200Inscrição registrada ou reativada.
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`OriginNotAllowed` (origem ausente/não declarada) ou `TenantSuspended`.
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503Segredo de verificação da chave de site ausente.

Exemplo

curl -X POST 'https://api.asender.net/v1/push/site/devices' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-sms

POST /v1/sms/send

Enfileira um SMS.

Onde é usadaAPI pública do cliente (autenticada por API key).
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency, Suppressed?}; 400 em validação local; 422 quando o asender_mkt_api recusa — com o código RemetenteNaoVerificado quando o Source não é de domínio verificado da conta (2026-10-02: antes o e-mail saía com o remetente da plataforma); 503 VerificacaoIndisponivel quando a verificação está fora; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Aceito e persistido; a entrega é assíncrona. Com o serviço dono indisponível, `Status: accepted` (spool durável, replay com a mesma `Idempotency-Key`).
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
409`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo DIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo original para obter o replay (`ReusedIdempotency: true`).
422`422 ValidationError` — semântica inválida: parâmetro de query fora da allowlist (nomeando o parâmetro), `from > to`, `depth` negativa, corpo de PATCH sem o campo obrigatório, ou recusa do upstream repassada.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/v1/sms/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-telegram

POST /v1/telegram/send

Envia uma mensagem de texto por Telegram, pelo bot da conta.

202 quer dizer enfileirado, não entregue. A mensagem sai pelo bot que a conta conectou no Telegram; BotId (o id público tgb_..., nunca o token do bot) só é necessário quando a conta tem mais de um bot.

O destinatário precisa ter iniciado o bot. O Telegram só deixa o bot escrever para quem o abriu e enviou /start. Para um ChatId que nunca fez isso a resposta é 422 ChatNaoIniciado e nada é enviado: peça ao destinatário que abra o bot e envie /start. Quem bloqueou o bot depois também deixa de receber.

ChatId é o id numérico do chat (positivo para pessoas, negativo para grupos), como texto ou como inteiro. Use Idempotency-Key para repetir o pedido com segurança: a mesma chave com o mesmo corpo não duplica a mensagem; com outro corpo devolve 409.

Chave de servidor nunca vai para o navegador: pedido com Origin / Sec-Fetch-* leva 403 ServerKeyInBrowser. Com o canal fora do ar a resposta é 503 ServiceUnavailable com Retry-After; repita depois.

Onde é usadaAPI pública do cliente (autenticada por API key).
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency, Suppressed?}; 400 em validação local; 422 quando o asender_mkt_api recusa — com o código RemetenteNaoVerificado quando o Source não é de domínio verificado da conta (2026-10-02: antes o e-mail saía com o remetente da plataforma); 503 VerificacaoIndisponivel quando a verificação está fora; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Aceito e enfileirado; a entrega é assíncrona.
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`InsufficientScope` (a chave não tem `telegram:send`), `TenantSuspended` ou `ServerKeyInBrowser`.
404`NotFound`: o `BotId` não existe nesta conta.
409`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo DIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo original para obter o replay (`ReusedIdempotency: true`).
422`ChatNaoIniciado`: esse chat nunca enviou `/start` ao bot (ou `ValidationError` na chave `Idempotency-Key`). Nada é enviado.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`ServiceUnavailable`: o canal de Telegram está fora do ar ou não está configurado. Traz `Retry-After`; repita depois.

Exemplo

curl -X POST 'https://api.asender.net/v1/telegram/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-telegramconta

POST /v1/telegramconta/send

Envia uma mensagem de texto pela conta do Telegram (não por bot).

202 quer dizer enfileirado, não entregue. A mensagem sai pela conta de atendimento que a conta Asender conectou no Telegram; SessionId (o id público tgc_...) só é necessário quando há mais de uma conta conectada.

To aceita @username, telefone ou id de peer. A conta só escreve para diálogos que já existem: para um destino sem conversa a resposta é 422 DialogoInexistente e nada é enviado. Envio em massa pode levar o Telegram a restringir a conta; use com moderação.

Use Idempotency-Key para repetir o pedido com segurança: a mesma chave com o mesmo corpo não duplica a mensagem; com outro corpo devolve 409. Chave de servidor nunca vai para o navegador: pedido com Origin / Sec-Fetch-* leva 403 ServerKeyInBrowser.

Onde é usadaAPI pública do cliente (autenticada por API key).
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency, Suppressed?}; 400 em validação local; 422 quando o asender_mkt_api recusa — com o código RemetenteNaoVerificado quando o Source não é de domínio verificado da conta (2026-10-02: antes o e-mail saía com o remetente da plataforma); 503 VerificacaoIndisponivel quando a verificação está fora; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Aceito e enfileirado; a entrega é assíncrona.
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`InsufficientScope` (a chave não tem `telegramconta:send`), `TenantSuspended` ou `ServerKeyInBrowser`.
404`NotFound`: o `SessionId` não existe nesta conta.
409`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo DIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo original para obter o replay (`ReusedIdempotency: true`).
422`DialogoInexistente`: a conta não tem conversa com esse destino. Nada é enviado. Também `ValidationError` na chave `Idempotency-Key`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`ServiceUnavailable`: o canal está fora do ar ou não está configurado. Traz `Retry-After`; repita depois.

Exemplo

curl -X POST 'https://api.asender.net/v1/telegramconta/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-webhooks

GET /v1/webhooks

Lista os endpoints de webhook da conta da API key.

Escopo webhooks:manage (só chave; token OAuth recebe 403). O tenant vem da chave, nunca da query. O segredo de assinatura não é devolvido aqui.

Onde é usadaAPI pública do cliente.
Entradasstatus, q, cursor, limit (allowlist). channel do cliente é IGNORADO: nesta rota o canal é do contrato, não do parâmetro.
Saídas200 com {Messages, NextCursor, HasMore}; 422 em query malformada.

Respostas

códigosignificado
200Endpoints da conta, em PascalCase.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/v1/webhooks' \
  -H 'Authorization: Bearer SEU_TOKEN'
POST /v1/webhooks

Cadastra um endpoint de webhook.

Escopo webhooks:manage (só chave). A resposta traz o Secret (whsec_…) uma única vez (Cache-Control: no-store): guarde-o agora, ele nunca mais é exibido — perdeu, apague o endpoint e crie outro. O segredo é sempre gerado pelo Asender (não aceita o seu). Com ele você confere a assinatura das entregas (formato Asender Webhook v1, ADR-0133: Asender-Signature: t=<unix>,v1=<hex>, HMAC-SHA256 do segredo sobre "<t>.<corpo cru>").

Validação. Url absoluta, https em produção (http só fora de produção), até 2048 caracteres, sem credencial nem fragmento e sem destino interno (loopback, faixa privada, link-local/metadados de nuvem, localhost, .local, .internal) — o destino é conferido de novo, no endereço resolvido, a cada entrega (anti-SSRF). Events é uma lista de 1 a 32 itens sem repetição, cada um * (tudo), <família>.* (email, sms, whatsapp, push, mail) ou um tipo exato do catálogo entregável: email.queued, email.failed, sms.sent, sms.failed, whatsapp.sent, whatsapp.failed, push.delivered, push.failed, mail.sent, mail.received. Eventos internos (interno.*) nunca são entregues.

Sem Idempotency-Key. Esta rota não guarda estado de idempotência: repetir o POST cria outro endpoint. Em caso de dúvida, liste antes (GET /v1/webhooks).

Onde é usadafluxo de cadastro (AuthHandler.Register faz o equivalente internamente) e recuperação manual quando o provisionamento automático falhou.
Entradas{Name, Slug} — allowlist explícita, ver tenantCreateInput.
Saídas201 com {Tenant:{Id,...}} em PascalCase; 400 JSON inválido OU campo fora da allowlist; 422 nome vazio; o status 4xx do core preservado; 502 quando o core está fora.
Efeitosescreve no asender-core. O owner_user_id vem SEMPRE do principal verificado, nunca do corpo.

Respostas

códigosignificado
201Endpoint criado; o `Secret` só aparece nesta resposta.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
413`413 PayloadTooLarge` — corpo acima de 2 MiB.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/v1/webhooks' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
DELETE /v1/webhooks/{id}

Apaga um endpoint de webhook.

Escopo webhooks:manage (só chave). Apagamento lógico; as entregas pendentes para ele deixam de sair. Inexistente ou de outra conta: 404.

Onde é usadaAPI pública, escopo webhooks:manage.

Respostas

códigosignificado
204Apagado. Sem corpo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X DELETE 'https://api.asender.net/v1/webhooks/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
GET /v1/webhooks/{id}

Um endpoint de webhook da conta.

Escopo webhooks:manage (só chave). Inexistente ou de outra conta: 404 idêntico. Sem o segredo.

Respostas

códigosignificado
200O endpoint.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X GET 'https://api.asender.net/v1/webhooks/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN'
PATCH /v1/webhooks/{id}

Altera URL, eventos e/ou estado de um endpoint.

Escopo webhooks:manage (só chave). Envie ao menos um campo; as mesmas regras do POST valem para o que for enviado. Não rotaciona o segredo. Active: false pausa as entregas.

Onde é usadaAPI pública, escopo webhooks:manage.

Respostas

códigosignificado
200O endpoint atualizado.
400`400 ValidationError` — JSON inválido, campo desconhecido (a mensagem nomeia o campo: `unknown field "X"`) ou tipo errado.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X PATCH 'https://api.asender.net/v1/webhooks/id_AQUI' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'
POST /v1/webhooks/{id}/test

Envia um evento `webhook.test` assinado a UM endpoint.

Escopo webhooks:manage (só chave). Faz um POST síncrono, sem retentativa, só para o endpoint pedido (o evento webhook.test não é assinável e não vai para os outros endpoints da conta). Leva os cabeçalhos atuais (X-Asender-*) e os do formato v1 (Asender-Webhook-Id, Asender-Event, Asender-Signature). O corpo da resposta do seu servidor não é devolvido.

Responde 200 sempre que o endpoint existe; o resultado da entrega está no corpo: Delivered, StatusCode (0 se não houve resposta) e Reason — "" (entregue), non_2xx, transport_error, destination_blocked (destino interno) ou non_production_blocked (fora de produção só domínios de teste, p.ex. *.test.asender.com.br/.test/.example; nada chega a terceiros).

Onde é usadaAPI pública, escopo webhooks:manage.

Parâmetros

nomeemobrigatóriodescrição
idpath simId público do endpoint (`whk_…`).

Respostas

códigosignificado
200Resultado do envio de teste.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`403 TenantSuspended` — a API key é válida mas o tenant não está `active`. Fail closed: status vazio também barra.
404`404 NotFound`. **Cobre dois casos de propósito:** o recurso não existe, ou existe e não é seu. Diferenciar seria oráculo de enumeração.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/v1/webhooks/id_AQUI/test' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-whatsapp

POST /v1/whatsapp/send

Enfileira uma mensagem de WhatsApp (texto livre ou template).

Mesmo padrão do SMS: 202 quer dizer enfileirado, não entregue. O opt-out do canal (destinatário que saiu do WhatsApp), a quota do plano e o limite de vazão são aplicados no enfileiramento; quem saiu do canal fica de fora e entra na contagem Suppressed (se ninguém sobra, 422).

Janela de 24h da Meta. Body (texto livre) só é entregue se o destinatário escreveu para o seu número nas últimas 24h. Fora da janela a Meta recusa e a mensagem termina failed (OutsideWindow) — isso é assíncrono, o 202 já foi dado. Para abrir conversa use Template: o slug de um template aprovado na Meta e registrado na conta (Admin → WhatsApp → Templates), com Variables posicionais "1".."N" — exatamente o número que o template exige, senão 422. Mande Body ou Template, nunca os dois.

From é o phone_number_id numérico do número da conta; omitido, vale o número padrão. Sem credencial do WhatsApp configurada na conta, o envio não sai (a mensagem fica failed).

Chave de servidor nunca vai para o navegador: pedido com Origin / Sec-Fetch-* leva 403 ServerKeyInBrowser.

Onde é usadaAPI pública do cliente (autenticada por API key).
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency, Suppressed?}; 400 em validação local; 422 quando o asender_mkt_api recusa — com o código RemetenteNaoVerificado quando o Source não é de domínio verificado da conta (2026-10-02: antes o e-mail saía com o remetente da plataforma); 503 VerificacaoIndisponivel quando a verificação está fora; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Aceito e enfileirado; a entrega é assíncrona.
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`TenantSuspended` (conta não ativa) ou `ServerKeyInBrowser` (chave de servidor usada de um navegador).
409`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo DIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo original para obter o replay (`ReusedIdempotency: true`).
422`ValidationError`: template inexistente na conta, variáveis em número errado, ou todos os destinatários fora do canal (opt-out). Nada é enfileirado nem cobrado.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
502`502 InternalError` — upstream inalcançável ou 5xx. O motivo real fica no log com `RequestId`; o cliente recebe mensagem genérica.

Exemplo

curl -X POST 'https://api.asender.net/v1/whatsapp/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'

public-whatsweb

POST /v1/whatsweb/send

Envia uma mensagem de texto pelo WhatsApp da conta, via sessão do WhatsApp Web (canal não oficial).

Risco do canal não oficial: this channel uses an unofficial WhatsApp Web session; the number may be banned by WhatsApp. Use apenas com um número que a conta aceita arriscar, e prefira POST /v1/whatsapp/send (canal oficial) sempre que possível.

202 quer dizer enfileirado, não entregue. A mensagem sai pelo número pareado pela conta; SessionId (o id público wws_...) só é necessário quando a conta tem mais de uma sessão.

Só responde a conversas que já existem. O canal escreve para quem já falou com este número. Para um número que nunca escreveu a resposta é 422 ChatInexistente e nada é enviado, a menos que o gestor da conta habilite novos contatos por dia. To é o telefone em E.164 (+5511987654321).

Use Idempotency-Key para repetir o pedido com segurança: a mesma chave com o mesmo corpo não duplica a mensagem; com outro corpo devolve 409. Chave de servidor nunca vai para o navegador: pedido com Origin / Sec-Fetch-* leva 403 ServerKeyInBrowser.

Onde é usadaAPI pública do cliente (autenticada por API key).
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency, Suppressed?}; 400 em validação local; 422 quando o asender_mkt_api recusa — com o código RemetenteNaoVerificado quando o Source não é de domínio verificado da conta (2026-10-02: antes o e-mail saía com o remetente da plataforma); 503 VerificacaoIndisponivel quando a verificação está fora; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey.

Parâmetros

nomeemobrigatóriodescrição
Idempotency-Keyheader nãoChave de idempotência do envio (1 a 128 caracteres de `[A-Za-z0-9._:-]`; uma chave malformada é recusada com `422`, nunca ignorada). **Tem precedência** sobre o campo `IdempotencyKey` do corpo. - **Mesma chave + mesmo corpo** = replay: devolve `202` com os ids da mensagem ORIGINAL e `ReusedIdempotency: true`; nada é enviado nem cobrado de novo. - **Mesma chave + corpo DIFERENTE** = **`409 IdempotencyConflict`**: o texto novo NÃO é enviado e a mensagem original não muda. Use uma chave nova para uma mensagem nova. O "mesmo corpo" ignora a ordem dos campos e o próprio campo `IdempotencyKey`. Em `/v1/emails/batch` a chave vale por item (`<chave>:<índice>`). A checagem de corpo vale hoje para SMS, push e WhatsApp; no e-mail, até o canal migrar para a ferramenta dedicada, o replay devolve o `202` original sem comparar o corpo. **Aceite assíncrono e replay (resiliência).** Se o serviço de mensageria estiver indisponível (timeout, conexão recusada ou 5xx do gateway), o envio NÃO falha: a API persiste o pedido em disco e responde **`202` com `Status: accepted`**, `MessageIds: []` e `IdempotencyKey` — a chave EFETIVA, também devolvida no header de resposta `Idempotency-Key`. Se o cliente não mandou chave, a API gera uma (`bffauto:…`) ANTES da primeira tentativa. O pedido é reenviado ao destino, em ordem, com **essa mesma chave** quando ele volta; por isso o replay nunca duplica o envio, nem quando a primeira tentativa chegou ao destino e só a resposta se perdeu. Para obter os ids depois, reenvie o MESMO `POST` com a mesma `Idempotency-Key`: a resposta traz os ids originais e `ReusedIdempotency: true`. Erros de validação (`4xx`) continuam síncronos e nunca entram na fila; um pedido aceito que o destino recusar de vez no replay (4xx) fica retido em arquivo de mortos para ação manual, com alerta.

Respostas

códigosignificado
202Aceito e enfileirado; a entrega é assíncrona.
400`400 ValidationError` com `Error.Details` mapeando campo → motivo.
401`401 AuthorizationError` — credencial ausente, malformada, inválida ou expirada.
403`InsufficientScope` (a chave não tem `whatsweb:send`), `TenantSuspended` ou `ServerKeyInBrowser`.
404`NotFound`: o `SessionId` não existe nesta conta.
409`409 IdempotencyConflict` — a `Idempotency-Key` já foi usada com um corpo DIFERENTE. Nada foi enviado. Use uma chave nova, ou reenvie o corpo original para obter o replay (`ReusedIdempotency: true`).
422`ChatInexistente`: esse número nunca escreveu para este WhatsApp. Nada é enviado. Também `ValidationError` na chave `Idempotency-Key`.
429`429 Throttling` — rate limit por API key/usuário. Acompanha `Retry-After`.
503`ServiceUnavailable`: o canal está fora do ar ou não está configurado. Traz `Retry-After`; repita depois.

Exemplo

curl -X POST 'https://api.asender.net/v1/whatsweb/send' \
  -H 'Authorization: Bearer SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <uuid-v4>' \
  -d '{ }'