Polygraph

Tools

Polygraph, where you work.

In your browser, in your AI assistant, or in your own code. One account, one set of credits: reading is free, and only a check spends.

API

Build on the same checks.

A JSON API over HTTPS at https://polygraphs.ai/api/v1. Quote a post, check it, read the result, re-verify it later and share it — everything the site shows about a check is in the response.

Quickstart

Get a read + check key (below) and export it as POLYGRAPH_KEY. This asks the price, runs the check, then waits for the result. Pick a language once and every sample on this page follows.

Quickstart
# Needs jq. 1. Ask the price. Nothing is charged.
QUOTE=$(curl -s -X POST https://polygraphs.ai/api/v1/quote \
  -H "Authorization: Bearer $POLYGRAPH_KEY" \
  -H "content-type: application/json" \
  -d '{"kind":"check","input":"https://x.com/janedoe/status/1843123456789012345"}')
CREDITS=$(echo "$QUOTE" | jq -e .credits) || { echo "$QUOTE" | jq '{code, error}'; exit 1; }
echo "$QUOTE" | jq '{credits, balance}'

# 2. Check it at the quoted price: maxCredits refuses (409 price-changed, nothing drawn) if it has risen.
CHECK=$(curl -s -X POST https://polygraphs.ai/api/v1/check \
  -H "Authorization: Bearer $POLYGRAPH_KEY" \
  -H "content-type: application/json" \
  -d "{\"input\":\"https://x.com/janedoe/status/1843123456789012345\",\"maxCredits\":$CREDITS}")
CONTENT_ID=$(echo "$CHECK" | jq -r '.contentId // empty')
[ -n "$CONTENT_ID" ] || { echo "$CHECK" | jq '{code, error}'; exit 1; }  # e.g. price-changed: quote again

