Quorum

MCP serverDev tools

Quorum is an integration that gives your AI market evidence where every claim comes with a receipt. Once added, your AI can trace a claim back to the real stored record behind it and fetch that record to see exactly what it says.

Use Quorum in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Quorum and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the research product tool from Quorum

Details

Available today. Use it from your connected AI after setup.

After adding quorum, ask your AI to pull the evidence behind a market claim and fetch the stored record it points to.

What your AI can do with it

  • Retrieve market evidence for a claim
  • Resolve any claim to its stored record
  • Fetch the full record behind a claim
  • Verify a claim by reading the record that supports it

Install Quorum

The server’s own address, for the clients that take one directly. Or connect Ahel once and every client you use reads it from one address, with the account kept on Ahel rather than in each client’s config.

  • Claude Code

    claude mcp add --transport http --scope user quorum 'https://quorum-api-j15n.onrender.com/mcp'

    Run it once in your project, then open /mcp to approve any sign-in the server asks for.

  • Claude Desktop

    https://quorum-api-j15n.onrender.com/mcp

    Add a custom connector in Settings, paste this address, and approve the sign-in.

  • Cursor

    cursor://anysphere.cursor-deeplink/mcp/install?name=quorum&config=eyJ1cmwiOiJodHRwczovL3F1b3J1bS1hcGktajE1bi5vbnJlbmRlci5jb20vbWNwIn0=

    Open the link and Cursor adds the server at that address.

  • ChatGPT

    https://quorum-api-j15n.onrender.com/mcp

    In Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.

  • Codex

    codex mcp add quorum --url 'https://quorum-api-j15n.onrender.com/mcp'

    Run it once, then sign in with codex mcp login quorum if the server asks for an account.

From the project's README

As published by godzilla-lab/quorum-api in README.md.

Market evidence with receipts.

Give it a subject, get back what a market actually says. Every claim carries receipt ids, and every receipt id resolves to a real stored record you can fetch back. Fabricated citations are structurally impossible, and a test proves it rather than a README asserting it.

[!TIP] The interesting part is not that it finds evidence. It is that you can check it. Every id in every answer resolves through GET /v1/evidence/{id}, and POST /v1/verify will re-resolve a set of claims against the corpus, ours or anybody else's. An id that does not resolve is a claim that was never real.

{
  "finding": "Sizing runs small, and buyers only discover it after delivery",
  "corroboration": { "records": 31, "channels": 5 },
  "receipts": ["rc_8f2a1", "rc_44b0e", "rc_91c37"],
  "confidence": "finding"
}

GET /v1/evidence/rc_8f2a1 returns the actual comment, its score, where it was posted, when, and its permalink. A customer of your customer can click a claim and read the human who said it.

The hosted API lives at https://quorum-api-j15n.onrender.com, on a free tier instance that sleeps when idle, so the first request after a quiet spell waits a few seconds for it to wake.

The hosted instance is keyed while the API is early. The API root says how to request a key, and always says which mode the instance you are talking to is in. The health endpoint needs no key, so you can check it is alive right now:

curl https://quorum-api-j15n.onrender.com/v1/healthz

Self hosting supports open mode or per key auth, quotas, tenancy and webhook secrets, all self served from GET /v1/usage.

[!NOTE] On Windows, the curl bundled with Git Bash uses the Schannel TLS stack, which refuses any HTTPS connection when it cannot reach the certificate revocation servers (CRYPT_E_REVOCATION_OFFLINE, common behind corporate proxies and VPNs). That is the network, not this API. Add --ssl-revoke-best-effort to the curl command, or call from WSL or PowerShell instead.

Contents

  • What it does
  • Use cases
  • Status
  • Requirements
  • Quick start
  • Usage
    • CLI
    • HTTP API
    • JavaScript SDK
    • Python SDK
    • MCP server
    • Webhooks
    • Running the API yourself
    • Postgres
  • What the API does
  • Why this is different
  • Documentation
  • Contributing
  • License
  • Acknowledgements

What it does

Voice of customer from public archives, competitor ads ranked by how long they have actually been running, and a video versus static verdict computed as arithmetic rather than opinion. It keeps everything it reads, so the second question about a market is nearly free and the archive can be asked about the past.

