Erros
Toda recusa tem a mesma forma, e o campo que a sua máquina lê é o "code": 59 códigos de vocabulário fechado, cada um com um status HTTP fixo — e 7 deles nunca chegam a um App.
Como eles chegam até você
Toda recusa vira um TrivoError, e o campo que o seu código lê é o code.
import { TrivoError } from "@trivo/sdk";
async function apagar() {
try {
await app.rest.deleteMessage({ serverId, channelId, messageId });
} catch (erro) {
if (!(erro instanceof TrivoError)) throw erro;
if (erro.is("MISSING_PERMISSION", "NOT_MESSAGE_AUTHOR")) return;
// `code` nulo quer dizer que não foi o Trivo que respondeu — um proxy, o
// seu túnel, um WAF. Aí o que existe é o `status`.
if (erro.code === null) console.error(`resposta de fora: ${erro.status}`);
throw erro;
}
}O 429 quase nunca chega até aqui: o SDK espera o Retry-After e repete sozinho, e só devolve o erro quando o prazo passa do teto que você configurou.
Referência de transporte
A forma no fio
O que viaja no fio, para transparência, depuração e compatibilidade — não é um segundo jeito de construir App. Se você está escrevendo um App, o SDK já faz isto.
{
"code": "MISSING_PERMISSION",
"message": "Você não tem permissão para isso.",
"erro": "Você não tem permissão para isso."
}code— vocabulário fechado. É o contrato estável.message— texto em português, para gente ler. Muda quando a copy melhorar, e vai mudar.erro— apelido demessage, por compatibilidade com o cliente web. Ele vai sair; não construa nada em cima dele.
Os extras
Conforme o caso, a recusa carrega campos ao lado do código. Todos são opcionais por definição — quem lê o corpo decide pelo code.
| Campo | Quando aparece |
|---|---|
currentRevision | Em STALE_REVISION: a revisão que o servidor tem agora. |
retryAfterSeconds | Em RATE_LIMIT_EXCEEDED, junto do cabeçalho Retry-After. |
resetAt | No mesmo caso, quando o instante da liberação é conhecido. |
expiresAt | Em SERVER_SUSPENDED: quando a suspensão acaba. Em APP_RESTRICTED: quando a restrição do App acaba (null quando ela não tem prazo). |
validIntents | Em UNKNOWN_INTENT: a lista das famílias que existem. |
ref | Em INTERNAL_ERROR: a referência que casa com a linha do nosso log. |
O catálogo, por status
Estes são os códigos que uma credencial de App pode receber. Os que não podem estão na seção seguinte, e ficam de fora daqui de propósito: procurar num catálogo de plataforma inteira o que a sua integração jamais verá é tempo perdido em cima da recusa errada.
400 — Pedido malformado
| Código | O que aconteceu |
|---|---|
UNSUPPORTED_VERSION | Você pediu uma versão da API que o Trivo não serve. A resposta traz a lista do que vale em `validVersions`. |
EMPTY_INTENTS | O parâmetro `intents` veio presente e vazio. Omita o parâmetro para receber o conjunto padrão. |
UNKNOWN_INTENT | Família de intent que não existe. O corpo lista as que valem em `validIntents`. |
401 — Sem credencial
| Código | O que aconteceu |
|---|---|
MISSING_CREDENTIAL | Sem credencial válida. Uma resposta só para sessão ausente e para Bearer que não vale. |
403 — Proibido
| Código | O que aconteceu |
|---|---|
MISSING_PERMISSION | Falta a permissão para este gesto. Para App, lembre da interseção: cargos ∩ escopos. |
ROLE_OUTRANKS_YOU | O cargo está acima do seu posto. |
MEMBER_OUTRANKS_YOU | A pessoa está acima do seu posto. Para um App, o posto é o do cargo mais alto que ele recebeu. |
CANNOT_GRANT_PERMISSION | Você tentou conceder o que não tem. |
OWNER_ONLY | Só o dono do servidor faz isto. |
TARGET_PROTECTED | O alvo não pode receber este gesto. |
SERVER_PROTECTED | O servidor não pode receber este gesto. |
NOT_MESSAGE_AUTHOR | Editar ou apagar mensagem de outra autoria. |
SERVER_SUSPENDED | O servidor está suspenso. O corpo traz `expiresAt`. |
SERVER_BANNED | O servidor está banido. |
APP_RESTRICTED | O App está restrito por sanção: ele continua autenticando, conectado e recebendo entrega, e só não pode PÔR COISA NOVA na frente dos outros — publicar e editar mensagem, reagir, criar canal/cargo/categoria e responder interação (reply, modal e sugestões de autocompletar; `defer` passa). Apagar, ajustar o que já existe e registrar comandos continuam. O corpo traz `expiresAt` quando há prazo. Não existe irmão para a restrição da DONA do App: ela não alcança o App em execução. |
FEATURE_DISABLED | O recurso está desligado para este escopo. |
404 — Não encontrado
| Código | O que aconteceu |
|---|---|
SERVER_NOT_FOUND | Servidor inexistente, id torto, ou o App não está instalado nele. Resposta única, de propósito. |
CHANNEL_NOT_FOUND | Canal inexistente, de voz, arquivado ou de outro servidor. |
MESSAGE_NOT_FOUND | Mensagem inexistente ou já apagada. |
ROLE_NOT_FOUND | Cargo inexistente neste servidor. |
CATEGORY_NOT_FOUND | Categoria inexistente neste servidor. |
APP_NOT_FOUND | App inexistente, ou não instalado aqui. |
TARGET_NOT_MEMBER | A pessoa alvo não é membro deste servidor. |
INTERACTION_NOT_FOUND | Interação inexistente, de outro App, ou já varrida. |
COMMAND_NOT_FOUND | Comando inexistente, de outro App, ou de um App que não está instalado aqui. |
COMPONENT_NOT_FOUND | O botão ou a seleção não está naquela mensagem, ou está desligado. |
FORM_NOT_FOUND | O formulário não abriu daquela interação, já foi enviado, venceu, ou é de outra pessoa. Uma resposta para os quatro. |
409 — Conflito
| Código | O que aconteceu |
|---|---|
STALE_REVISION | Alguém mudou o servidor antes de você. O corpo traz `currentRevision`. |
NAME_TAKEN | Esse nome já está em uso. |
MAILBOX_EXPIRED | A caixa do gateway não existe mais. Abra outra. |
INTERACTION_EXPIRED | O prazo da interação passou. O estado é derivado do prazo, não de um temporizador. |
INTERACTION_ALREADY_CONFIRMED | Outra execução já confirmou esta interação. É o lock de quem não guarda estado. |
422 — Entrada recusada
| Código | O que aconteceu |
|---|---|
INVALID_INPUT | O corpo ou o parâmetro não foi entendido, ou foi entendido e não vale. |
INVALID_IMAGE | A imagem não passou nas regras de formato ou tamanho. |
CANNOT_TARGET_SELF | Este gesto não vale sobre si mesmo. |
BASE_ROLE_IMMUTABLE | O cargo base (@everyone) não aceita esta mudança. |
APP_ROLE_IMMUTABLE | O cargo automático de uma instalação nasce e morre com ela. |
SCOPE_NOT_REQUESTED | O App não pediu esse escopo, então ele não pode ser concedido. |
ADMIN_CONFIRMATION_REQUIRED | Conceder `administrador` exige confirmação própria. |
NAME_MISMATCH | O nome digitado não confere com o do recurso. |
TEXT_BLOCKED | O texto foi barrado pela moderação preventiva. |
CHANNEL_LIMIT_EXCEEDED | O servidor chegou ao teto de canais. |
ROLE_LIMIT_EXCEEDED | O servidor chegou ao teto de cargos. |
CATEGORY_LIMIT_EXCEEDED | O servidor chegou ao teto de categorias. |
FOLLOWUP_LIMIT_EXCEEDED | Esta interação já teve cinco seguimentos. |
COMMAND_LIMIT_EXCEEDED | Um teto de comandos estourou: os do App (globais ou por servidor), ou os visíveis no servidor. |
APP_MISSING_SCOPE | O comando exige uma permissão que a instalação deste App já não autoriza. |
ENDPOINT_REJECTED | O endereço do endpoint não passa nas guardas de saída (https, porta 443, endereço público). |
ENDPOINT_UNREACHABLE | O endpoint não respondeu 2xx com o desafio ecoado no prazo. |
429 — Ritmo
| Código | O que aconteceu |
|---|---|
RATE_LIMIT_EXCEEDED | Teto de ritmo. Espere o que o `Retry-After` disser — é a única decisão possível. |
500 — Falha do lado de cá
| Código | O que aconteceu |
|---|---|
INTERNAL_ERROR | Falha não prevista do lado do Trivo. O corpo traz `ref`, que casa com a linha do log. |
503 — Indisponível
| Código | O que aconteceu |
|---|---|
SERVICE_UNAVAILABLE | Infraestrutura conhecida fora do ar. |
O que nunca chega a um App
O catálogo é da plataforma inteira, e o back office, a entrada por sessão e a conversa privada leem a mesma lista. Os códigos abaixo saem de molduras que não aceitam Authorization: Bearer — o corte é do núcleo, e não uma leitura desta página. Uma ramificação escrita para qualquer um deles é código morto no seu bot.
| Código | Status | Por que ele não alcança você |
|---|---|---|
ORIGIN_REJECTED | 403 | A defesa de origem recusou. Não alcança App: Bearer não é credencial de ambiente. |
DM_NOT_FOUND | 404 | Conversa privada inexistente. Nenhuma rota de DM aceita credencial de App. |
NOT_FOUND | 404 | O 404 mudo do back office. |
PLATFORM_OWNER_ONLY | 403 | Reservado a quem opera a plataforma. |
ACCOUNT_RESTRICTED | 403 | A conta está restrita por sanção. |
ACCOUNT_SUSPENDED | 403 | A entrada por sessão foi recusada: a conta está suspensa (`expiresAt` traz o prazo). Não alcança App. |
ACCOUNT_BANNED | 403 | A entrada por sessão foi recusada: a conta foi banida. Não alcança App. |
A pergunta maior — o que um App não alcança de jeito nenhum — tem página própria em O que um App nunca alcança.
O 404 que não distingue
SERVER_NOT_FOUND serve ao servidor inexistente, ao id torto, a quem não é membro, a quem está banido e ao App não instalado. É resposta única de propósito: um cliente que quisesse saber qual dos cinco estaria pedindo exatamente o que a recusa existe para não contar. O mesmo vale para CHANNEL_NOT_FOUND, APP_NOT_FOUND e INTERACTION_NOT_FOUND.
Credencial recusada e gesto recusado
Um App suspenso ou banido não autentica: toda porta responde MISSING_CREDENTIAL, e o gateway fecha. Um App restrito autentica normalmente, fica conectado e continua recebendo interação — o que ele não faz é escrever, e é aí que sai APP_RESTRICTED. Os dois casos ficam separados porque o conserto de quem integra é diferente: no primeiro não adianta tentar de novo com o mesmo token; no segundo o token está bom e o bot deve seguir escutando.
Os dois que parecem iguais e não são
INTERACTION_EXPIRED e INTERACTION_ALREADY_CONFIRMED ficam separados porque exigem conserto diferente de quem integra: o primeiro é "o seu handler está lento"; o segundo é "você recebeu a mesma entrega duas vezes". Fundi-los economizaria um código e custaria a informação que resolve o problema.
Como esta lista cresce
Nenhum código sem produtor: um código declarado "para o futuro" é uma ramificação que o seu SDK escreve e nunca exercita, e um dia alguém a apaga por parecer morta. Código novo entra junto da recusa que o produz.
Renomear ou remover um código existente é quebra de contrato. Dentro de uma versão, nada some. O que pode acontecer é um código novo aparecer — então trate o desconhecido como "falhou, e eu não sei classificar", e não como impossível.