API pública
A mesma API que o widget usa. Com ela você monta os comentários no seu próprio design, num aplicativo ou em qualquer lugar que faça pedidos HTTP.
O básico
- Endereço base:
https://useqorum.com - Formato: JSON na ida e na volta. Datas em ISO 8601, no fuso UTC.
- Chave: todo pedido leva a chave pública no cabeçalho
X-Qorum-Key(ou?key=na URL). - Login: ações de quem está logado levam
Authorization: Bearer TOKEN, com o token do login do seu site ou da conta Qorum. Sem ele, só dá para ler. - Origem: pedidos feitos de um navegador precisam vir de um domínio liberado no site. Pedidos do seu servidor (sem cabeçalho
Origin) passam direto. - Erros: sempre
{"error": "mensagem"}, com uma mensagem que pode ser mostrada para a pessoa. Veja os códigos em Solução de problemas.
const QORUM = 'https://useqorum.com';
const CHAVE = 'pk_SUA_CHAVE_PUBLICA';
async function qorum(metodo, caminho, corpo, token) {
const r = await fetch(QORUM + caminho, {
method: metodo,
headers: {
'X-Qorum-Key': CHAVE,
...(token ? { Authorization: 'Bearer ' + token } : {}),
...(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;
}
O comentário
Todas as rotas que devolvem comentários usam este formato:
{
"id": 81,
"content": "Ótimo **texto**!",
"html": "Ótimo <strong>texto</strong>!",
"images": [{ "url": "https://i.ibb.co/...", "thumb": "https://i.ibb.co/...", "width": 1200, "height": 800 }],
"userId": 123,
"user": { "id": 123, "username": "ana_lima", "displayName": "Ana Lima", "avatar": "https://..." },
"likes": 12, "dislikes": 0, "hasLiked": true, "hasDisliked": false,
"replyCount": 2, "isEdited": false,
"createdAt": "2026-11-08T21:00:00Z",
"replies": [ { "id": 82, "parentId": 81, "replyToUserId": 123, "replyToUsername": "Ana Lima", ... } ]
}
| Campo | O que é |
|---|---|
content | O texto como a pessoa escreveu. |
html | O texto pronto para pôr na página: tudo escapado, com negrito, itálico, riscado, links e <span class="spoiler"> para o texto oculto. Pode usar direto com innerHTML. |
images | Imagens anexadas (só aparece quando tem). thumb é a miniatura. |
userId | O id da pessoa no seu site, ou q:N para conta Qorum. Ids só com números chegam como número. |
user | O cartão que o seu site mandou no login, sempre com id e username. |
hasLiked, hasDisliked | Se quem está lendo curtiu. Sem login, sempre false. |
replyToUserId, replyToUsername | Em respostas: quem foi respondido. |
pinnedUntil | Só quando está fixado: até quando. |
status | Só aparece como "pending", logo depois de comentar, quando o comentário foi para a fila. |
replies | Só nos comentários principais: até 50 respostas, da mais antiga para a mais nova. |
Ler
/v1/configAparência e regras do site, para você seguir na sua interface.
{ "site": "Meu Blog", "template": "classico", "tema": "auto", "cor1": "#6b2fe0", "cor2": "#2a8ad8", "raio": 8,
"ordem": "recent", "mostrarNaoCurti": true, "loginSite": true, "loginQorum": true, "urlLogin": "",
"preModeracao": false, "imagens": true, "imagemMaxMB": 32 }
/v1/comments?ref=post-42Os comentários de uma conversa, página por página. Conversa que ainda não tem comentários devolve lista vazia, sem erro.
| Parâmetro | Padrão | |
|---|---|---|
ref | Obrigatório. A conversa. | |
page | 1 | Página. |
limit | 20 | Comentários principais por página, até 50. |
sort | o do painel | recent, oldest, likes_desc, likes_asc, dislikes_desc, dislikes_asc. |
{
"comments": [ ... ],
"pinned": [ ... ],
"pagination": { "page": 1, "limit": 20, "total": 37, "totalRootComments": 21, "totalPages": 2 },
"thread": { "ref": "post-42", "title": "Meu post", "url": "https://...", "locked": false }
}
pinned só vem na página 1 e só quando há comentário fixado. Os fixados também aparecem em comments, na posição normal: esconda os repetidos se for mostrar os dois. total conta as respostas; totalPages conta só os principais.
/v1/comments/:idUm comentário, para link direto. Devolve sempre o comentário principal com as respostas, e em focusId o que foi pedido (que pode ser uma das respostas).
{ "comment": { "id": 81, ..., "replies": [ ... ] }, "focusId": 82, "thread": { "ref": "post-42", ... } }
/v1/comments/:id/replies?offset=50&limit=50Mais respostas de um comentário principal, além das 50 que vêm na lista. limit vai até 100.
{ "replies": [ ... ], "total": 73, "offset": 50 }
/v1/counts?refs=post-1,post-2,post-3Quantos comentários tem cada conversa, para mostrar "12 comentários" numa lista de posts. De 1 a 100 refs por pedido.
{ "counts": { "post-1": 12, "post-2": 0, "post-3": 5 } }
Quem está logado
/v1/meDados de quem está logado neste site.
{ "userId": 123, "user": { ... }, "isMod": false, "qorumAccount": false,
"ban": null, // ou { "reason", "isPermanent", "expiresAt" }
"entitlements": [ { "item": "fixar_comentario", "quantity": 2, "expiresAt": null } ],
"unread": 3 } // notificações não lidas
/v1/notificationsAs respostas que quem está logado recebeu (padrão 30, até 100, com ?unread=1 e paginação). Tudo sobre notificações, inclusive como levar para o seu site, em Notificações.
{ "notifications": [ { "id": 9, "commentId": 82, "read": false, "createdAt": "...",
"preview": "Concordo, e...", "actor": { "username": "bia" },
"thread": { "ref": "post-42", "title": "Meu post", "url": "https://..." } } ],
"unread": 1 }
/v1/notifications/readMarca como lidas. Corpo {"ids": [9, 10]}, ou vazio para marcar todas.
Escrever
Todas precisam de login.
/v1/commentsComenta ou responde. A primeira vez que alguém comenta num ref, a conversa é criada.
| Campo | |
|---|---|
ref | Obrigatório. A conversa. |
content | De 3 a 2000 caracteres. Pode ficar vazio se tiver imagem. |
parentId | Para responder: o id do comentário respondido. Resposta de resposta fica presa ao comentário principal. |
replyToUserId | Quem foi respondido. Padrão: o autor do comentário em parentId. |
images | Até 4 endereços devolvidos por POST /v1/images para esta mesma pessoa. |
title, url | Nome e endereço da conversa. Guardados no primeiro comentário. |
await qorum('POST', '/v1/comments', { ref: 'post-42', content: 'Que bom!', parentId: 81 }, token);
// 201: o comentário criado. Com "status": "pending", ele foi para a fila de aprovação.
Erros comuns: 400 texto curto ou longo; 403 conversa trancada ou pessoa banida (com isBanned, reason, isPermanent, expiresAt); 404 comentário respondido não existe; 429 comentou há menos de 5 segundos.
/v1/imagesEnvia uma imagem para anexar num comentário. Formulário multipart/form-data com o arquivo no campo image: JPG, PNG, GIF ou WebP, até 32 MB. Depois, mande o url devolvido em images ao comentar. A imagem é convertida para WebP (GIF animado continua animado) e reduzida para no máximo 2560 pixels no lado maior; width e height já vêm com o tamanho final.
const fd = new FormData();
fd.append('image', arquivo); // de um <input type="file">
const r = await fetch('https://useqorum.com/v1/images', {
method: 'POST',
headers: { 'X-Qorum-Key': CHAVE, Authorization: 'Bearer ' + token }, // sem Content-Type
body: fd,
});
const img = await r.json(); // { "url", "thumb", "medium", "width", "height" }
Erros: 400 formato não aceito ou resolução acima de 40 megapixels; 403 imagens desligadas no site; 413 maior que 32 MB; 429 mais de 20 imagens em 10 minutos.
/v1/comments/:idEdita o próprio comentário: {"content": "..."}. Devolve {"id", "content", "html", "isEdited": true}.
/v1/comments/:idApaga o próprio comentário (moderadores apagam qualquer um). Apagar um principal apaga as respostas.
/v1/comments/:id/like e /dislikeCurtir e "não curti". Mandar de novo desfaz. Curtir tira o "não curti" e vice-versa.
{ "liked": true, "likes": 13, "dislikes": 0 } // em /dislike vem "disliked"
/v1/comments/:id/reportDenuncia: {"reason": "Spam ou propaganda"}. Não dá para denunciar o próprio (400) nem denunciar duas vezes (409).
/v1/comments/:id/pin e DELETEFixa e desafixa. Moderadores mandam {"hours": 48} (padrão 24). O autor fixa o próprio por 24 horas se tiver o benefício fixar_comentario. Só comentários principais.
Reações, seguir, bloquear e companhia
| GET | /v1/reactions?ref= | {"kinds": [...], "counts": {"amei": 12, ...}, "total": 55, "mine": "haha"} |
| POST | /v1/reactions | {"ref", "kind"}. Mesma reação de novo (ou kind vazio) tira. Login. |
| GET | /v1/follow?ref= | {"following": true, "followers": 8} |
| POST DELETE | /v1/follow | Seguir {"ref"} e deixar de seguir ?ref=. Até 2000 conversas por pessoa. |
| GET POST | /v1/blocks | Quem a pessoa bloqueou / bloquear {"userId"} (até 500). A lista de comentários já vem sem os bloqueados. |
| DELETE | /v1/blocks/:userId | Desbloquear. |
| GET | /v1/comments/pulse?ref=&after=ID | Quantos comentários entraram depois do maior id que você tem: {"new": 3, "last": 912}. Leve: pode chamar a cada 20 s. |
| GET | /v1/users/search?q=an | Completar @: até 8 pessoas do site. Login. |
| GET | /v1/links/search?q=solo | Completar #: as páginas do seu site (pela API de busca). Login. |
| GET | /v1/gifs?q=gato&page=1 | Busca de GIFs (sem q = em alta). Login. |
| POST | /v1/gifs | {"url"} de um GIF da busca: devolve a imagem pra mandar em images ao comentar. |
| GET | /v1/mod/comments/:id/history | Moderador: versões anteriores de um comentário editado. |
Nos comentários: sort=best e sort=hot na lista; o campo embeds traz os vídeos do YouTube citados ({"type": "youtube", "id", "thumb"}); no html, [texto](url) vira link e @usuario vira <span class="mencao">.
Conta Qorum
Para fazer a sua própria tela de entrar. Só funciona se a conta Qorum estiver ligada no site.
/v1/auth/register/v1/auth/login// criar conta: { "username": "ana_lima", "password": "uma senha boa", "name": "Ana Lima" } (name opcional)
// entrar: { "username": "ana_lima", "password": "uma senha boa" }
// resposta: { "token": "eyJ...", "userId": "q:12", "user": { "username": "ana_lima", "displayName": "Ana Lima" } }
O token dura 30 dias e vai em Authorization: Bearer. Erros: 400 usuário em uso ou fora das regras; 401 usuário ou senha errados; 403 conta Qorum desligada no site; 429 mais de 10 tentativas por minuto.
Moderar pelo widget
Com login de moderador (mod: true no token do seu site, ou conta Qorum que é moderadora do site):
| GET | /v1/mod/reports | Denúncias (?status=pending, reviewed, dismissed ou all). |
| PUT | /v1/mod/reports/:id | {"status": "reviewed" ou "dismissed", "deleteComment": true} |
| GET | /v1/mod/bans | Quem está banido agora. |
| POST | /v1/mod/bans | {"userId", "reason", "hours"} ou "permanent": true. |
| DELETE | /v1/mod/bans/:userId | Desbane. |
| PUT | /v1/mod/threads/lock | {"ref", "locked": true} |
| POST | /v1/mod/comments/:id/approve | Aprova um comentário da fila. |
Testar a conexão
/v1/ping?key=pk_...&origin=https://meusite.comDiz se a chave existe, se a origem está liberada e quais logins estão ligados. Não é bloqueado pela origem, justamente para diagnosticar. Veja o teste pronto em Solução de problemas.