Key capabilities

  • Receipts, not links. A stable id into a retained corpus, which resolves identically forever, including after the source deletes the original.
  • Corroboration as arithmetic. A claim needs at least three independent records before it prints as a finding, and the count travels with it.
  • Ten sources behind one interface, from a volunteer Reddit archive and GitHub issue search to four government safety regulators, each degrading rather than failing when unconfigured.
  • A corpus that compounds. Cold retrieval measured 596 seconds and about 500 throttled requests. The same category answers in half a second once warm.
  • Offline mode that touches no network and costs nothing.
  • Fifteen HTTP operations, a typed SDK, an MCP server and a CLI, all the same pipeline.

What it looks like

Real output, excerpted from a cold npx quorum "espresso machine" run on 2026-08-24: 401 records in 227 seconds for $0.00.

EVIDENCE  a claim needs 3 independent receipts to be stated as a finding
          the percentage is share of all 401 records held for this category

  quality     88 receipts /  64 channels  21.9%  A0 B3 C85 D0   [finding]
      "I often hear the priority list being 1. Quality of beans 2. Quality
       of the grinder 3. Quality of the espresso machine..."
         hackernews Why a spritz of water before grinding coffee yields
         better results   rc_4d6d444821b0044f

ATTESTED  7 records from 6 named parties   [finding]
  A named party stated this to a regulator, on the record, with consequences
  for lying. Two of these are a finding on their own.

      "CPSC And Krups Announce Action On Espresso Makers..."
         cpsc Krups   rc_a4883bfb1b04d1c1

RECEIPTS  195 cited, 195 resolved back to real records

COST      $0.0000 in 227.3s

Every id in that output is fetchable from the corpus that run wrote. If an id does not resolve, the run exits non zero and says which one.

Use cases

  • Product and brand research. What buyers actually complain about, with the comment behind every claim, rather than a summary you have to trust.
  • Competitive teardown. One full retrieval per rival, so shares are compared instead of counts, and co-occurrence is never mistaken for a signal.
  • Ad creative decisions. Video versus static judged by how long real campaigns ran, from dated observations rather than from a platform's own label.
  • Agent tooling that can be audited. An MCP server whose every claim the calling model can independently resolve, which is the one thing a research tool cannot fake.
  • Diligence and monitoring. Attested records a named party filed with a regulator, and a diff against the last report for the same subject.

Status

EngineWorking. 1,330 tests, offline and keyless
CLIWorking, every flag
MCP serverWorking, five tools over stdio, four of them also remote at /mcp
JavaScript SDKWorking, 11 methods
Python SDKPublished: pip install quorum-api. 14 methods, standard library only
WebhooksWorking. Signed to Standard Webhooks, durable, retried for about 75 hours
Hosted APIDeployed and live at https://quorum-api-j15n.onrender.com, on PostgreSQL, verified against the running instance
npmPublished: npx quorum-api "running shoes". Five packages, zero external dependencies
CorpusYoung. 22 consumer and developer categories warmed on the hosted instance as of 2026-08-25; everything else is a cold run away

[!NOTE] Do not depend on the API shape yet. It is stable enough to build against and not yet frozen.

Named rather than left as a surprise: request quotas are held in memory, so the per minute lookup counter resets on restart and none of the counters would hold across two instances. The hourly report counter is the exception: it is re-seeded from persisted reports at boot, so a restart is not a refill. And evals/ holds only its first layer, a labelled relevance set scored in CI; the paid, scored report evals described in the project docs do not exist yet.

Requirements

Node 22.18 or newer. The engine has one runtime dependency, pg, and only in the hosted server: no framework, no ORM, no HTTP client, no test runner, and nothing at all in the CLI or the corpus. That is a deliberate constraint rather than a boast, because every dependency in a tool that fetches untrusted text from the public internet is another thing that can reach the network on your behalf. pg earned its place by being a connection pool that survives a database restart, which is not a thing worth hand writing.

No key is required for anything. Reddit through a public archive, Hacker News, the App Store, the CFPB consumer complaint database and four government safety archives are all free and keyless. Keys only ever ADD sources (YouTube comments with a free API key, Amazon reviews and the Meta ad library through Apify), and a missing one degrades a run rather than failing it.

