Polygraph

API reference

The Polygraph API.

JSON over HTTPS at https://polygraphs.xyz/api/v1. Every response that fails is { error, code } with the status below.

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 (a post link or X post id) or a re-verify (a post id) before anything runs. Nothing is charged. Quoting a post we don't have yet fetches it, so the check that follows doesn't fetch it again.

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

Request
curl -s -X POST https://polygraphs.xyz/api/v1/quote \
  -H "Authorization: Bearer $POLYGRAPH_KEY" \
  -H "content-type: application/json" \
  -d '{"kind":"check","input":"https://x.com/janedoe/status/1843123456789012345"}'
Body
{
  "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
  },
  "addOnPossible": false,
  "reenrich": null,
  "balance": 200,
  "introEndsOn": "2027-01-01"
}

Check a post

POST/api/v1/check

Check every claim in one post. A new check draws its price from your credits first (refunded if Polygraph decides the post makes no checkable claim). 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, unsupported-link, text-not-supported, fetch-failed, insufficient-credits, key-cap-reached, price-changed

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

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.xyz/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.xyz/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.xyz/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.xyz/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.xyz/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.xyz/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.xyz/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.
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 quote you agreed to (`maxCredits`) before the check ran. Nothing was charged; quote again.
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-linkA link, but not to a post on a platform Polygraph checks.
422text-not-supportedPasted words instead of a post link.
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. Nothing was stored or charged.