# 3. Poll until the post leaves "checking".
while :; do
  sleep 5
  POST=$(curl -s https://polygraphs.ai/api/v1/posts/$CONTENT_ID -H "Authorization: Bearer $POLYGRAPH_KEY")
  [ "$(echo "$POST" | jq -r .state)" != "checking" ] && break
done
echo "$POST" | jq .claims

Keys

Name each key for where it lives — “my script”, “Claude (MCP)” — and give it only the scope it needs. Each shows when it was last used and what it read, checked and spent today and this month. Revoke one and it stops at once; the others keep working.

Auth
A key (pg_…) as x-api-key or Authorization: Bearer. Keep it server-side.
Scopes
Read gets posts, claims and changes. Read + check can also quote, check, re-verify and share — and only it can spend.
Pricing
Reads are free today. Coming: each key keeps 1,000 free reads a day, and a read past that costs 1 credit. A check costs 25 credits for a text or image post or an article or pasted text up to 20,000 characters, 10 credits more per started 10,000 characters beyond that, 5 credits per started minute of video, with a minimum of 25 credits — the same as on the site, from the same wallet. A key can carry a monthly credit cap. Pricing

Authentication, scopes and limits

Send a key (pg_…) as x-api-key: pg_… or Authorization: Bearer pg_…. A key acts for your account: what it can read is what you can read, and what it checks is charged to your wallet. A read key can get posts, claims and changes; a read + check key can also quote, check, re-verify and share. A key may carry a monthly credit cap; a charge that would pass it is refused with 402 key-cap-reached.

Per key: reads 60 a minute and 5,000 a UTC day; checks and re-verifies 10 a minute and 200 a day. Quote and share count as reads. Past a limit you get 429 rate-limited with a Retry-After header in seconds.

Connecting an AI assistant (OAuth) makes an ordinary pg_ key, named for the assistant: it is listed on your keys page, where you can cap or revoke it, and it works on this REST API like any other key.

Reads are free today. Coming: each key keeps 1,000 free reads a day, and a read past that costs 1 credit.

Cross-origin browser calls are allowed only from approved origins; call the API from your server and keep the key there.

Quote a check

POST/api/v1/quote

The credit price of a check or a re-verify (a post id) before anything runs. A check's `input` is a link to any web page or social post, a bare X post id, or pasted text to check. Nothing is charged. Quoting a page or post we don't have yet fetches it, so the check that follows doesn't fetch it again. A web page or pasted text is priced by length: the post price up to 20,000 characters, then 10 credits per started 10,000 more (`basis.chars` is the length counted).

Scope read + check · counts as a read · errors unauthorized, rate-limited, bad-input, text-not-supported, fetch-failed, not-found, forbidden

Request
curl -s -X POST https://polygraphs.ai/api/v1/quote \
  -H "Authorization: Bearer $POLYGRAPH_KEY" \
  -H "content-type: application/json" \
  -d '{"kind":"check","input":"https://x.com/janedoe/status/1843123456789012345"}'

200 The price.

Response
{
  "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
  "slug": null,
  "kind": "check",
  "credits": 25,
  "free": null,
  "basis": {
    "shape": "text",
    "durationSec": null,
    "chars": null
  },
  "addOnPossible": false,
  "reenrich": null,
  "balance": 200,
  "introEndsOn": "2027-01-01"
}

Check a page, post or text

POST/api/v1/check

Check every claim in one web page, social post or piece of pasted text (`input`). A new check draws its price from your credits first (refunded if Polygraph decides it makes no checkable claim). Send the quote's credits as maxCredits and the check won't draw more up front: a higher price refuses with 409 price-changed. A video quoted with addOnPossible can still add its extra minutes once the run learns its length. 202 while it runs; 200 when everything was already on file. Poll the post for the result.

Scope read + check · counts as a check · errors unauthorized, rate-limited, bad-input, text-not-supported, fetch-failed, insufficient-credits, key-cap-reached, price-changed

Request
curl -s -X POST https://polygraphs.ai/api/v1/check \
  -H "Authorization: Bearer $POLYGRAPH_KEY" \
  -H "content-type: application/json" \
  -d '{"input":"https://x.com/janedoe/status/1843123456789012345","maxCredits":25}'

202 A run started or is in flight.

Response
{
  "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
  "slug": "jane-doe-the-bill-cuts-3f1c2a",
  "status": "pending",
  "started": [
    "claim-analysis",
    "framing"
  ],
  "runId": "run_cmg1x2y3z",
  "credits": 25
}

200 Everything was already on file; nothing ran or was charged.

Response
{
  "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
  "slug": "jane-doe-the-bill-cuts-3f1c2a",
  "status": "complete",
  "started": [],
  "runId": null,
  "credits": 0
}

Get a post

GET/api/v1/posts/{contentId}

One post's results: per-stage status, the post, its claims with verdicts and sources, scores, the Polygraph score, the framing and rhetoric reads, related posts and reply drafts. Send the last ETag as If-None-Match and an unchanged post answers 304.

Scope read · counts as a read · errors unauthorized, rate-limited, bad-input, not-found, forbidden

ParameterInDescription
contentId · requiredpathThe post's id (a UUID, from a check or a quote).
If-None-MatchheaderThe ETag of your last read.
Request
curl -s https://polygraphs.ai/api/v1/posts/3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d \
  -H "Authorization: Bearer $POLYGRAPH_KEY"

200 The post (abridged here).

Response
{
  "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
  "state": "done",
  "stages": {
    "route": {
      "state": "done",
      "send": true
    },
    "claims": {
      "state": "done",
      "count": 3
    },
    "framing": {
      "state": "done"
    },
    "scores": {
      "state": "done"
    },
    "replies": {
      "state": "done"
    }
  },
  "updatedAt": "2026-10-06T14:02:11.000Z",
  "etag": "\"p-9c1e0a7f\""
}

304 Unchanged since the ETag you sent.

Get a claim

GET/api/v1/claims/{claimId}

One claim with its latest verdict, confidence, summary, sources and verdict history, and the posts you can read that make it.

Scope read · counts as a read · errors unauthorized, rate-limited, bad-input, not-found, forbidden

ParameterInDescription
claimId · requiredpathThe claim's numeric id (from a post's claims).
Request
curl -s https://polygraphs.ai/api/v1/claims/48213 \
  -H "Authorization: Bearer $POLYGRAPH_KEY"

200 The claim (abridged here).

Response
{
  "claimId": 48213,
  "text": "The bill cuts school funding by 40 percent.",
  "claimType": "statistic",
  "temporalType": "current_state",
  "verdict": "false",
  "confidence": 0.91,
  "summary": "The bill trims one grant program by 4 percent; total school funding rises. The 40 percent figure does not appear in the bill or its budget score.",
  "verifiedAt": "2026-10-06T13:58:40.000Z",
  "verifications": 1,
  "appearancesTotal": 2
}

Re-verify a post

POST/api/v1/posts/{contentId}/reverify

Check a checked post's claims again, against today's sources. Quote it first (kind reverify): it is charged like a check. 409 when nobody has checked the post yet.

Scope read + check · counts as a check · errors unauthorized, rate-limited, bad-input, not-found, forbidden, not-checked, insufficient-credits, key-cap-reached

