polygraph.so

HTTP API

Three public endpoints behind the polygraphso CLI and the polygraph MCP tools. Same data, same shape — pick whichever you prefer to integrate against.

01

Overview

The API exposes three endpoints: a lookup for a single server, a full list of everything graded, and a request queue for servers we haven’t graded yet. All are anonymous and serve the same data the CLI displays; the first two are read-only.

  • Base URL · https://polygraph.so
  • Auth · none on these endpoints; subscriber endpoints land later under a separate path.
  • Rate limit · per-IP: check 60/min, grade-request 20/min. Over-budget calls get a 429 with Retry-After.
  • Content type · application/json in and out.

Prefer the CLI for ergonomics: npx polygraphso check <ref>, npx polygraphso list, and npx polygraphso request <ref>. The CLI hits these same routes. So does the hosted MCP endpoint just below, the fastest path in for an agent (no install step), and the polygraph MCP tools shipped with @polygraphso/litmus for local use (check_server / list_servers / request_grade).

02

MCP endpoint (hosted)

The fastest way in for an agent: the same three lookups, served as a hosted MCP server over Streamable HTTP. Any MCP-capable client, an agent, an IDE, a connector directory, reads polygraph grades without installing anything.

POST/api/mcp

Point an MCP client at https://polygraph.so/api/mcp. It serves exactly three tools, check_server, list_servers, and request_grade: the read/queue surface, and nothing that runs a server’s code. Grading is deliberately not offered here: it executes the target, which has no place on a hosted, anonymous endpoint. To grade a server yourself, run the open harness (npx @polygraphso/litmus) locally.

Add it to a client

{
  "mcpServers": {
    "polygraph": { "url": "https://polygraph.so/api/mcp" }
  }
}

Note · the tools return the same grades as the HTTP endpoints below; a not_available result means unevaluated (neither safe nor unsafe), not a failing grade. For local, one-command grading and the CLI, install the polygraph plugin.

Listing without the whole corpus

list_serversdefaults to the first 25 rows (capped at 100 per call), so a single call doesn’t spend an agent’s whole context budget. Pass grade to restrict to one letter, and limit / offset to page further. summary (a total plus a count per grade) always covers the full graded corpus, regardless of filtering or paging. See the full list_servers reference below for the shared response shape.

Paying for a grade request from an agent

request_grade returns a payment object with two ways to pay: payUrl, a human/browser checkout paid in $POLYGRAPH, and x402Url, the agent rail. POST the same request body to x402Url with an x402-capable client; a bare POST returns a 402 with the exact payment requirements, and a retry with an X-PAYMENT header pays the one-time $1 fee in USDC on Base mainnet (eip155:8453). Paying starts the 48h grading clock. Settlement itself is deferred: the fee is taken only once a grade lands, and a run the harness cannot complete voids the authorization, so nothing is charged. After paying, poll check_server with the same server_reffor the published result, or poll the response’s statusUrl. See the full request_grade reference below for the settlement mechanics in detail.

03

Server-ref format

Every server we track is addressed by a registry-prefixed reference. Three variants, each matching its registry's native namespace:

npm/<package>@<version>           name (unscoped) or @scope/name (scoped)
pypi/<name>@<version>             flat — no owner segment
github/<owner>/<repo>@<version>   owner required

Real examples:

npm/@modelcontextprotocol/server-filesystem
npm/@notionhq/notion-mcp-server
pypi/mcp-server-git
pypi/mcp-server-fetch

The version segment is optional and version-aware. A pinned @versionreturns the grade for that exact version. A bare ref resolves the version in play — the installed version (CLI) or the registry’s current latest — and returns its grade; if that version isn’t graded yet, the most recent graded version is returned and flagged (version_match: false).

04

Check a server

POST/api/cli/check

Looks up one server’s published polygraph grade, or a notify URL if it hasn’t been graded yet.

Request

