Plataforma

https://api.asender.net · API key (/v1) + sessão OIDC/OAuth 2.1 (/api) · 152 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

A superfície pública de máquina. Envie a API key da conta em Authorization:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx

O tenant_id vem da chave. 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.

/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

As chaves são gerenciadas pela própria Platform API, na superfície de sessã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"] }'

# `scopes` é obrigatório (veja a tabela de escopos); sem ele a emissão responde 400.

# 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 de API key é limitada por principal (a chave), por janela fixa. O teto vem do plano da conta (ou do default do serviço). 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

Ao estourar, a resposta é 429:

{ "Error": { "Type": "Sender", "Code": "Throttling", "Message": "Rate limit exceeded." }, "RequestId": "req_…" }
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
404NotFoundrecurso fora do escopo do principal (inclui conta alheia)
409IdempotencyConflicta Idempotency-Key já foi usada com um corpo diferente — ver Idempotência
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)
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.

Superfície da API

/v1/* — pública (API key)

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.

escopolibera
email:sendPOST /v1/emails/send, POST /v1/emails/batch, GET /v1/senders
email:readGET /v1/emails, GET /v1/emails/{id}
sms:sendPOST /v1/sms/send
push:sendPOST /v1/push/send
push:devicesGET/POST /v1/push/devices
whatsapp:sendPOST /v1/whatsapp/send
account:readGET /v1/account/usage
logs:readGET /v1/logs, GET /v1/logs/{id} (log das requisições da própria conta)
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)
forms:readler formulários, em api.pages.asender.net (não são rotas de api.asender.net)
forms:writecriar, editar e apagar formulários e enviar leads (submissões), em api.pages.asender.net
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)
pages:writecriar e editar páginas (versões, rascunho, SEO, importar, duplicar, mudar de domínio, mídia), em api.pages.asender.net
pages:publishpublicar e despublicar páginas (ação visível ao público; escopo separado do pages:write), em api.pages.asender.net

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.

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.

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 e push

POST /v1/sms/send (To em E.164, Body) e POST /v1/push/send respondem 202 com enfileirado, não entregue.

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ã." } }'

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.

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 e WhatsApp; 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.

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/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

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 '{ }'
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-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.

Onde é usadatela de audiência do dashboard.
Entradasq, list_id, 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
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 '{ }'
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'
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}; 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 '{ }'
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: POST /api/lists, POST /api/lists/{id}/members e GET /api/lists/{id}/contacts seguem exigindo marketing.

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 o produto marketing (a LEITURA em GET /api/lists aceita também as ferramentas de despacho; 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 '{ }'
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'
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/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}/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.

Onde é usadao formulário "acrescentar relay" no backoffice.
Efeitosuma escrita no core, registrada no log com o ator (nunca o segredo).

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}/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'
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/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 '{ }'

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, mais o contexto da própria chave.

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'

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

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.

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.

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.

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 — 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.

Respostas

códigosignificado
202Aceito e persistido; a entrega é assíncrona.
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 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…).
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-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.

Onde é usadasuperfície pública /v1, escopo logs:read.

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-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.

Respostas

códigosignificado
202Aceito e persistido; a entrega é assíncrona.
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).
Entradascorpo PascalCase {From, To[], Body, IdempotencyKey, Tags}.
Saídas202 com {MessageId, MessageIds, Status, ReusedIdempotency}; 400 em validação local; 401 sem tenant; 422 quando o asender_mkt_api recusa; 502 se ele está fora.
Efeitosescrita no serviço de mensageria. Idempotente por IdempotencyKey — que agora é de fato honrado, porque quem grava é o asender_messages.

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.

Respostas

códigosignificado
202Aceito e persistido; a entrega é assíncrona.
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-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}; 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.

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 '{ }'