QUORUM_YOUTUBE_API_KEYA free YouTube Data API v3 key. Comments under review videos join the voice tier; absent, the source reports itself unconfigured and the run proceeds without it. Costs nothing: the free daily quota covers dozens of runs.
QUORUM_CONTACT_EMAILNot a key. The SEC requires a User-Agent naming who is calling and returns 403 without one, so sec-edgar reports itself unconfigured until this is set. A role address outlives whoever set it up.
OPENROUTER_API_KEYSubject expansion, --synthesise and --read-images. All three are off by default, and the counts never come from a model, so the deterministic report is identical without it.
APIFY_TOKENThe two metered legs: the Meta ad library and Amazon reviews, both through the same Apify account. Every call charges the cost meter and lands on the report's bill. Absent, both legs are skipped, exactly as --no-ads does for ads.
QUORUM_CORPUSSQLite corpus path, read by the server and the MCP server. Default ./quorum.db. The CLI does not read it: pass --corpus instead.
QUORUM_PG_URLPostgres, for the hosted corpus. Paste the provider uri whole: it is parsed rather than split, so sslmode is honoured and a password containing @ survives.
QUORUM_PG_CAPath to the provider CA. Worth setting, because sslmode=require means encrypt and not verify.
QUORUM_PG_CA_PEMThe same CA as an inline PEM, for hosts that supply secrets as values rather than files.
QUORUM_API_KEYSComma separated bearer keys for the server. Absent means the instance is open, which the server says out loud on boot.
QUORUM_WEBHOOK_SECRETSigning secret for webhook delivery, 24 characters or more. Absent means webhooks are accepted and never delivered, which the server also says on boot.
QUORUM_CONCURRENCYConcurrent report runs, default 2. Not a throughput dial: every concurrent run is concurrent pressure on the same volunteer archives.
PORTDefault 8787. A host normally sets this.
QUORUM_REPORTS_PER_MINUTEReports one key may start per minute. Default 20.
QUORUM_LOOKUPS_PER_MINUTEEverything else, per key per minute. Default 600, which is 10 a second.
QUORUM_MAX_CAP_USDCeiling on any one report. A caller's own capUsd can only lower it. Default 0.
QUORUM_SPEND_PER_KEY_USDWhat one key may spend on metered sources per day. Default 0.
QUORUM_SPEND_TOTAL_USDWhat every key together may spend per day. Default 0.

Quick start

Requires Node 22.18 or newer, and nothing else.

npx quorum-api "running shoes"

That is the whole install. Five packages come down from npm, about 240 KB with zero external dependencies, and the first report starts.

Working on Quorum itself is the clone path:

git clone https://github.com/Godzilla-lab/Quorum-API && cd Quorum-API
npm install
npm run build
npm test          # 1,330 tests, offline, no keys

Then research something. The input is a subject, not a URL. Plain text works, a product URL works, and a product URL the store refuses still works, which matters because four of four real store pages blocked a server side fetch when this was measured.

npx quorum "running shoes" --offline    # corpus only, no network, no cost
npx quorum "running shoes" --offline --corpus ./mine.db   # somewhere else

[!NOTE] Nothing above needs a key, and --offline never touches the network at all. Start with none of them and add one when a report tells you what it could not reach.

Usage

CLI

The whole engine on your machine, against a corpus you own.

# A subject in plain words
npx quorum "running shoes" --communities running,runningshoegeeks

# A product url, including one a store refuses to serve to a server
npx quorum "https://allbirds.com/products/mens-wool-runners"

# Corpus only. No network, no cost, and it says so
npx quorum "running shoes" --offline

# Machine readable. Progress goes to stderr, so this pipes cleanly
npx quorum "running shoes" --json | jq '.findings[].term'

# Versus what: one full retrieval per rival
npx quorum "wool runner" --compare "brooks ghost"

# Skip the one metered source explicitly
npx quorum "running shoes" --no-ads

--compare is one full retrieval per rival, and it has to be. Counting records in one corpus that happen to mention a rival measures co-occurrence: a comment saying "these run smaller than my Brooks" names a rival and a complaint and attributes the complaint to neither. So every side is a corpus of its own, shares are compared rather than counts, and a gap inside the sampling noise for those corpus sizes is reported as no difference rather than as a result.

Every claim it prints is either a finding, meaning at least three independent receipts stand behind it, or a weak signal, which is shown so it can be chased and is never stated as a market pattern. Before anything is printed, every cited receipt is fetched back out of the corpus, and the run exits non zero if one of them does not resolve.