{
  "server_ref": "npm/@modelcontextprotocol/server-filesystem",
  "source": "mcp",               // optional: "mcp" or "cli"
  "agent_id": "claude-code/2.1", // optional: calling client, name/version
  "agent_meta": {                // optional: what the client declared
    "title": "Claude Code",
    "capabilities": ["sampling", "roots"]
  }
}

The identity fields are optional and aggregate-only: they feed per-agent usage counters (software metadata — never IPs, never request logs). Callers that send nothing are counted by a normalized User-Agent.

Response · graded

{
  "status": "graded",
  "polygraph": "A",
  "polygraph_detail": {
    "grade": "A",
    "c01": "pass",
    "c02": "pass",
    "c03": "pass",
    "tool_defs_fingerprint": "0x256a…66db6",
    "methodology_version": "litmus-v17",
    "resolved_version": "1.4.0",
    "rationale": "All three categories passed.",
    "computed_at": "2026-06-11T14:14:04Z"
  },
  "current_version": "1.4.0",
  "version_match": true,
  "notify_url": "https://www.polygraph.so/notify?for=npm/@modelcontextprotocol/server-filesystem"
}

Note · polygraph is one of "A", "B", "C", "D", "F" (see the rubric). polygraph_detail.resolved_version is the version that was graded; current_version is the version in play, and version_match is falsewhen an older graded version is returned because the current one isn’t graded yet.

Response · not graded

{
  "status": "not_available",
  "notify_url": "https://polygraph.so/notify?for=npm/some-owner/some-package",
  "message": "No published polygraph for npm/some-owner/some-package yet — treat it as unevaluated (neither safe nor unsafe). To get it graded, call request_grade ($1 one-time fee; graded within 48h of payment), or grade it yourself now with the self_grade command.",
  "self_grade": "npx -y -p @polygraphso/litmus polygraphso-litmus litmus npm/some-owner/some-package"
}

message spells out the next steps for an agent; self_grade is a one-shot command to run the open litmus yourself; to queue it instead, see request a grade. A miss bumps an anonymous demand counter so we can see which ungraded servers are most in demand. No request body is logged beyond the ref itself.

curl

curl -X POST https://polygraph.so/api/cli/check \
  -H 'content-type: application/json' \
  -d '{"server_ref":"npm/@modelcontextprotocol/server-filesystem"}'

Errors

  • 400 — missing, malformed, or too-long server_ref. The body includes a short error string.
  • 429 — rate limited (per-IP). Back off and retry after the Retry-After interval.
  • 500 — lookup failed server-side. Safe to retry.
05

List graded servers

GET/api/cli/list

Returns every server with a published polygraph grade, sorted by grade (A first), then alphabetically. With no query params this is the full set, unpaged, matching the original v0 shape. Three optional query params narrow and page it:

  • grade · one of A, B, C, D, F; restricts the listing to that letter.
  • limit · a positive integer. Unset returns every matching row.
  • offset · a non-negative integer, rows to skip before taking limit.

summary is always computed over the full graded corpus, total plus a count per grade, regardless of grade, limit, or offset, so a filtered or paged caller can still see the shape of the whole set.

Response shape

{
  "servers": [
    {
      "server_ref": "npm/@modelcontextprotocol/server-filesystem",
      "polygraph": "A"
    },
    {
      "server_ref": "npm/@upstash/context7-mcp",
      "polygraph": "D"
    }
  ],
  "total": 6,
  "summary": {
    "total": 111,
    "byGrade": { "A": 88, "B": 12, "C": 3, "D": 6, "F": 2 }
  }
}

polygraph is one of "A", "B", "C", "D", "F" (see the rubric). total counts rows matching grade before paging (it equals summary.total when grade is omitted). Only graded servers appear; check a specific ungraded server with /api/cli/check.

curl

curl https://polygraph.so/api/cli/list

curl "https://polygraph.so/api/cli/list?grade=A&limit=5"
06

Request a grade

POST/api/cli/grade-request