ParameterInDescription
contentId · requiredpathThe post's id (a UUID, from a check or a quote).
Request
curl -s -X POST https://polygraphs.ai/api/v1/posts/3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d/reverify \
  -H "Authorization: Bearer $POLYGRAPH_KEY"

202 The re-verify started (or was already running).

Response
{
  "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
  "slug": "jane-doe-the-bill-cuts-3f1c2a",
  "status": "pending",
  "started": true,
  "runId": "run_cmg4a5b6c",
  "credits": 25
}

Share a check

POST/api/v1/posts/{contentId}/share

Make a check you ran public on the Polygraph site, so a link to it resolves for anyone. Idempotent: 201 the first time, 200 after.

Scope read + check · counts as a read · errors unauthorized, rate-limited, bad-input, not-found, forbidden, not-ready, withdrawn

ParameterInDescription
contentId · requiredpathThe post's id (a UUID, from a check or a quote).
Request
curl -s -X POST https://polygraphs.ai/api/v1/posts/3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d/share \
  -H "Authorization: Bearer $POLYGRAPH_KEY"

201 Now public.

Response
{
  "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
  "path": "/checks/jane-doe-the-bill-cuts-3f1c2a",
  "slug": "jane-doe-the-bill-cuts-3f1c2a",
  "origin": "shared",
  "created": true,
  "url": "https://polygraphs.ai/checks/jane-doe-the-bill-cuts-3f1c2a"
}

200 Already public.

Response
{
  "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
  "path": "/checks/jane-doe-the-bill-cuts-3f1c2a",
  "slug": "jane-doe-the-bill-cuts-3f1c2a",
  "origin": "shared",
  "created": false,
  "url": "https://polygraphs.ai/checks/jane-doe-the-bill-cuts-3f1c2a"
}

List changes

GET/api/v1/changes

What changed in your checks (or one collection you can read), at least once, oldest first — a feed to poll instead of polling every post. Pass back `cursor` as `since`; `more: true` means poll again now.

Scope read · counts as a read · errors unauthorized, rate-limited, bad-input, not-found, forbidden

ParameterInDescription
sincequeryThe cursor from your last call. Without it the feed starts seven days back.
collectionIdqueryA collection you own or lease. Default: your checks.
limitquery1–500, default 100.
Request
curl -s https://polygraphs.ai/api/v1/changes \
  -H "Authorization: Bearer $POLYGRAPH_KEY"

200 A page of changes.

Response
{
  "changes": [
    {
      "contentId": "3f1c2a9e-7b4d-4e21-9c55-0d8e6a1b2c3d",
      "state": "done",
      "stages": {
        "route": {
          "state": "done",
          "send": true
        },
        "claims": {
          "state": "done",
          "count": 3
        },
        "framing": {
          "state": "done"
        },
        "scores": {
          "state": "done"
        },
        "replies": {
          "state": "done"
        }
      },
      "updatedAt": "2026-10-06T14:02:11.000Z",
      "etag": "\"p-9c1e0a7f\""
    }
  ],
  "cursor": "eyJ0IjoiMjAyNi0xMC0wNlQxNDowMjoxMS4wMDAifQ",
  "more": false
}

Errors

StatusCodeWhen
400bad-inputThe request is malformed — a missing field, a bad id, a bad cursor — or the input can't be checked: pasted text shorter than 40 or longer than 120,000 characters, a link carrying a password or access token, or a link to an image file.
401unauthorizedNo key, or the key is unknown, revoked or expired.
402insufficient-creditsYour balance can't cover the price. The body carries `credits` and `balance`.
402key-cap-reachedThe charge would pass this key's monthly credit cap. The body carries `cap`, `spent`, `credits`.
403forbiddenThe key is not a Polygraph key, or the post or claim is not one you can read.
403key-scope-requiredA read key on quote, check, re-verify or share. The body carries `scope` and `required`.
404not-foundNo such post, claim or collection.
409price-changedThe price rose above the `maxCredits` you sent (your quote) before the check ran. Nothing was charged; quote again. The body carries `credits` and `maxCredits`.
409not-checkedRe-verify of a post nobody has checked yet. Check it instead.
409not-readyShare of a check that hasn't finished.
409withdrawnShare of a check Polygraph withdrew from the public site.
422unsupported-linkNo longer returned: any web page can be checked. Listed for clients written against an earlier version.
422text-not-supportedPasted text, while checking pasted text is switched off. Send a link to the page or post the words come from.
429rate-limitedPast the key's per-minute or per-day limit. `Retry-After` says when to try again; the body carries `window`, `limit`, `callClass`.
502fetch-failedThe platform did not return the post, or the web page could not be read (a login or paywall, too little text). Nothing was stored or charged.