quorum --help lists the flags. Every command above works through npx quorum-api with nothing cloned, or through the quorum bin inside a checkout; they are the same binary.

HTTP API

Start a report, poll it, then resolve the ids it cites.

# Start one. Returns 202 immediately with an id
curl -sX POST http://localhost:8787/v1/reports \
  -H "authorization: Bearer $KEY" \
  -H 'content-type: application/json' \
  -d '{"subject":"wool runner","terms":["sizing","durability"]}'
{ "id": "rep_3568995af829290a", "status": "running", "coalesced": false,
  "category": "wool runner", "queuePosition": 1, "estimatedSeconds": 60 }
# Poll it. Honours Retry-After while running, and ETags so polling costs nothing
curl -s http://localhost:8787/v1/reports/rep_3568995af829290a \
  -H "authorization: Bearer $KEY" -H 'if-none-match: "rep_3568995af829290a-7"'

# Watch it instead of polling
curl -N http://localhost:8787/v1/reports/rep_3568995af829290a/stream \
  -H "authorization: Bearer $KEY"

# Resolve one receipt. This is the endpoint everything else rests on
curl -s http://localhost:8787/v1/evidence/rc_a8697befab91e873 \
  -H "authorization: Bearer $KEY"

# Resolve up to 200 at once, because a report cites more than one
curl -sX POST http://localhost:8787/v1/evidence/batch \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"receiptIds":["rc_8f2a1","rc_44b0e","rc_91c37"]}'

# Is this category warm? Free, and worth asking before paying for a cold report
curl -s http://localhost:8787/v1/categories/running%20shoes \
  -H "authorization: Bearer $KEY"

# Check somebody's claims, ours or a competitor's
curl -sX POST http://localhost:8787/v1/verify \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"claims":[{"text":"sizing runs small","receipts":["rc_8f2a1"]}]}'

JavaScript SDK

Typed, zero dependency, written against spec/openapi.yaml. Every method is one operationId from that file.

import { createClient } from '@quorum/sdk-js';

const quorum = createClient({ baseUrl: 'https://quorum-api-j15n.onrender.com', apiKey: process.env.QUORUM_KEY });

const found = await quorum.searchEvidence({ query: 'sizing', category: 'running shoes' });
if (!found.ok) throw new Error(found.error.message);

for (const record of found.data.records) console.log(record.receiptId, record.text);

Errors are values, never thrown, the same rule the engine follows anywhere a vendor can be down. A caller gets { ok: false, error } carrying the server's type, its requestId and any retryAfterSeconds, because a 429, a 503 and a report that is simply not finished yet are all normal and none of them is exceptional.

It honours the server's pacing. waitForReport polls to completion using Retry-After when the server sends one, and treats a 503 as the load shedder rather than a failure. A client that gives up on the first refusal reports a busy service as a broken one, which is exactly what the shedder exists to prevent.

const started = await quorum.createReport({ subject: 'wool runner', offline: true });
if (started.ok) {
  const report = await quorum.waitForReport(started.data.id, {
    onPoll: (r) => console.log(r.status),
  });
}

streamReport returns the same run as an async iterable of server sent events, parsed by hand because there is no EventSource in Node that accepts an Authorization header.

Python SDK

The same client in Python, in packages/sdk-py, written against the same spec with the same two decisions: errors are values, never raised, and wait_for_report honours Retry-After so a busy service is not reported as a broken one. Standard library only, no dependencies, and the transport is injectable so its tests run with no network.

pip install quorum-api
from quorum_api import QuorumClient

quorum = QuorumClient("https://quorum-api-j15n.onrender.com", api_key="qk_...")
started = quorum.create_report("wool runner", offline=True)
if started.ok:
    report = quorum.wait_for_report(started.data["id"])

MCP server

Five tools spoken as JSON-RPC with no dependency, over two transports.

Remote, for connector forms that want a URL. The hosted instance answers Streamable HTTP at:

https://quorum-api-j15n.onrender.com/mcp

Open without a key: the read tools spend nothing and serve public data, and anonymous callers share one rate allowance so they cannot crowd out keyed customers. On a subject nothing is held for, research_product starts a harvest and returns at once; records are searchable as they land, so search_evidence and category_warmth answer while the run continues and say so. The public door runs one harvest at a time and at most three an hour; a key gets the ordinary report allowance. Ads and synthesis never run from it.

