04API v1
Base: https://castelobruxorp.cntoficial.com.br/disc/v1 · JSON · chave por projeto
1. Autenticação e escopos
Toda chamada leva a chave do projeto no header x-api-key. Cada projeto (jogo, site, staff) tem sua chave e só usa as rotas dos seus escopos. Chaves são pedidas à staff do Castelo Bruxo RP e nunca vão para o navegador, commit ou log.
| Escopo | Libera |
|---|---|
mensagens | POST /mensagens, POST /avisos, PATCH /avisos |
dm | POST /dm |
membros.ler | GET /membros/:discordId |
membros.editar | PATCH /membros/:discordId (apelido) |
casas | PUT /membros/:discordId/casa |
cargos | PUT e DELETE /membros/:discordId/cargos/:cargo |
wl.ler | GET /wl, GET /wl/:discordId |
wl.criar | POST /wl |
wl.avaliar | aprovar, reprovar e revogar WL |
login | POST /login/url, POST /login/trocar |
organizacao | GET /organizacao, POST /organizacao/cargos |
Hoje: jogo = mensagens, membros.ler, casas, wl.ler · site = login, wl.criar, wl.ler, membros.ler · staff = tudo.
Sucesso: { "ok": true, ... }. Erro: { "ok": false, "error": "<mensagem>" }. Limite de 120 chamadas por minuto por chave.
| HTTP | Quando |
|---|---|
| 400 | campo inválido |
| 401 | chave ausente ou errada |
| 403 | chave sem o escopo; bot sem permissão no Discord (cargo do bot abaixo do cargo que ele mexe) |
| 404 | usuário fora do servidor, pedido, canal ou mensagem inexistente |
| 409 | WL duplicada ou já avaliada; cargo configurado que não existe |
| 422 | usuário não aceita DM do bot |
| 429 | limite por minuto |
| 503 | bot conectando ou login com Discord sem configurar |
Toda ação que muda algo fica registrada (com o projeto) e aparece no log de auditoria do Discord. Mande "staff": "Nome" no corpo para registrar quem fez, por exemplo o admin do jogo.
2. Mensagens e avisos
canal aceita o ID, um apelido configurado (ex.: avisos) ou o nome do canal.
POST /v1/mensagens
{ "canal": "avisos", "texto": "Servidor reiniciando em 5 minutos.", "cargos": ["<id do cargo>"], "allowEveryone": false }
Texto até 2000 caracteres. cargos (até 10) marca e notifica só esses cargos. Resposta: { "ok": true, "messageId": "..." }
POST /v1/avisos
{ "canal": "avisos", "texto": "Quadribol hoje às 20h!", "imagens": ["<base64>"], "cargos": [], "allowEveryone": false }
Até 4 imagens (PNG, JPEG, GIF, WEBP; 8 MB no total).
PATCH /v1/avisos
{ "canal": "avisos", "messageId": "...", "texto": "Quadribol remarcado para 21h." }
Edita só o texto de mensagem do próprio bot.
POST /v1/dm
{ "discordId": "...", "texto": "Sua carta chegou no correio coruja." }
3. Membros, casas e cargos
GET /v1/membros/:discordId
{ "ok": true, "membro": {
"discordId": "...", "usuario": "harry", "nome": "Harry", "apelido": null, "avatar": "https://...",
"noServidor": true, "entrouEm": "2026-11-02T12:00:00.000Z",
"cargos": [{ "id": "...", "nome": "Grifinória" }],
"casa": "gryffindor", "wl": true, "staff": false } }
Fora do servidor: { "discordId": "...", "noServidor": false }.
PATCH /v1/membros/:discordId
{ "apelido": "Harry Potter" }
Até 32 caracteres; null tira o apelido.
PUT /v1/membros/:discordId/casa
{ "casa": "gryffindor" }
Aceita as chaves do jogo (gryffindor, hufflepuff, ravenclaw, slytherin, as mesmas do hmp-houses) e os nomes em português (Grifinória, Lufa-Lufa, Corvinal, Sonserina). Tira as outras casas. { "casa": null } tira todas.
PUT · DELETE /v1/membros/:discordId/cargos/:cargo
Dá ou tira um cargo pelo apelido da lista de cargos gerenciáveis (ex.: monitor). Cargo fora da lista: 400; cargo de staff: 403. A API nunca mexe em cargo que não foi liberado.
4. Whitelist
Fluxo: o site manda o pedido → o Monstro publica no canal da staff com os botões Aprovar e Reprovar → a staff avalia pelo botão (ou pela API) → aprovado ganha o cargo de WL, recebe DM e é anunciado. Reprovado recebe DM com o motivo e pode pedir de novo. Só a staff usa os botões.
POST /v1/wl
{ "discordId": "...", "respostas": { "Nome do personagem": "Harry Potter", "História": "..." } }
De 1 a 30 respostas, até 1000 caracteres cada. O usuário precisa estar no servidor. 409 se já tem WL ou pedido pendente. Resposta: 201 { "ok": true, "pedido": { "id": 1, "status": "pendente", ... } }
GET /v1/wl/:discordId
{ "ok": true, "wl": { "discordId": "...", "noServidor": true, "aprovado": false, "pedido": { "id": 1, "status": "pendente", ... } } }
aprovado vem do cargo no Discord: é o que o servidor do jogo confere na entrada.
GET /v1/wl?status=pendente&pagina=1&porPagina=25
Lista os pedidos (pendente, aprovado, reprovado, revogado).
POST /v1/wl/pedidos/:id/aprovar · /v1/wl/pedidos/:id/reprovar
{ "motivo": "opcional, até 500", "staff": "Nome de quem avaliou" }
POST /v1/wl/:discordId/revogar
Tira o cargo de WL; o último pedido aprovado vira revogado.
5. Login com Discord (site)
O site faz o OAuth2 e o Monstro troca o code (só ele tem o Client Secret).
POST /v1/login/urlcom{ "redirectUri": "https://.../callback", "state": "<aleatório do site>" }→{ "url": "https://discord.com/oauth2/authorize?..." }- O site manda o usuário para essa URL. O Discord volta em
redirectUri?code=...&state=.... - O site confere o
statee chamaPOST /v1/login/trocarcom{ "code": "...", "redirectUri": "https://.../callback" }→{ "ok": true, "perfil": { ... } }, o mesmo perfil deGET /membros, com casa, wl e staff. - O site cria a própria sessão com o
discordId.
O redirectUri precisa estar liberado pela staff (no Monstro e no Developer Portal).
6. Organização do servidor
GET /v1/organizacao
Servidor, cargos configurados (existe ou não, com ID) e canais resolvidos.
POST /v1/organizacao/cargos
Cria os cargos de WL e das casas que ainda não existem, com as cores das casas. Não mexe nos que já existem. Depois, o cargo do Monstro precisa ficar acima deles em Configurações → Cargos.
7. Exemplo (Node)
const MONSTRO = 'https://castelobruxorp.cntoficial.com.br/disc/v1';
async function monstro(caminho, { method = 'GET', body } = {}) {
const res = await fetch(MONSTRO + caminho, {
method,
headers: { 'content-type': 'application/json', 'x-api-key': process.env.MONSTRO_API_KEY },
body: body && JSON.stringify(body),
signal: AbortSignal.timeout(10000),
});
const json = await res.json();
if (!json.ok) throw Object.assign(new Error(json.error), { status: res.status });
return json;
}
const { wl } = await monstro(`/wl/${discordId}`);
if (!wl.aprovado) recusarEntrada('Você ainda não tem whitelist no Castelo Bruxo RP.');
await monstro(`/membros/${discordId}/casa`, { method: 'PUT', body: { casa: 'gryffindor' } });
Falha do Discord nunca deve desfazer nada no jogo nem no site: registre e siga. Envio de mensagem não é idempotente: depois de timeout, confira o canal antes de reenviar.