Records a grade request: the write counterpart to check. Recording is free. Paying the request’s one-time $1 fee, via the payment link in the response, starts the 48h grading clock. Two rails: the web checkout settles up front in $POLYGRAPH, while the x402 endpoint (for agents holding USDC on Base) takes an authorization that is charged only once a grade lands; a run the harness cannot complete voids it, so that rail never charges for an incomplete run. The fee buys the run, never the grade. Read the result with /api/cli/check; nothing is returned synchronously.

Request

{
  "server_ref": "npm/some-owner/some-package",
  "source": "cli",               // optional: "cli" (default) or "mcp"
  "agent_id": "claude-code/2.1", // optional: calling client, name/version
  "agent_meta": { "capabilities": ["sampling"] } // optional, like /check
  // "email": "you@example.com"  // optional: get notified when the grade lands
}

No contact details required — agents have no inbox, so the queue records who asked (agent_id) instead. Requesting the same server twice is a no-op, not a duplicate.

Response

{
  "status": "queued",
  "created": true,   // false when the target was already recorded
  "demand": 3,       // requests standing behind this target
  "requestId": "3f2a1b4c-5d6e-4f70-8a9b-0c1d2e3f4a5b",
  "payment": {
    "required": true,          // false once the fee is paid
    "usdPrice": 1,
    "payUrl": "https://www.polygraph.so/request/priority/3f2a1b4c-…",
    "x402Url": "https://www.polygraph.so/api/x402/grade-request"
  }
}

payUrl is the web checkout (paid in $POLYGRAPH). x402-capable clients can instead POST the same body to x402Url: a bare request gets a 402 whose payment-required header carries the requirements ($1 USDC on Base), and a retry with an X-PAYMENT header records the request, authorizes the fee, and starts the 48h clock. Settlement is deferred to grade delivery, so the authorization is only charged once a grade lands.

curl

curl -X POST https://polygraph.so/api/cli/grade-request \
  -H 'content-type: application/json' \
  -d '{"server_ref":"npm/some-owner/some-package"}'

Errors

  • 400 — missing or malformed server_ref (or an invalid email).
  • 429 — rate limited (per-IP). Back off and retry after the Retry-After interval.
  • 500 — the write failed server-side. Safe to retry (idempotent per target).
07

Versioning

v0 changes are additive. We add fields; we don't remove or rename them. A breaking change — a removed field, a renamed key, a changed shape — would ship under a new path (/api/v1/...), and the v0 path would keep its existing behavior for a deprecation window we'll announce here.

The harness version that produced a given polygraph will be exposed on the grade payload once behavioral grading lands, so grades stay comparable within a harness version.

08

Stability

These endpoints are the contract the CLI is built against, so the URLs and field names are stable. The notify URL pattern (polygraph.so/notify?for=<ref>) is stable too; it's the same place the website's "tell me when this is graded" funnel writes to.

What isn't stable yet: the cadence of behavioral grade publication. The methodology and rubric are documented at /methodology.

09

Embeddable badge

A live grade badge any server can embed — in a README, on npm, or on a docs site. Three artifacts, all keyed by the same server ref, all served from the canonical grade so they update themselves when a grade changes.

GET/api/badge?server=<ref>

A small inline SVG pill (image/svg+xml) — polygraph · A, colored by grade. An ungraded server renders a muted unratedpill rather than an error, so it’s safe to embed before a grade exists.

GET/api/badge/card?server=<ref>

A larger card image (image/png) with the grade, the three category slots, and the methodology version.

GET/mcp/<ref>

The human-readable grade report the badge and card link to — category breakdown, fingerprint, and the command to reproduce the grade. It also hosts ready-to-copy embed snippets.

Markdown

[![polygraph](https://polygraph.so/api/badge?server=npm/@modelcontextprotocol/server-filesystem)](https://polygraph.so/mcp/npm/@modelcontextprotocol/server-filesystem)

Note · the ref is passed unencoded in the query string (/ and @ are legal there). Images are cached at the CDN; a regrade propagates within the hour.

10

See also

  • /llms.txt · short, machine-readable summary for agents.
  • Landing · positioning, principles, who we are.