Pular para o conteúdo

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

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", ... } ]
}
CampoO que é
contentO texto como a pessoa escreveu.
htmlO 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.
imagesImagens anexadas (só aparece quando tem). thumb é a miniatura.
userIdO id da pessoa no seu site, ou q:N para conta Qorum. Ids só com números chegam como número.
userO cartão que o seu site mandou no login, sempre com id e username.
hasLiked, hasDislikedSe quem está lendo curtiu. Sem login, sempre false.
replyToUserId, replyToUsernameEm respostas: quem foi respondido.
pinnedUntilSó quando está fixado: até quando.
statusSó aparece como "pending", logo depois de comentar, quando o comentário foi para a fila.
repliesSó nos comentários principais: até 50 respostas, da mais antiga para a mais nova.

Ler

GET/v1/config

Aparê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 }
GET/v1/comments?ref=post-42

Os comentários de uma conversa, página por página. Conversa que ainda não tem comentários devolve lista vazia, sem erro.

ParâmetroPadrão
refObrigatório. A conversa.
page1Página.
limit20Comentários principais por página, até 50.
sorto do painelrecent, 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.

GET/v1/comments/:id

Um 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", ... } }
GET/v1/comments/:id/replies?offset=50&limit=50

Mais respostas de um comentário principal, além das 50 que vêm na lista. limit vai até 100.

{ "replies": [ ... ], "total": 73, "offset": 50 }
GET/v1/counts?refs=post-1,post-2,post-3

Quantos 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

GET/v1/me

Dados 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
GET/v1/notifications

As 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 }
POST/v1/notifications/read

Marca como lidas. Corpo {"ids": [9, 10]}, ou vazio para marcar todas.

Escrever

Todas precisam de login.

POST/v1/comments

Comenta ou responde. A primeira vez que alguém comenta num ref, a conversa é criada.

Campo
refObrigatório. A conversa.
contentDe 3 a 2000 caracteres. Pode ficar vazio se tiver imagem.
parentIdPara responder: o id do comentário respondido. Resposta de resposta fica presa ao comentário principal.
replyToUserIdQuem foi respondido. Padrão: o autor do comentário em parentId.
imagesAté 4 endereços devolvidos por POST /v1/images para esta mesma pessoa.
title, urlNome 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.

POST/v1/images

Envia 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.

PUT/v1/comments/:id

Edita o próprio comentário: {"content": "..."}. Devolve {"id", "content", "html", "isEdited": true}.

DELETE/v1/comments/:id

Apaga o próprio comentário (moderadores apagam qualquer um). Apagar um principal apaga as respostas.

POST/v1/comments/:id/like e /dislike

Curtir 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"
POST/v1/comments/:id/report

Denuncia: {"reason": "Spam ou propaganda"}. Não dá para denunciar o próprio (400) nem denunciar duas vezes (409).

POST/v1/comments/:id/pin e DELETE

Fixa 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/followSeguir {"ref"} e deixar de seguir ?ref=. Até 2000 conversas por pessoa.
GET POST/v1/blocksQuem a pessoa bloqueou / bloquear {"userId"} (até 500). A lista de comentários já vem sem os bloqueados.
DELETE/v1/blocks/:userIdDesbloquear.
GET/v1/comments/pulse?ref=&after=IDQuantos 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=anCompletar @: até 8 pessoas do site. Login.
GET/v1/links/search?q=soloCompletar #: as páginas do seu site (pela API de busca). Login.
GET/v1/gifs?q=gato&page=1Busca 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/historyModerador: 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.

POST/v1/auth/register
POST/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/reportsDenúncias (?status=pending, reviewed, dismissed ou all).
PUT/v1/mod/reports/:id{"status": "reviewed" ou "dismissed", "deleteComment": true}
GET/v1/mod/bansQuem está banido agora.
POST/v1/mod/bans{"userId", "reason", "hours"} ou "permanent": true.
DELETE/v1/mod/bans/:userIdDesbane.
PUT/v1/mod/threads/lock{"ref", "locked": true}
POST/v1/mod/comments/:id/approveAprova um comentário da fila.

Testar a conexão

GET/v1/ping?key=pk_...&origin=https://meusite.com

Diz 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.