API de gerência
Tudo o que o painel faz, para o seu servidor. Use para moderar de dentro do painel administrativo do seu site, sincronizar o cartão dos usuários, liberar benefícios e acompanhar os números.
Acesso
Todo pedido leva as duas chaves do site:
X-Qorum-Key: pk_SUA_CHAVE_PUBLICA X-Qorum-Secret: sk_SUA_CHAVE_SECRETA
Essas chamadas não funcionam de um navegador e nunca devem ser feitas de um. Quem tem a chave secreta tem o poder do dono do site.
Chave secreta errada devolve 401 "Chave secreta inválida". Isso acontece logo depois de trocar as chaves no painel, até você atualizar o seu servidor.
// Node 18+: um ajudante para as chamadas abaixo
async function gerencia(metodo, caminho, corpo) {
const r = await fetch('https://useqorum.com/v1/site' + caminho, {
method: metodo,
headers: {
'X-Qorum-Key': process.env.QORUM_PUBLICA,
'X-Qorum-Secret': process.env.QORUM_SECRET,
...(corpo ? { 'Content-Type': 'application/json' } : {}),
},
body: corpo ? JSON.stringify(corpo) : undefined,
});
const dados = await r.json();
if (!r.ok) throw new Error(dados.error);
return dados;
}
Comentários
/v1/site/commentsTodos os comentários do site, do mais novo para o mais antigo, 25 por página (ou o que você pedir em limit). Os filtros se combinam.
| Parâmetro | |
|---|---|
status | approved (padrão), pending (fila), deleted ou all. |
q | Busca no texto. |
ref | Só de uma conversa. |
refs | De várias conversas, separadas por vírgula (até 500). Ex.: refs=post-1,post-2. |
refPrefix | De todas as conversas cujo ref começa assim. Ex.: refPrefix=produto: pega produto:12, produto:80... Dá para separar seções do site só pelo jeito de montar o ref. |
user | Só de uma pessoa (o id no seu site, ou q:N). |
page | Página. |
limit | Itens por página, de 1 a 100 (padrão 25). |
{ "comments": [ { "comment": { ...comentário... }, "status": "pending", "deleted": false, "openReports": 0,
"canRestore": false,
"thread": { "ref": "post-42", "title": "Meu post", "url": "https://..." } } ],
"total": 4, "page": 1, "totalPages": 1 }
canRestore é true nos apagados que ainda podem voltar. Os apagados junto com a conta da pessoa (veja Apagar os dados de alguém) ficam sem texto e não voltam.
/v1/site/comments/:id/approveAprova um comentário da fila. Na hora, ele aparece, conta no total, notifica quem foi respondido e dispara o webhook comment.created.
/v1/site/comments/:idApaga qualquer comentário. Para um comentário da fila, isso é recusar. Apagar um comentário principal leva junto as respostas dele.
/v1/site/comments/:id/restoreDesfaz o apagar. O comentário volta, e se for um principal, voltam com ele as respostas que saíram junto. Respostas que já tinham sido apagadas antes, uma por uma, continuam apagadas. Os contadores são refeitos e o seu servidor recebe o webhook comment.restored.
// resposta
{ "message": "Comentário restaurado", "restoredReplies": 2 }
- Para restaurar uma resposta cujo comentário principal também está apagado, restaure o principal primeiro (a API responde
409explicando). - Comentário apagado junto com a conta da pessoa não volta (
409).
/v1/site/comments/:id/historyAs versões anteriores de um comentário editado, da mais nova pra mais antiga: {"comment": {...}, "edits": [{"content", "html", "editorId", "createdAt"}]}. Edições feitas antes de 09/11/2026 não têm versão guardada.
/v1/site/comments/:id/pin e DELETEFixa por {"hours": N} (padrão 24, máximo 8760) e desafixa.
Conversas
/v1/site/threads?q=post&page=1As conversas do site, das mais movimentadas recentemente. q busca no ref e no título.
{ "threads": [ { "ref": "post-42", "title": "Meu post", "url": "https://...", "locked": false,
"comments": 37, "rootComments": 21, "updatedAt": "...", "createdAt": "..." } ],
"total": 12, "page": 1, "totalPages": 1 }
/v1/site/threads/lock{"ref": "post-42", "locked": true} tranca; false destranca.
Denúncias e banimentos
/v1/site/reports?status=pending&page=1status: pending (padrão), reviewed, dismissed ou all. Aceita os mesmos ref, refs, refPrefix e limit (1 a 100, padrão 20) da lista de comentários.
{ "reports": [ { "id": 5, "commentId": 81, "reason": "Spam ou propaganda", "status": "pending", "createdAt": "...",
"content": "texto denunciado", "commentDeleted": false, "threadRef": "post-42", "threadUrl": "https://...",
"author": { ...cartão... }, "reporter": { ...cartão... } } ],
"total": 1, "page": 1, "totalPages": 1 }
/v1/site/reports/:id{"status": "reviewed", "deleteComment": true} apaga o comentário; {"status": "dismissed"} ignora. As duas resolvem todas as denúncias abertas do mesmo comentário.
/v1/site/bans POST/v1/site/bans DELETE/v1/site/bans/:userId// banir por 7 dias
await gerencia('POST', '/bans', { userId: '123', reason: 'Spam repetido', hours: 168 });
// para sempre
await gerencia('POST', '/bans', { userId: '123', reason: '...', permanent: true });
Usuários
/v1/site/users/:userId/cardAtualiza na hora o nome e o cartão de alguém (trocou a foto, ganhou um selo). Sem isso, o cartão só muda no próximo login. Os comentários na tela podem levar até 30 segundos para mostrar o novo cartão.
await gerencia('PUT', '/users/123/card', {
name: 'Ana Lima',
card: { username: 'ana_lima', avatar: 'https://.../nova-foto.jpg', rank: { name: 'Assinante', nameColor: '#7c3aed' } }
});
/v1/site/users/cardsO mesmo, para muitas pessoas de uma vez: até 1000 por pedido. O corpo tem o mesmo formato da resposta da API de dados, então dá para reaproveitar o código. Use numa migração (para o nome e a foto de quem comentou no sistema antigo aparecerem desde a primeira visita) ou depois de uma mudança que atinge muita gente (um selo novo para todos os assinantes).
await gerencia('PUT', '/users/cards', {
users: {
'123': { name: 'Ana Lima', card: { username: 'ana_lima', avatar: 'https://.../ana.jpg' } },
'456': { name: 'Bruno', card: { username: 'bruno' } }
}
});
// resposta: { "saved": 2, "ignored": 0 } (ignored = id inválido)
/v1/site/users/:userId/entitlements GETLibera benefícios. Hoje existe fixar_comentario (a pessoa fixa o próprio comentário por 24 horas, gastando uma unidade). A quantidade é somada à que a pessoa já tem; -1 é ilimitado; hours define até quando vale.
// assinante ganhou 3 usos que valem por 30 dias
await gerencia('POST', '/users/123/entitlements', { item: 'fixar_comentario', quantity: 3, hours: 720 });
/v1/site/users/:userIdA pessoa apagou a conta no seu site: chame isto junto. O Qorum apaga, de uma vez:
- o texto e as imagens de todos os comentários dela (eles saem da tela, e o texto não fica guardado);
- as respostas que estavam embaixo dos comentários principais dela;
- as curtidas e "não curti" que ela deu, as denúncias que fez, as notificações dela e sobre ela;
- as denúncias abertas contra os comentários dela (saem da fila como revisadas);
- o cartão (nome e foto), os benefícios e o banimento.
Os contadores das conversas e dos comentários são refeitos. Não dá para desfazer.
await gerencia('DELETE', '/users/123');
// resposta: { "deletedComments": 14, "deletedReplies": 3, "removedVotes": 52 }
Notificações
Listar, contar e marcar como lidas as notificações de um usuário, e o feed para copiar tudo para o seu sistema: veja Notificações.
Números
/v1/site/stats{ "comments": 1284, "threads": 96, "last7Days": 311, "last30Days": 1102, "activeAuthors30Days": 240,
"pending": 4, "openReports": 1, "activeBans": 2, "webhookQueue": 0, "webhookFailed7Days": 0,
"updatedAt": "2026-11-09T14:00:00Z" }
Os totais de comentários ficam guardados por até 1 minuto (updatedAt diz quando foram contados); ?fresh=1 conta de novo. Pendentes, denúncias e avisos são sempre na hora.
/v1/site/usage?days=30Medidores por dia, de 1 a 365 dias. O dia de hoje inclui o que aconteceu até agora.
{ "days": [ { "day": "2026-11-08", "reads": 1840, "writes": 96, "comments": 41, "logins": 3,
"management": 12, "webhook_ok": 44, "webhook_err": 0, "denied": 2 } ],
"totals": { "reads": 1840, ... } }
| Medidor | Conta |
|---|---|
reads | Listas, contagens, configuração, comentário único, "eu" e notificações. |
writes | Comentar, responder, votar, editar, apagar, denunciar, fixar, enviar imagem e moderar pelo widget. |
comments | Comentários e respostas enviados, incluindo os que foram para a fila. |
logins | Entradas e cadastros com conta Qorum. |
management | Chamadas do painel e desta API. |
webhook_ok, webhook_err | Avisos entregues e tentativas que falharam. |
denied | Pedidos recusados: domínio fora da lista, token vencido, chave secreta errada. |
Configurações
/v1/site/settings PUTNo PUT, mande só o que quer mudar. Dentro de config, também só os campos que mudam.
await gerencia('PUT', '/settings', {
allowedOrigins: ['https://meusite.com', 'https://www.meusite.com'],
webhookUrl: 'https://meusite.com/qorum/webhook',
config: { template: 'cartoes', preModeracao: 'links', palavrasBloqueadas: ['cassino', 'aposta'] }
});
| Campo de config | Valores |
|---|---|
template | classico, cartoes, bolhas, compacto, minimalista |
tema | auto, claro, escuro |
cor1, cor2 | Cores no formato #rrggbb |
raio | Cantos, de 0 a 20 |
ordem | recent, best, hot, oldest, likes_desc |
mostrarNaoCurti | true ou false |
loginSite, loginQorum | Quais logins valem. Se os dois ficarem falsos, a conta Qorum é ligada. |
urlLogin | Para onde o botão Entrar leva quando só vale o login do seu site |
preModeracao | nao, links (link ou imagem), tudo |
permitirImagens | true ou false |
palavrasBloqueadas | Lista de textos |
reacoes, permitirVideos, permitirGifs | true ou false (padrão: true) |
apiBusca | Endereço da API de busca do seu site (link com #) |
Valores fora do permitido não dão erro: voltam para o padrão. O PUT devolve a configuração como ficou, então confira a resposta.
Chaves, webhook e diagnóstico
/v1/site/keys POST/v1/site/keys/rotateMostra e troca as chaves. A troca gera uma chave secreta e um segredo de webhook novos, e os antigos param de valer na hora.
/v1/site/webhook/testManda um evento ping agora mesmo. Devolve {"ok": true, "status": 200, "ms": 84}, ou o erro.
/v1/site/diagnostics/tokenExplica o que está errado num token de login: {"token": "eyJ..."}.
{ "ok": false,
"problems": [ "aud precisa ser a chave PÚBLICA deste site: pk_...", "Token vencido em 08/11/2026 21:00" ],
"claims": { "sub": "123", "aud": "pk_errada", ... } }
/v1/site/moderators POST DELETE/:accountIdModeradores do site. Para adicionar, {"username": "usuario_da_pessoa"} de uma conta Qorum que já existe.
Trazer comentários de outro sistema
Para migrar os comentários que o seu site já tem. Pode rodar quantas vezes quiser: cada comentário é identificado pelo source e pelo legacyId (o id dele no sistema antigo), e o que já entrou é pulado.
/v1/site/import{
"source": "comentarios_antigos", // nome do sistema de origem (até 40 caracteres)
"comments": [
{ "legacyId": 120, "ref": "post-42", "title": "Meu post", "url": "https://...",
"userId": "7", "content": "Primeiro!", "isEdited": false,
"createdAt": "2025-03-01T12:00:00Z", "updatedAt": "2025-03-01T12:00:00Z",
"likes": ["3", "9"], "dislikes": ["4"],
"reports": [ { "userId": "5", "reason": "Spam", "status": "pending", "createdAt": "..." } ] },
{ "legacyId": 121, "ref": "post-42", "parentLegacyId": 120, "replyToUserId": "7",
"userId": "3", "content": "Concordo", "createdAt": "2025-03-01T12:05:00Z" }
]
}
// resposta: { "imported": 2, "skipped": 0, "orphans": 0, "invalid": 0 }
- Até 1000 comentários por pedido. Mande os comentários principais antes das respostas: a resposta procura o pai pelo
parentLegacyId, no mesmo pedido ou em pedidos anteriores. - Resposta de resposta fica presa ao comentário principal, como no resto do Qorum, e o
@aponta para quem foi respondido. orphanssão respostas cujo pai não veio (por exemplo, foi apagado no sistema antigo): ficam de fora.- As datas originais são mantidas. Importar não gera notificação nem webhook.
- Os contadores (total da conversa, respostas, curtidas) são recalculados a cada pedido.
- Nome e foto de quem comentou: mande os cartões de todos os autores com
PUT /v1/site/users/cards(antes ou depois de importar). Sem isso, eles vêm da API de dados conforme as páginas são abertas, ou do próximo login de cada pessoa; até lá aparece "Usuário". - Banimentos ativos: envie com
POST /v1/site/bans.
Exemplo: fila de aprovação no seu admin
Duas rotas no seu servidor, usando o ajudante do começo da página. A tela do seu admin lista os pendentes e chama aprovar ou recusar:
app.get('/admin/comentarios/pendentes', soAdmin, async (req, res) => {
const { comments } = await gerencia('GET', '/comments?status=pending');
res.json(comments.map(c => ({
id: c.comment.id,
autor: c.comment.user.displayName || c.comment.user.username,
html: c.comment.html,
pagina: c.thread.url,
})));
});
app.post('/admin/comentarios/:id/:acao', soAdmin, async (req, res) => {
const id = Number(req.params.id);
if (req.params.acao === 'aprovar') await gerencia('POST', `/comments/${id}/approve`);
else await gerencia('DELETE', `/comments/${id}`);
res.sendStatus(204);
});