Local, over stdio, for clients that launch a command:

QUORUM_CORPUS=./quorum.db node packages/mcp/src/bin.ts
{
  "mcpServers": {
    "quorum": {
      "command": "node",
      "args": ["packages/mcp/src/bin.ts"],
      "env": { "QUORUM_CORPUS": "./quorum.db" }
    }
  }
}
toolanswers
search_evidenceHow many independent records exist, across how many channels, whether that clears the threshold, and the loudest few quotes
get_receiptResolves ids to the real records. This is how an agent checks us
category_warmthWhether asking is instant and free, or minutes and expensive
compare_formatsVideo versus static, from how long real ads ran
research_productRemote: starts a harvest and returns at once. Local: a full report, off unless QUORUM_MCP_RESEARCH=1

Three decisions worth knowing, because the tool schema is the expensive part to change later:

Five tools, not one per endpoint. A tool definition costs 100 to 500 tokens on every turn, so a server mirroring ten routes spends the context window before the model has done anything.

Aggregated, never a row dump. search_evidence returns counts and a handful of quotes. A research tool that streams a corpus into a context window has spent the budget it was meant to save.

Markdown, not JSON, at roughly 60% of the tokens for the same content.

research_product is off by default because a report is minutes of throttled retrieval against volunteer archives, and an agent should not be able to start one by accident. The other four answer from what is already held and touch no network.

The payoff is a thing no other research server can offer: the calling agent can independently verify every claim. search_evidence cites ids and get_receipt resolves them, so an id that does not resolve is a claim that was never real, and it is the loudest line in the response when it happens.

Webhooks

A report is minutes long, so POST /v1/reports can call you back instead of being polled. Set a signing secret and delivery turns on:

QUORUM_WEBHOOK_SECRET=$(openssl rand -base64 32) npm start
curl -sX POST http://localhost:8787/v1/reports \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"subject":"wool runner","webhookUrl":"https://your-app.example/hooks/quorum"}'

When the report reaches a terminal state you receive a POST whose body is byte identical to GET /v1/reports/{id}, plus three headers following the Standard Webhooks specification, so you can verify with an off the shelf library rather than with code we invented:

headervalue
webhook-idThe report id. Stable across retries, so deduplicate on it
webhook-timestampUnix seconds at the moment of this attempt
webhook-signaturev1,{base64 hmac-sha256}, a space delimited list

The signed content is {webhook-id}.{webhook-timestamp}.{body}, and the key is the base64 body of your secret, not the whsec_ label. Your secret is the webhookSecret field on GET /v1/usage: scoped to your key, stable, and derived rather than stored, so it rotates exactly when the operator rotates the instance secret and never behind your back.

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, headers, body, toleranceSeconds = 300) {
  const id = headers['webhook-id'];
  const timestamp = Number(headers['webhook-timestamp']);
  /* A signature alone is replayable forever, so bound the age first. */
  if (!Number.isFinite(timestamp)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest();

  /* The header is a list so a secret can be rotated. Any one match is enough. */
  return String(headers['webhook-signature']).split(' ').some((part) => {
    const [version, value] = part.split(',');
    if (version !== 'v1') return false;
    const offered = Buffer.from(value ?? '', 'base64');
    return offered.length === expected.length && timingSafeEqual(offered, expected);
  });
}

Delivery is at least once, and retried for about 75 hours. A 2xx is success. A 5xx, a 429, a timeout or a network failure is retried on the Standard Webhooks schedule (immediately, 5s, 5m, 30m, 2h, 5h, 10h, 14h, 20h, 24h, with jitter). Any other 4xx is not retried, because a receiver rejecting the body will reject it again. Deliveries are held in the database, so a restart does not lose them.

Shortened here. Read the whole README on GitHub.

Tools it offers (5)

What this server listed when Ahel dialed its public endpoint in Sep 2026, with no key and no account of yours. The names are the server’s own.

  • research_product
  • search_evidence
  • get_receipt
  • category_warmth
  • compare_formats

Signals

Last commit
Sep 2026
Advanced
Delivery
quorum MCP server → your Ahel connector (mcp.ahel.ai) → your AI.
Item type
mcp-server
Key
io-github-godzilla-lab-quorum
Source
github.com/godzilla-lab/quorum-api
Hosted endpoint
https://quorum-api-j15n.onrender.com/mcp