REST
Tudo o que o App escreve passa por app.rest — as mesmas rotas do produto, com uma moldura que aceita credencial de App. Não existe um /v1 paralelo: duas rotas para o mesmo recurso seriam duas verdades de autorização.
O objeto que faz tudo
await app.rest.whoAmI(); // o smoke da credencial
await app.rest.getServer({ serverId }); // retrato + revision
await app.rest.listMembers({ serverId });
await app.rest.listMessages({ serverId, channelId, beforeId });
await app.rest.sendMessage({ serverId, channelId, content, clientRef });
await app.rest.editMessage({ serverId, channelId, messageId, content });
await app.rest.deleteMessage({ serverId, channelId, messageId });
await app.rest.toggleReaction({ serverId, channelId, messageId, emoji: "👍" });
await app.rest.createChannel({ serverId, name, type: "text", revision });
await app.rest.updateChannel({ serverId, channelId, revision, name });
await app.rest.moveChannel({ serverId, channelId, revision, categoryId });
await app.rest.archiveChannel({ serverId, channelId, revision });
await app.rest.createRole({ serverId, name, permissions, revision });
await app.rest.updateRole({ serverId, roleId, revision, permissions });
await app.rest.deleteRole({ serverId, roleId, revision });
await app.rest.addMemberRole({ serverId, userId, roleId, revision });
await app.rest.removeMemberRole({ serverId, userId, roleId, revision });
await app.rest.addAppRole({ serverId, appId, roleId, revision });
await app.rest.removeAppRole({ serverId, appId, roleId, revision });
await app.rest.getSigningKeys();Cada um deles já carrega a credencial, a versão da API, a retentativa e o respeito ao Retry-After — os tetos, e o que o 429 traz no corpo, estão em Limites e ritmo. Precisa de algo que ainda não virou método? app.rest.request() fala com qualquer rota com o mesmo tratamento de 429 e o mesmo erro — e continua sendo o SDK, não um caminho paralelo.
Fora desta lista há mais quatro métodos, os de registrar comando: eles têm página própria em Comandos, porque o que decide o que você escreve ali não é a rota, é a árvore de opções.
- Identificadores públicos são snowflakes em string. Não os converta para número.
- Toda recusa vira
TrivoError, comcodede vocabulário fechado. Ramifique nocode, nunca na frase.
Ler o servidor, e o que o retrato responde
can é o que o ator desta chamada alcança neste servidor, para desenhar interface. Quem decide de verdade continua sendo o servidor, a cada mutação. Para um App, o que ele pode é a interseção dos cargos que ele recebeu com os escopos que a instalação autorizou.
myRank é o posto do ator, e para um App ele é o do cargo mais alto que ele tiver — como o de qualquer membro, e não um valor fixo. Quem impede a escalada não é a posição, é o teto: sem o escopo autorizado, cargo nenhum, por mais alto, produz capacidade. Os dois eixos estão em Permissões e escopos.
O que vem no retrato
channelsé o que o ator vê — é esta lista que desenha uma barra lateral, e um canal que o App não alcança simplesmente não está nela.managedChannelssó aparece comgerenciar_canais, e traz os canais que existem, restritos inclusive. Ausente é diferente de vazio: vazio seria "você administra e não há canal nenhum". É existência, nunca conteúdo — um canal aqui e não emchannelsé um canal que o seu App pode mudar e não pode ler.categories,roleserevision: a revisão é a que toda mutação administrativa precisa devolver.can,myRankeisOwnerdescrevem o ator. Eserver.notificationDefaulté o aviso que a casa sugere a quem nunca escolheu nada — vale para o App instalado como vale para gente, e não é decisão de moderação.
Mensagens
Paginação
Página fixa de 50, sem limit e sem cursor. Para subir o histórico, mande em antesDe o publicId da mensagem mais antiga que você já tem.
Reações
const { count, reacted } = await app.rest.toggleReaction({
serverId,
channelId,
messageId,
emoji: "👍",
});É um toggle, e não um par de métodos: a identidade de uma reação é mensagem mais emoji mais quem reagiu, e o App só alcança a linha dele. Chamar de novo com o mesmo emoji tira a reação, e o que volta é o agregado — count e reacted.
O emoji pode ser qualquer um do conjunto RGI do Unicode — o mesmo que um teclado produz: sequências de família, tons de pele e bandeiras inclusive. O que não for emoji volta como INVALID_INPUT.
Mande a grafia totalmente qualificada (❤️, e não ❤). O servidor conserta as variantes que reconhece e guarda sempre a canônica — duas grafias do mesmo emoji seriam dois chips na mesma mensagem —, mas o que volta na listagem é a canônica, e é por ela que se compara.
Os seis do seletor rápido, para quem quiser começar por eles:
👍 ❤️ 😂 😮 😢 🎉
Referência de transporte
As rotas, e o que cada uma responde
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.
Corpo e resposta em application/json; credencial em Authorization: Bearer, nunca em query string. É o que app.rest chama por baixo.
Servidor
| Rota | Resposta |
|---|---|
GET /api/servidores/{servidor} | server, channels, categories, roles, revision, can, myRank, isOwner. |
GET /api/servidores/{servidor}/membros | members, roles (sem permissions), truncated. Até 500 pessoas, sem paginação; os Apps instalados vêm na mesma lista, com isApp: true. |
Mensagens
| Rota | Corpo | Resposta |
|---|---|---|
GET /api/servidores/{servidor}/canais/{canal}/mensagens | Query antesDe (opcional). | messages (50 por página, cronológico crescente) e reactions, um mapa pelo publicId da mensagem. |
POST /api/servidores/{servidor}/canais/{canal}/mensagens | content; opcionais clientRef e attachments. attachments reaproveita a chave de um anexo que já existe: o App não sobe arquivo, porque a rota de anexo não aceita Bearer. | 201 com message, e moderationWarning quando houver. |
PATCH /api/servidores/{servidor}/canais/{canal}/mensagens/{mensagem} | content. | message. Só alcança mensagem do próprio App. |
DELETE /api/servidores/{servidor}/canais/{canal}/mensagens/{mensagem} | Nenhum. | { "ok": true }. A própria sempre; a dos outros exige gerenciar_mensagens. |
Canais e cargos
Toda mutação administrativa exige revision, e responde com a coleção inteira mais a revisão nova. categoryId é mutuamente exclusivo com os outros campos de edição de canal: mandar os dois juntos é INVALID_INPUT. E dar cargo a alguém exige posto maior que o da pessoa — para um App, o do cargo mais alto que ele tiver.
| Rota | Corpo | Resposta |
|---|---|---|
POST /api/servidores/{servidor}/canais | name, type, revision; opcional voiceLimit. | 201 com channels e revision. |
PATCH /api/servidores/{servidor}/canais/{canal} | revision; e name / voiceLimit / ageRestricted ou categoryId. | channels e revision; com categoryId, também categories. |
DELETE /api/servidores/{servidor}/canais/{canal} | revision. | channels e revision. Arquiva: a linha fica no banco para a moderação, e some do produto. |
POST /api/servidores/{servidor}/cargos | name, permissions, revision; opcionais color e displaySeparately. | 201 com roles e revision. |
PATCH /api/servidores/{servidor}/cargos/{cargo} | revision; opcionais name, color, permissions, displaySeparately. | roles e revision. |
DELETE /api/servidores/{servidor}/cargos/{cargo} | revision. | roles e revision. |
Cargos de membros e de Apps
| Rota | Resposta |
|---|---|
PUT /api/servidores/{servidor}/membros/{conta}/cargos/{cargo} | roles e revision. |
DELETE /api/servidores/{servidor}/membros/{conta}/cargos/{cargo} | roles e revision. |
PUT /api/servidores/{servidor}/apps/{app}/cargos/{cargo} | apps e revision. Repare: apps, não roles. |
DELETE /api/servidores/{servidor}/apps/{app}/cargos/{cargo} | apps e revision. |
A revisão e o conflito
revision é controle de concorrência otimista. Você lê o retrato do servidor, mexe, e devolve a revisão que leu. Se outra pessoa mudou nesse meio-tempo:
import { TrivoError } from "@trivo/sdk";
try {
await app.rest.createChannel({ serverId, name: "geral", type: "text", revision });
} catch (erro) {
if (erro instanceof TrivoError && erro.is("STALE_REVISION")) {
// erro.currentRevision é a revisão de agora. QUEM decide reenviar é você:
// repetir cegamente sobrescreve a mudança de outra pessoa, que é
// exatamente o que a revisão existe para impedir.
}
}O SDK não decide por você aqui, de propósito: releia o retrato, decida, e só então reenvie.
O que esta credencial não abre
Algumas rotas existem e recusam Bearer com 401: conversa privada, moderação sobre pessoas, categorias, ordenação, subir anexo e a configuração do próprio App, entre outras. A lista inteira, com o motivo de cada corte, está em O que um App nunca alcança.