# AttestPage > Signed evidence about web pages for AI agents, paid per call with x402. Check whether a quote appears on a page or check all the citations in an answer at once, fetch a page with a verdict on what came back, check links, check that package names exist on npm, PyPI or crates.io, or timestamp a hash. Every answer carries an Ed25519-signed receipt you can check offline. A receipt records what our fetcher saw at a given time; it does not show that a statement is correct. Prices are per call in US dollars, paid in USDC with x402 v2 on Base Sepolia (a test network). A request with a body that fails validation is refused before payment and never charged; a POST with an empty body gets the 402 offer, so the price can be read. Every page below is also served as HTML at the same URL without `.md`. To pay after a 402, read the offer from the `PAYMENT-REQUIRED` header, sign it with your wallet, and send the same request again with the signed payment in the `PAYMENT-SIGNATURE` header; the paid answer carries `PAYMENT-RESPONSE`. Which route: you have a URL and a quote, use verify/quote; you already hold the text, use verify/text-quote; several quotes from one page, use verify/quotes. The live offer is `eip155:84532`, Base Sepolia (a test network): payments use free test USDC from a faucet, and no real money moves. Install the client first: `npm install @x402/fetch @x402/evm viem`. Get test USDC first (test mode only: Base Sepolia, `eip155:84532`, free; skip it on the live offer, which is real money): 1. Make a wallet key: 0x followed by 64 hex characters (a 32-byte private key). In Node: `import { generatePrivateKey } from 'viem/accounts'; console.log(generatePrivateKey())`. Put it in `ATTESTPAGE_X402_KEY`. 2. Get its address with `privateKeyToAccount(key).address` (the example below prints it). 3. Ask the Base Sepolia faucet: POST `{"address": ""}` as JSON to `https://api.run402.com/faucet/v1`; one drip per wallet and IP every 24 hours, no key or account needed. If it answers 429, wait for the limit to reset (about 24 hours) or fund the wallet address with Base Sepolia test USDC from another source (for example the Circle faucet at faucet.circle.com, or ask your operator). One drip is 0.25 USDC, enough for 25 calls at 0.01 USDC (US$0.01) each. 4. Then pay with the example below. No ETH is needed; the facilitator pays the gas. Example with an x402 v2 client (installed above; save as an `.mjs` file for top-level await), paying one check on Base Sepolia (a test network): ```js import { wrapFetchWithPayment, x402Client } from '@x402/fetch'; import { registerExactEvmScheme } from '@x402/evm/exact/client'; import { privateKeyToAccount } from 'viem/accounts'; // ATTESTPAGE_X402_KEY: 0x + 64 hex characters, funded with USDC (steps 1-3) before this runs const account = privateKeyToAccount(process.env.ATTESTPAGE_X402_KEY); console.log('wallet address', account.address); const client = new x402Client(); registerExactEvmScheme(client, { signer: account }); const payingFetch = wrapFetchWithPayment(fetch, client); const res = await payingFetch('https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/verify/quote', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ url: 'https://www.iana.org/help/example-domains', quote: 'These domains may be used as illustrative examples in documents' }), }); console.log(res.status, res.headers.get('PAYMENT-RESPONSE')); const body = await res.json(); if (res.status === 200) { console.log(body.result ?? body); } else { // not a payment problem when the status is not 402: 400 is a bad request (never charged), 429 slow down, 5xx try again later console.error('not paid or refused', res.status, body); } ``` There is no free trial at the moment; use the free samples instead. ## Paid endpoints - [POST /v1/verify/quote](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-quote.md): Body fields: `url` (required), `quote` (required), `match`, `case_sensitive`, `threshold`. US$0.01. Use when you are about to quote or cite a web page as the source of a claim: send the URL and the quote word for word, and get back exact, fuzzy or none, offsets, context, page verdict, an additive `evidence` field (the matched passage and a one-line reason) and a signed receipt, so a misquote is caught before a user sees it. A fact check of the claim against its source. For a paraphrased claim, the 3 passages that share most of its words are evidence to judge, not a verdict. For 3 or more quotes on one page, verify/quotes costs less. PDFs are read too, on the full service (edge: 501 not_on_edge, uncharged), with pdf_page and pdf_page_end. - [POST /v1/verify/quotes](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-quotes.md): Body fields: `url` (required), `quotes` (required; max 20 items), `match`, `case_sensitive`, `threshold`. US$0.02 per batch of up to 20 quotes on one page. Use when you are about to quote one page several times. The verify/quote verdict for each quote, the page fetched once, under one receipt; one price for 1 to 20 quotes, so from 3 quotes up it costs less than one verify/quote call each. A call where every quote fails answers 422, not charged, under the same per-IP limit as verify/citations. - [POST /v1/verify/text-quote](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-text-quote.md): Body fields: `text` (required), `quote` (required), `start`, `end`, `match`, `case_sensitive`, `threshold`. US$0.005. Use when you already hold the source text (a file you read, a tool result) and are about to quote it. Send the text and the quote, get exact, fuzzy or none and a signed receipt; nothing is fetched. Declared offsets (start and end) check one slice of the text; declared offsets are raw, result offsets are into the normalised text. The receipt attests the hash and the match, not where the text came from. - [POST /v1/verify/text-quote/preview](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-text-quote.md): free, 30 a day per IP address. Send the same body as POST /v1/verify/text-quote and get back exact, fuzzy or none, the score and the number of matches. Use when you want the label before paying. Not signed: no offsets, no receipt. - [POST /v1/verify/citations](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-citations.md): Body fields: `citations` (required; max 10 items), `match`, `case_sensitive`, `threshold`. US$0.10 per batch of up to 10 quote and URL pairs. Use before giving an answer that cites web pages. A fact check of each cited claim against its source: the verify/quote verdict for each citation and the verdict on each page, each page fetched once, under one receipt. For 1 to 9 pairs, one verify/quote call per pair costs less. A batch where every citation fails answers 422, not charged; each IP address gets 6 of those a minute, then 429 `rate_limited` (`details.reason` `uncharged_failures`) with `Retry-After`. - [POST /v1/verify/document](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-document.md): Body fields: `document` (required), `format`, `match`, `case_sensitive`, `threshold`, `min_words`, `part_size`, `part`. US$0.10 per part of up to 10 quote and link pairs. Use before sending a finished answer or report that quotes web pages. Send it as written; its quotes and their links are found and checked on the live pages, with offsets in your document and on each page. For a part of 1 to 9 pairs, one verify/quote call per pair costs less. Free preview at POST /v1/verify/document/preview (pairs, doc_spans/doc_spans_truncated for repeats, skipped with skipped_truncated and skipped_counts by reason, parts, price). The check response lists skipped the same way; too_short means a quote with fewer words than min_words, which is skipped and not checked. - [POST /v1/fetch](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/fetch.md): Body fields: `url`, `urls` (max 10 items), `return_text`. US$0.002 per URL. Use when you need a web page as clean text, or to notarize one. Send a URL and get `page.content_kind` (real, bot_wall, js_shell, paywall, http_error and others), `page.http_status`, `page.final_url`, `page.title`, hashes, optional clean text and `injection_flags` (prompt-injection patterns found in the text), with a signed receipt as provenance: evidence of what the page said at that time. `page.content_kind` is our verdict on what the server sent: real (readable text), bot_wall (a named challenge page), js_shell (no text until scripts run), paywall, or an error kind. A soft block served with status 200 is caught only by known wording, so it can read as real. content_sha256 covers the full text: keep the receipt, and a changed hash on a later call means the page changed. The receipt is signed; check it free at /v1/receipt/verify. A URL whose host name does not resolve gets 422 `host_not_found` before payment, not charged. Example response, one line: `{"page":{"final_url":"https://www.iana.org/help/example-domains","http_status":200,"content_kind":"real","title":"Example Domains","content_sha256":"…"},"receipt":"eyJ…"}`. Send `urls` for up to 10 pages in one call, priced per URL, each with its own receipt. PDFs too, on the full service (edge: 501, uncharged): page.pdf with page_count, pages_read and truncated. - [POST /v1/recheck](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/recheck.md): Body fields: `receipt`, `url`, `content_sha256`, `return_text`, `history` (max 10 items), `quote`, `quotes` (max 20 items). US$0.003. Use when you rely on a web page you read earlier and need change detection: send an earlier receipt or URL with its content_sha256, and get back unchanged, changed, gone or unreadable with the fields that differ, and a signed receipt naming both hashes. With `quote` (the quote of a verify/quote receipt, sent word for word), `quote.verdict` says still_exact, now_exact, now_fuzzy, now_missing or unchecked. Nothing is stored and nothing calls you back: re-check as often as you like (daily for 30 days is about US$0.09). - [POST /v1/check/links](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/check-links.md): Body fields: `urls` (required; max 10 items). US$0.005 per batch of up to 10 URLs. Use when you are about to give a user links or citations: send up to 10 URLs, and get back the status, redirect chain, final URL, title and hash of each, so a dead, moved or blocked link is caught first. Each URL gets a link verdict: ok, broken, moved or unknown. An optional expected_text per URL is matched on the page and reported; it does not change the verdict. - [POST /v1/check/packages](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/check-packages.md): Body fields: `packages` (required; max 10 items). US$0.005 per batch of up to 10 package names on npm, PyPI or crates.io. Use when you are about to install or recommend a package an LLM suggested: send each name with its ecosystem (npm, pypi or crates), and get back whether each is listed, its latest version, licence and known OSV.dev advisories (vulnerabilities, malware) under one signed receipt, so a made-up or known-bad package is caught before install. No advisory listed does not mean no flaws: not a security verdict. - [POST /v1/attest](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/attest.md): Body fields: `sha256` (required), `note`, `idempotency_key`. US$0.002. Use when you need to show later that a file, answer or record existed unchanged. Signed, timestamped receipt over a SHA-256 you supply: provenance, evidence that the content existed at that time. ## Free - [POST /v1/receipt/verify](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/receipt-verify.md): check a receipt against our published keys, free. Receipts are signed by iss https://still-rapids-9yt7.here.now (JWKS at /.well-known/jwks.json), not the origin; this is the free check. A paid answer's body has `receipt_id` and the receipt names the settled `payment.tx`. - [GET /v1/sample/{quote,citations,document,fetch,links,packages,attest,quotes,recheck,textquote}](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/samples.md): each paid route run on fixed demo data, signed with the example key. Try these first; `/v1/sample/quote?match=passages` shows match "passages" on a paraphrased claim. In openapi.json each paid operation names its sample in `x-402.sample`. - [Receipt demo](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/receipt-demo.md): a signed receipt end to end with free calls: sample, verify, a changed receipt failing, offline check. - [Demo: one paid call and its signed receipt](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/demo.md): one `POST /v1/verify/quote` call recorded in full: the request, the 402 offer, the paid retry with x402, the answer and its receipt, and how to check the receipt signature with Node.js. - [Verify a receipt offline](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-offline.md): a single-file Node.js checker and the JWKS. - [MCP server](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/mcp.md): `quoteproof-mcp`, a stdio MCP server with one tool per route. It pays with x402 from your own key. Install it from [the download list](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/dl/index.json). - Remote MCP endpoint: `POST https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/mcp` (streamable HTTP, JSON answers, no session). The same tools; pay per call by putting an x402 payment payload in `_meta["x402/payment"]`, otherwise paid tools return the payment requirements. Server card: [/mcp/server-card](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/mcp/server-card). See [MCP server](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/mcp.md). - [Agent Skill](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/skill.md): `attestpage-check-sources`, a SKILL.md that tells an agent when to check a source and how to read the verdict. Index with digests: [/.well-known/agent-skills/index.json](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/.well-known/agent-skills/index.json). - [POST /v1/verify/document/preview](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-document.md): free. Send the same body as POST /v1/verify/document and get back the quote and link pairs it would check, what was skipped and why, the number of parts and the exact price. Nothing is fetched, signed or charged; call it before paying. ## How to use it - [Docs index](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/index.md): all pages. - [Paid routes at a glance](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/demo.md#paid-routes-at-a-glance): a curl example for every paid route. - [Payments](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/payments.md): the 402 flow, headers, network and when you are charged. A charge without a delivered result (result_lost) is refunded by hand in USDC, no time limit. - [Quickstart](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/quickstart.md): from discovery to your first paid call, step by step, with prices from pricing.json. - [Worked examples](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/worked-examples.md): one paid call per route, from the request and the 402 offer to the signed receipt, with prices from pricing.json. - [Example recipes](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/recipes.md): LangChain, LlamaIndex, OpenAI Agents SDK and Claude Agent SDK agents that check a source before citing it, paying with x402. - [More examples](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/index.md): runnable recipes with a route and price table: [LangChain](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/langchain.md), [OpenAI Agents SDK](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/openai-agents-sdk.md), [LlamaIndex.TS](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/llamaindex.md), [Vercel AI SDK](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/vercel-ai-sdk.md), [Claude Agent SDK with the stdio MCP server](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/claude-agent-sdk.md), [one x402 payment step by step with plain `fetch`](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/fetch-x402.md) and [one x402 payment step by step in Python](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/examples/python-x402.md). Their clients ask for the price once the day's trial is used up. - Checking several quotes on one page with `POST /v1/verify/quotes` (US$0.02 per call): [OpenAI Agents SDK](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/openai-agents.md) and [Claude Agent SDK](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/claude-agent-sdk.md), through the MCP server or over HTTP with x402, and [LangChain](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/langchain.md) and [LlamaIndex](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/llamaindex.md) in Python over HTTP with x402, [Vercel AI SDK](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/vercel-ai-sdk.md) in JavaScript over HTTP with x402, [CrewAI](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/crewai.md) and [Pydantic AI](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/pydantic-ai.md) in Python over HTTP with x402, and [Mastra](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/mastra.md) in JavaScript over HTTP with x402. - Quickstarts for any client: [curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/curl.md) (one x402 payment) and [an MCP client](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/examples/mcp.md) (the `quoteproof-mcp` server, one tool per route). - [How it works](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/how-it-works.md): what the fetcher does, content_kind values and receipt fields (including the site's IP, TLS certificate and key headers). - [Limits](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/limits.md): size caps, timeouts, rate limits and what the fetcher does not do. - [Errors](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/errors.md): every error code and whether it was charged. - [Status](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/status.md): current state of each route and uptime for 24 hours, 7 days and 30 days. JSON: [/status.json](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/status.json). ## Answers - [Guides](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides.md): every step-by-step guide in one list, with what each one checks, its price and how to use it. - [How to verify a quote an LLM cites](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-a-quote-an-llm-cites.md): fetch the cited page and match the quote, allowing for small differences, with a page verdict and a signed receipt. - [How to check a quote an LLM cites, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/verify-llm-quote.md): the free sample, the 402 offer, one check with x402, how to read the answer, how to check the receipt and the same call as an MCP tool. - [How to check every quote in a draft before publishing, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/verify-document-before-publishing.md): the free preview, the 402 offer, one part paid with x402, how to read the summary and each result, and how to check the receipt. - [How to check the quotes in a draft one source at a time before publishing, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/check-quotes-before-publishing.md): the recipe for grouping quotes by URL, the price from the 402 offer, one quote or one page of up to 20 quotes with x402, how to read the verdict and page kind, and how to keep the receipt. - [How to check every quote in a Markdown document from a CI job before merge, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/check-documents-in-ci.md): the free preview, the 402 offer, one part per paid call, the rule that fails a build on a quote missing from a readable page, a shell script for the CI step, and the same call as an MCP tool. - [How to get a signed receipt of a web page](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/signed-receipt-of-a-web-page.md): what a receipt records about one fetch and how to check it offline. - [How to get a signed receipt of a web page, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/signed-page-receipt.md): the free sample, the 402 offer, one receipt with x402, how to check it and match the page text to its hash, and the same call as an MCP tool. - [How to check links before citing them](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/check-links-before-citing.md): dead, moved, unreadable and invented links, checked 10 at a time. - [How to check links before citing them, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/check-links-before-citing.md): the free sample, the 402 offer, one batch with x402, how to read each result, how to check the cited words, how to check the receipt and the same call as an MCP tool. - [How to check packages before installing them, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/check-packages-before-installing.md): the free sample, the 402 offer, one batch of up to 10 names, how to read exists, the latest version and advisories, how to check the receipt and the same call as an MCP tool. - [How to check a package's licence before installing it, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/check-package-licences-before-installing.md): the free sample, the 402 offer, what the license field holds for npm, PyPI and crates.io, how to sort names by an allow list, how to treat a null licence, and the same call as an MCP tool. - [How to check whether a web page has changed since you read it, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/recheck-page-for-changes.md): the free sample, the 402 offer, one recheck from a receipt or a URL and hash, how to read unchanged, changed, gone and unreadable, how to chain receipts and the same call as an MCP tool. - [How to check a signed receipt before you rely on it, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/check-a-receipt-before-relying-on-it.md): free; get a sample receipt, check it, see what an altered one returns, read the payload and the same call as an MCP tool. - [How to check the citations at the end of an answer before you send it, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/verify-citations-before-answering.md): the free sample, the 402 offer, one batch of up to 10 quote and URL pairs, how to read exact, fuzzy and none beside content_kind, and the same call over MCP. - [How to check many quotes against one page in a single call, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/check-many-quotes-on-one-page.md): the free sample, the 402 offer, up to 20 quotes on one URL at one price, how to read exact, fuzzy and none, and the same call over MCP. - [How to check a quote against text you already hold, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/use-attestpage-text-quote.md): the free sample, the 402 offer, one check of a quote against text you send with no fetch, how to read exact, fuzzy and none, declared offsets, how to check the receipt and the same call as an MCP tool. - [How to turn a web page into clean text for an agent, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/read-web-page-as-clean-text.md): the free sample, the 402 offer, one page as clean text with x402, how to read content_kind and injection_flags, how to check the receipt and the same call as an MCP tool. - [How to fetch a web page as clean text from code, with an x402 client](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/fetch-a-page-with-an-x402-client.md): the free sample, the 402 offer, a Node script that pays one page with x402 and checks the offer first, how to read content_kind and injection_flags, the receipt and the same call as an MCP tool. - [How to get a signed timestamp that shows a file did not change, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/attest-a-file-hash.md): the free sample, the 402 offer, one attestation of a SHA-256, how to read the answer, a retry with idempotency_key, checking the file later and the same call as an MCP tool. - [How to use AttestPage tools from an MCP client, step by step with curl](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/guides/use-attestpage-tools-in-mcp.md): the tool names and prices, the remote endpoint, listing the tools, the 402 offer for a tool call and where the x402 payment goes. - [How to check a package name an LLM suggested before installing it](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/check-package-names-before-install.md): invented package names on npm, PyPI and crates.io, caught before install, 10 at a time. - [How to tell a bot wall from the real page](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/tell-a-bot-wall-from-the-real-page.md): bot challenges, JavaScript shells, paywalls and redirects that return 200 but are not the page. ## Machine-readable - [OpenAPI 3.1](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/openapi.json) - [Prices](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/pricing.json) - [x402 discovery](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/.well-known/x402) - [Well-known index](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/.well-known/) - [API catalog (RFC 9727)](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/.well-known/api-catalog) - [Agent Skills index](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/.well-known/agent-skills/index.json) - [MCP server card (SEP-2127)](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/mcp/server-card) - [AI Catalog](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/.well-known/ai-catalog.json) - [Receipt signing keys (JWKS)](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/.well-known/jwks.json) - [All docs in one file](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/llms-full.txt) ## About - [Home](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/index.md) - [Our fetcher and how to opt out](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/bot.md) - [Terms](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/terms.md) - [Privacy](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/privacy.md) --- Source: https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-quote.md # Verify a quote Last updated: 2026-10-10 `POST /v1/verify/quote` fetches a page and reports whether a quote appears on it: `exact`, `fuzzy` or `none`, with offsets, surrounding context, the page verdict and a signed receipt. For a paraphrased claim, `"match": "passages"` returns instead the passages that share most of its words (below). Price: US$0.01 per call. It checks your exact words, not a reworded version, against the page and returns offsets and page hashes in a signed receipt, all in one US$0.01 call. Use it before citing a page, to check that the words you attribute to it are on it, and that the page you got is real content rather than a bot wall or an error page. For 3 or more quotes on one page, [verify/quotes](/docs/verify-quotes) costs less: one call checks up to 20. ## Request ```json { "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents", "match": "fuzzy", "case_sensitive": false, "threshold": 0.9 } ``` | Field | Required | Meaning | |---|---|---| | `url` | yes | http or https URL, at most 2,048 characters | | `quote` | yes | text to look for, or with `passages` the claim to find support for; at most 1,000 characters after normalisation | | `match` | no | `fuzzy` (default) also accepts small differences; `exact` needs the same text after normalisation; `passages` finds the passages that best share a paraphrased claim's words | | `case_sensitive` | no | default `false`; must be `false` with `passages` | | `threshold` | no | fuzzy similarity needed, 0.5 to 1, default 0.9 | Before matching, both the page text and the quote are normalised: Unicode NFKC, curly quotes and dashes become plain ones, invisible characters are dropped, and all whitespace becomes one space. ## Response From the free sample (`GET /v1/sample/quote`), shortened: ```json { "result": { "match": "fuzzy", "score": 0.963, "start": 211, "end": 238, "context": "...the agency said that emissions fell 12 % in 2025, after two years of slow growth...", "occurrences": 1, "normalised_sha256": "086a29af…" }, "page": { "requested_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "final_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "http_status": 200, "content_kind": "real", "signals": ["status_200"], "title": "Regional climate report 2025 (demo page)", "content_sha256": "0e429607…", "retrieved_at": "2026-10-09T14:19:34.903Z" }, "injection_flags": [], "advice": "The page loaded and looks like real content.", "receipt": "eyJhbGciOiJFZERTQSIs…", "tier": "paid", "rail": "x402", "network": "eip155:8453" } ``` - `match` is `exact`, `fuzzy` or `none`. `score` is the similarity from 0 to 1. - `start` and `end` are code-point offsets into the normalised page text. `normalised_sha256` is the SHA-256 of that text, so anyone holding the page text can check the offsets. - `occurrences` counts exact matches. - `evidence` (added, nothing else changed) holds `passage` (`start`, `end` and `text`: the matched text with 200 characters either side, or `null` with no match), `verdict_reason` (one line, such as `exact at offset 115` or `fuzzy 0.93, differs in 1 word`), `content_kind` and `fetched_at`. The receipt signs only the passage offsets and the reason. - Read `page.content_kind` before relying on `match`. A `none` on a `bot_wall` or `js_shell` page means we could not read the page, not that the quote is absent from it. See [How it works](/docs/how-it-works). ## Passages: a paraphrased claim An agent rarely quotes word for word. With `"match": "passages"`, `quote` is a claim (for example "The agency reported a 12 percent drop in emissions during 2025") and the answer is the 3 passages of the page that share most of its words: On the demo page ([`https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page`](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page)), shortened; try it free with [`GET /v1/sample/quote?match=passages`](https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/quote?match=passages): ```json { "result": { "match": "passages", "score": 0.583, "start": 115, "end": 271, "occurrences": 3, "passages": [ { "start": 115, "end": 271, "score": 0.583, "text": "It is not a real report and its numbers are made up. In its annual summary the agency said that emissions fell 12 % in 2025, after two years of slow growth.", "missing": ["drop"], "flags": ["negation_mismatch"] }, { "start": 357, "end": 513, "score": 0.217, "text": "The agency also noted that the share of renewable power rose to 41 percent, and that the next report will cover water use and air quality across the region.", "missing": ["12", "drop", "emissions", "2025"], "flags": ["number_mismatch"] }, { "start": 0, "end": 114, "score": 0.136, "text": "Regional climate report 2025 This is a demo page served by AttestPage so that agents can try every route for free.", "missing": ["agency", "12", "percent", "drop", "emissions"], "flags": [] } ], "scorer": "lexical-v2", "note": "Passages ranked by how many of the claim's words (weighted by rarity on the page) and word pairs they share. This is evidence for you to judge, not a verdict: ..." } } ``` Here "reported" counted as found because the passage has "report". The best passage does support the claim, but only a reader can tell that "fell 12 %" means a 12 percent drop. It is flagged `negation_mismatch` only because its first sentence says "not a real report": a flag tells you where to look, not that the passage contradicts the claim. - **This is evidence, not a verdict.** A score measures shared words, not meaning. "Emissions did not fall" shares as many words as "emissions fell". Read each passage, and check negation, numbers, dates and the words in `missing`. - **Passages.** A passage is a sentence, or two neighbouring ones; text with no sentence ends is cut every 40 words. A sentence ends at `.`, `!` or `?` before a space, or at `。`, `!` or `?`, but not after a short abbreviation such as "e.g.", "i.e.", "Dr.", "Mr." or an initial ("J. Smith"); "etc." ends one only before a capital. Passages do not overlap and are sorted by `score`, highest first. The top-level `score`, `start`, `end` and `context` are those of the best passage, and `occurrences` counts the passages. - **Score.** From 0 to 1: 0.75 × the share of the claim's words the passage holds, each weighted by how rare it is on the page, plus 0.25 × the share of the claim's neighbouring word pairs it holds. - **How words are compared.** Without case, with light English stemming ("emissions" meets "emission"), and ignoring common English words such as "the" and "was". `%` counts as "percent". In Chinese and Japanese, each character counts as a word. - **Offsets and text.** `start` and `end` are code-point offsets into the normalised page text, as above. `text` is cut to 800 characters with `...` when the passage is longer. - **missing.** Lists the claim's words that the passage lacks, at most 20. - **flags.** Marks a passage worth a closer read; it never changes the score or the order. `negation_mismatch`: the claim or the passage has an English negation ("not", "no", "never", "n't", "without" and similar) and the other has none. `number_mismatch`: the claim has a number the passage lacks and the passage has one the claim lacks, such as 12 against 15 (`12 %`, `12 percent` and `12.0` count as the same number, and so do `1,000` and `1000`). An empty list does not mean the passage agrees with the claim. - **No match.** `match` is `none` with an empty `passages` list when no passage shares a word. - **Errors.** A claim made only of common words or punctuation gets 400. - **Same answer every time.** No language model is used, so the same page and claim give the same answer. `scorer` names the method (now `lexical-v2`) and changes whenever the same page and claim could give different passages, scores or flags. - **Cost.** The cost is linear in the page size, so passages never gets `match_too_costly`. - **Receipt.** The receipt signs `mode`, `scorer` and each passage's `start`, `end` and `score`. The text can be recovered from the page at those offsets, and `missing` and `flags` from that text, the claim and the `scorer`. - **Price.** The same as any other `verify/quote` call. ## PDF A PDF page is matched like any other page. Its text layer is read as described in [Fetch: PDF](/docs/fetch), with no OCR. - A match on a PDF also has `pdf_page` and `pdf_page_end`: the 1-based pages where it starts and ends. Each passage has them too. - `start` and `end` are still offsets into the whole normalised text. In that text, each page break is one space. - Only text inside each page's box is read, as a reader sees it. A quote from text drawn outside the page is not found. - The receipt signs `pdf_page` and `pdf_page_end` next to the offsets. Only the full service reads PDFs: an edge server answers 501 `not_on_edge` (`details.reason` `pdf`) for one, not charged. Limits are in [Limits](/docs/limits). ## Charging The quote and options are checked before payment: a bad request gets 400 and is not charged. A quote too costly to match fuzzily gets 422 `match_too_costly`, not charged; use `"match": "exact"` or a shorter quote. A URL whose host name does not resolve gets 422 `host_not_found`, not charged. Results about the page itself, including 404s, bot walls and timeouts, are charged. See [Payments](/docs/payments). --- Source: https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-quotes.md # Verify quotes on one page Last updated: 2026-10-10 `POST /v1/verify/quotes` checks 1 to 20 quotes against one web page in one call. The page is fetched once, and each quote gets the same answer as [Verify a quote](/docs/verify-quote) (`exact`, `fuzzy` or `none`, score, offsets, context), all under one signed receipt. Price: US$0.02 per call, whether it carries 1 quote or 20, so from 3 quotes up it costs less than one `verify/quote` call per quote. Use it when an answer quotes the same source several times: every quote is checked against the same copy of the page in one paid call. ## Request ```json { "url": "https://www.iana.org/help/example-domains", "quotes": [ "These domains may be used as illustrative examples in documents", "without prior coordination with us" ], "match": "fuzzy" } ``` | Field | Required | Meaning | |---|---|---| | `url` | yes | the page, http or https, at most 2,048 characters | | `quotes` | yes | 1 to 20 strings, each at most 1,000 characters after normalisation | | `match` | no | for every quote: `fuzzy` (default) also accepts small differences; `exact` needs the same text after normalisation; `passages` treats each quote as a paraphrased claim (below) | | `case_sensitive` | no | default `false`; must be `false` with `passages` | | `threshold` | no | fuzzy similarity needed, 0.5 to 1, default 0.9 | The URL and every quote are checked before payment. If the URL is malformed, blocked or disallowed by robots.txt, or any quote is empty, too long or not a string (or, with `passages`, has no word to compare), the whole request is refused with 400 or 403 and not charged. The error's `details.field` names the quote, for example `quotes[2]`. Other fields are refused too. ## Response From the free sample (`GET /v1/sample/quotes`), shortened: ```json { "results": [ { "match": "exact", "score": 1, "start": 384, "end": 431, "context": "... The agency also noted that the share of renewable power rose to 41 percent, and that the next report will cover...", "occurrences": 1, "normalised_sha256": "086a29af…" }, { "match": "fuzzy", "score": 0.963, "start": 211, "end": 238, "context": "...In its annual summary the agency said that emissions fell 12 % in 2025, after two years of slow growth...", "occurrences": 1, "normalised_sha256": "086a29af…" }, { "match": "none", "score": null, "start": null, "end": null, "context": null, "occurrences": 0, "normalised_sha256": "086a29af…" } ], "page": { "requested_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "final_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "http_status": 200, "content_kind": "real", "title": "Regional climate report 2025 (demo page)", "content_sha256": "0e429607…" }, "injection_flags": [], "advice": "The page loaded and looks like real content.", "summary": { "exact": 1, "fuzzy": 1, "none": 1, "failed": 0 }, "receipt": "eyJhbGciOiJFZERTQSIs…", "tier": "paid" } ``` - `results` has one entry per quote, in request order. Each is the `result` of [Verify a quote](/docs/verify-quote): `match`, `score`, `start`, `end`, `context`, `occurrences` and `normalised_sha256` mean the same, and give the same values a `verify/quote` call on that page would. - `page` is the page verdict, hashes and network evidence, once for all the quotes. - Read `content_kind` before relying on `match`. A `none` on a page that is not `real` (a 404, bot wall or JavaScript shell) means we could not read the page, not that the quote is absent from it. - Each result has the additive `evidence` field, the same as in [Verify a quote](/docs/verify-quote): the matched passage with 200 characters either side and a one-line `verdict_reason`. - `summary` counts the verdicts; `failed` counts quotes that carry an `error` instead of a verdict (below). - The receipt signs the page's URL, status, `content_kind`, hashes and evidence, and per quote the SHA-256 of the quote and the verdict. It does not contain the quote text. Check it with [`POST /v1/receipt/verify`](/docs/receipt-verify). ## Passages: paraphrased claims With `"match": "passages"`, each quote is a claim. Each result is then the one described in [Verify a quote: passages](/docs/verify-quote): up to 3 passages from the page that share most of the claim's words, each with `start`, `end`, `score`, `text`, `missing` and `flags`, plus `scorer` and `note`. These are evidence for you to judge, not a verdict. - `match` is `passages` or `none`, and `summary` adds a `passages` count. - The receipt signs each claim's passages (`start`, `end`, `score`) and the `scorer`. - The work is linear in page size, so passages never fail with `match_too_costly`. The price is the same. ## PDF A PDF page is read and matched as in [Verify a quote: PDF](/docs/verify-quote). Each match and passage on it has `pdf_page` and `pdf_page_end`, and the receipt signs them. If the PDF cannot be queued for reading, the call gets 503 `over_capacity` (`details.reason` `pdf_queue_full`) and is not charged. ## Partial failures A quote that would cost too much to match fuzzily gets `"error": { "code": "match_too_costly", ... }` and null match fields; the other quotes are still checked and the call is charged. The fuzzy searches of one call also share a work limit (twice what one `verify/quote` call may use), spent in request order: a quote reached after it is used up gets the same error with a message saying so. If every quote fails this way, the call gets 422 `match_too_costly`, not charged, and `details.failed` lists only which quotes failed. Use `"match": "exact"` or shorter quotes. This route shares the per-address count of uncharged failed batches and the match queue with [Verify citations](/docs/verify-citations): over the count it answers 429 `rate_limited` with `details.reason` `uncharged_failures`, and when the queue is full 503 `over_capacity` with `details.reason` `match_queue_full`, both with a `Retry-After` header and not charged. See [Limits](/docs/limits). ## Edge servers Only the full service runs this route: an edge server answers 501 `not_on_edge`, not charged. ## Charging A URL whose host name does not resolve gets 422 `host_not_found`, not charged. If the page turns out to be disallowed by robots.txt or rate limited after payment (for example after a redirect), the call answers with an error and is not charged. Results about the page itself, including 404s, bot walls and timeouts, are charged. See [Payments](/docs/payments). A free-trial call covers at most 5 quotes. A larger one is not run as a trial: it gets the normal 402 offer, with `details.reason` `trial_too_large`, and is not charged. --- Source: https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-text-quote.md # Verify a quote against text you hold Last updated: 2026-10-13 `POST /v1/verify/text-quote` checks a quote against text the caller already has: a file it read, a tool result, a document. Nothing is fetched, so there are no robots, DNS or bot-wall failures. The answer is `exact`, `fuzzy` or `none`, with a score, offsets and context, under a signed receipt. Price: US$0.005 per call. Use it when the source is already in hand. To check a quote against a live web page, use [Verify a quote](/docs/verify-quote), which fetches the page. ## Free preview `POST /v1/verify/text-quote/preview` takes the same body and returns `result` (`match`, `score`, `occurrences`), `signed: false`, `advice` and `paid_route`. It is free, 30 a day per IP address; `start` and `end` are ignored. The answer has no offsets, hashes or receipt, so nobody can verify it later. Past 30, it answers 429 with `reason: "preview_cap"`. For the signed receipt, call the paid route. ## Request ```json { "text": "Refunds are paid within 14 days of purchase, to the card that was used.", "quote": "refunds are paid within 14 days", "match": "fuzzy", "case_sensitive": false } ``` | Field | Required | Meaning | |---|---|---| | `text` | yes | the source text, as sent: a non-empty string of at most 50,000 characters (code points) and 64 KB of UTF-8 | | `quote` | yes | the quote, at most 1,000 characters after normalisation | | `start`, `end` | no | both or neither: a slice of `text` to check, in characters (code points), 0-based, `end` exclusive, at most 1,000 characters | | `match` | no | `fuzzy` (default) also accepts small differences; `exact` needs the same text after normalisation; `passages` treats the quote as a paraphrased claim and returns the passages of the text that share its words (not with `start` and `end`) | | `case_sensitive` | no | default `false`; must be `false` with `passages` | | `threshold` | no | fuzzy similarity needed, 0.5 to 1, default 0.9 | `url` is refused: this route never fetches. Every field is checked before payment, so a bad request is answered with 400 and not charged. ## Offsets Two coordinate systems, named differently: - `start` and `end` in the request are **raw** code-point offsets into `text` as sent. With them, only that slice is matched, so the answer is whether the text at those offsets says the quote. `result.declared` echoes them with `slice_sha256` (the hash of the raw slice) and `slice_matches`. - `result.start` and `result.end` in a search (no offsets sent) are **normalised**: offsets into the text after normalising (curly quotes, spaces and case folding for matching, as on `verify/quote`). `normalised_sha256` is the hash of that normalised text. - `result.located` appears only when the declared slice does not say the quote but the whole text does: it gives the quote's position in the whole text (`match`, `score`, `start`, `end`, `occurrences`), with offsets normalised (`offsets: "normalised"`), not raw. ## Response ```json { "result": { "match": "fuzzy", "score": 0.972, "start": 0, "end": 34, "context": "Refunds are paid within 14 days of purchase", "occurrences": 1, "normalised_sha256": "…", "source": { "kind": "supplied", "text_sha256": "…", "code_points": 79 } }, "injection_flags": [], "advice": "The supplied text contains a close match to this quote, with small differences. This says nothing about where the text came from.", "receipt": "eyJ…", "tier": "paid" } ``` - `injection_flags` lists instruction-like text found in the supplied text. Agents paste untrusted output, so read them. - The receipt is the caller's claim: it attests the hash of the text (`source.text_sha256`) and the match, not that the text came from any particular site. ## Errors A 400 with `invalid_request` covers: an unknown field (including `url`), a missing or empty `text` or `quote`, `start` or `end` alone, a non-integer or negative offset, `start` not below `end` or `end` past the text (`reason` `offset_out_of_range`), a slice over 1,000 characters, text over 50,000 characters, a lone surrogate in `text` or `quote`, and offsets with `match` `passages`. A body over 64 KB gets 413. A fuzzy match too costly for the matcher gets 422 `match_too_costly`, not charged. --- Source: https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-citations.md # Verify citations Last updated: 2026-10-10 `POST /v1/verify/citations` checks 1 to 10 citations, each a quote and the URL it is attributed to, in one call. For each one you get the same answer as [Verify a quote](/docs/verify-quote) (`exact`, `fuzzy` or `none`, score, offsets, context) plus the verdict on its page, all under one signed receipt. Price: US$0.10 per batch. Use it on the sources at the end of an answer before the user sees it: a made-up quote, a misquote, or a link to a dead or blocked page is caught in one paid call. For 1 to 9 pairs, one [verify/quote](/docs/verify-quote) call per pair costs less, without the Crossref data. ## Request ```json { "citations": [ { "url": "https://www.iana.org/help/example-domains", "quote": "These domains may be used as illustrative examples in documents" }, { "url": "https://www.iana.org/domains/reserved", "quote": "IANA-managed Reserved Domains" } ], "match": "fuzzy" } ``` | Field | Required | Meaning | |---|---|---| | `citations` | yes | 1 to 10 objects `{ "url", "quote" }`. `url` is http or https, at most 2,048 characters; `quote` is at most 1,000 characters after normalisation | | `match` | no | for every citation: `fuzzy` (default) also accepts small differences; `exact` needs the same text after normalisation; `passages` treats each quote as a paraphrased claim (below) | | `case_sensitive` | no | default `false`; must be `false` with `passages` | | `threshold` | no | fuzzy similarity needed, 0.5 to 1, default 0.9 | Several citations may share a URL: each distinct URL is fetched once and every quote on it is matched against the same copy. Every URL is checked before payment; if one is malformed, blocked or disallowed by robots.txt, or any quote is empty or too long (or, with `passages`, has no word to compare), the whole request is refused with 400 or 403 and not charged. The error's `details.field` names the citation, for example `citations[2].quote`. ## Response From the free sample (`GET /v1/sample/citations`), shortened: ```json { "results": [ { "url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "page": 0, "final_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "http_status": 200, "content_kind": "real", "match": "fuzzy", "score": 0.963, "start": 211, "end": 238, "context": "...the agency said that emissions fell 12 % in 2025, after two years of slow growth...", "occurrences": 1, "advice": "The page loaded and looks like real content." }, { "url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/missing", "page": 1, "final_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/missing", "http_status": 404, "content_kind": "http_error", "match": "none", "score": null, "start": null, "end": null, "context": null, "occurrences": 0 } ], "pages": [ { "requested_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "final_url": "https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/v1/sample/page", "http_status": 200, "content_kind": "real", "title": "Regional climate report 2025 (demo page)", "content_sha256": "0e429607…", "normalised_sha256": "086a29af…", "injection_flags": [] } ], "summary": { "exact": 0, "fuzzy": 1, "none": 2, "failed": 0, "retracted": 0, "doi_unchecked": 0 }, "receipt": "eyJhbGciOiJFZERTQSIs…", "tier": "paid" } ``` - `results` has one entry per citation, in request order. `page` is its index in `pages`, which has one entry per distinct URL with the page verdict, hashes and network evidence. - `match`, `score`, `start`, `end`, `context` and `occurrences` mean what they mean in [Verify a quote](/docs/verify-quote). Offsets are into the normalised text of that citation's page; `normalised_sha256` on the page lets anyone holding the text check them. - Read `content_kind` before relying on `match`. A `none` on a page that is not `real` (a 404, bot wall or JavaScript shell) means we could not read the page, not that the quote is absent from it. - Each result has the additive `evidence` field, the same as in [Verify a quote](/docs/verify-quote): the matched passage with 200 characters either side and a one-line `verdict_reason`. - `summary` counts the verdicts; `failed` counts citations that carry an `error` instead of a verdict (below). `retracted` counts citations on a paper Crossref marks retracted, and `doi_unchecked` citations whose page's DOI lookup was skipped, so their retraction status is unknown (see Retracted papers; both absent when DOI lookups are off). - The receipt signs, per page, the URL, status, `content_kind`, hashes and evidence, and per citation its page index, the SHA-256 of the quote and the verdict. It does not contain the quote text. Check it with [`POST /v1/receipt/verify`](/docs/receipt-verify). ## Passages: paraphrased claims With `"match": "passages"`, each citation's `quote` is a claim. Each result is then the one described in [Verify a quote: passages](/docs/verify-quote): up to 3 passages from the page that share most of the claim's words, each with `start`, `end`, `score`, `text`, `missing` and `flags`, plus `scorer`. - `match` is `passages` or `none`, and `summary` adds a `passages` count. - The response has a top-level `note`: the passages are evidence for you to judge, not a verdict. - The receipt signs each citation's passages (`start`, `end`, `score`) and the `scorer`. - The work is linear in page size, so passages share no work limit and never fail with `match_too_costly`. The price is the same. ## PDF A cited PDF is read and matched as in [Verify a quote: PDF](/docs/verify-quote). Each match and passage on it has `pdf_page` and `pdf_page_end`, and the receipt signs them. Its entry in `pages` has `pdf` (`page_count`, `pages_read`, `truncated`). If one PDF in the batch cannot be queued for reading, the whole batch gets 503 `over_capacity` (`details.reason` `pdf_queue_full`) and is not charged. ## Retracted papers (DOI) A cited page with a DOI gets `crossref` in its `pages` entry: the work's `title`, `authors` (up to 10, with `authors_total`), `year`, `container_title` (the journal) and `type` from Crossref's public metadata, and `retracted`: `true` when Crossref lists a retraction, withdrawal or removal notice for it (Crossref carries the Retraction Watch database). `updates` lists every notice that updates the work (`type` such as `retraction`, `correction` or `expression_of_concern`, notice `doi`, `source`, `date`), and `update_to` is filled when the cited DOI is itself such a notice. `summary.retracted` counts the citations on retracted works. A citation that matches its quote on a retracted paper still needs a warning before you use it. The DOI is taken from a `doi.org` link, else the page's own `citation_doi` (or similar) meta tag, else a DOI in the URL path; `doi_source` says which (`url` or `page`). `status` is `found`, `not_found` (not a Crossref DOI; DataCite and other agencies are not checked) or `skipped` with a `reason` (`rate_limited`, `busy`, `timeout` or `unavailable`) when Crossref is slow or we are at our share of its public limits. A skipped lookup never fails the call or changes the charge; `summary.doi_unchecked` counts the citations whose page's DOI lookup was skipped, so `retracted`: 0 with `doi_unchecked` above 0 means not every DOI was checked. Lookups are made one at a time across all callers (at most 120 a minute for paid calls; free trial calls have their own budget of 6 a minute, so they never use the paid one) and answers are cached for a day. The receipt signs each page's `doi`, `status` and `retracted`. ## Partial failures A quote that would cost too much to match fuzzily on its page gets `"error": { "code": "match_too_costly", ... }` and null match fields; the rest of the batch is still checked and the batch is charged. The fuzzy searches of one batch also share a work limit (twice what one `verify/quote` call may use), spent in page order: a citation reached after it is used up gets the same error with a message saying so, while an exact match is still found. If every citation in the batch fails this way, the call gets 422 `match_too_costly`, not charged, and `details.failed` lists only which citations failed. Use `"match": "exact"` or shorter quotes. Each caller IP address gets 6 uncharged failed batches a minute (a bucket that refills one every 10 seconds). A batch holds a place in that bucket while it runs and gives it back unless every citation failed, so a caller with 6 batches still running also waits. Over the limit, `verify/citations` answers 429 `rate_limited` with `details.reason` `uncharged_failures` and a `Retry-After` header, before any page is fetched, and nothing is charged. This normally comes before the 402; if your own parallel batches take the last place meanwhile, it comes after your payment is verified and before it is settled. Charged batches never use the bucket up. A few batches are matched at once across all callers and a short queue waits behind them. When that queue is full, `verify/citations` answers 503 `over_capacity` with `details.reason` `match_queue_full` and a `Retry-After` header (a few seconds), and nothing is charged: before the 402 if the queue is already full, or after your payment is verified and before it is settled if it filled while your pages were fetched. Retry the same request after `Retry-After`. ## Dead domains A URL whose host name does not resolve is reported as a result: `content_kind` `http_error`, `http_status` null, `match` `none`, and the batch is charged. If no URL in the batch resolves, the call gets 422 `host_not_found` and is not charged. ## Charging If a page turns out to be disallowed by robots.txt or rate limited after payment (for example after a redirect), the whole batch answers with an error and is not charged. Results about the pages themselves, including 404s, bot walls and timeouts, are charged. See [Payments](/docs/payments). A free-trial call covers at most 5 citations. A larger batch is not run as a trial: it gets the normal 402 offer, with `details.reason` `trial_too_large`, and is not charged. --- Source: https://s2h6qt73jtzlgbgnnfacln2poq.srv.us/docs/verify-document.md # Verify a document Last updated: 2026-10-10 `POST /v1/verify/document` takes an answer or report as written, finds every quote in it with its link by fixed rules, and checks each pair on the live page, as [Verify citations](/docs/verify-citations) does: `exact`, `fuzzy` or `none`, score, offsets in your document and on the page, and the verdict on each page, under one signed receipt. Price: US$0.10 per part of up to 10 pairs. Use it before sending a finished answer or report that quotes web pages. Call the free preview first: it lists the pairs, what was skipped and why, the number of parts and the exact price. For a part of 1 to 9 pairs, one [verify/quote](/docs/verify-quote) call per pair costs less; the free preview lists each pair and its part. ## Request ```json { "document": "WHO says \"Air pollution kills an estimated seven million people every year\" ([WHO](https://www.who.int/health-topics/air-pollution)).", "format": "markdown", "match": "fuzzy", "part": 0 } ``` | Field | Required | Meaning | |---|---|---| | `document` | yes | The answer or report as text, 1 character to 60,000 bytes UTF-8 | | `format` | no | `markdown` (default), `text` (only bare URLs and `[n]` references are links) or `html` (`` links, `
` and `` quotes; other tags are dropped) | | `match` | no | `fuzzy` (default), `exact` or `passages`, as verify/citations; it also chooses what is found (below) | | `case_sensitive` | no | default `false`; must be `false` with `passages` | | `threshold` | no | 0.5 to 1, default 0.9 (fuzzy only) | | `min_words` | no | 1 to 20, default 4: a quoted span with fewer words is skipped as `too_short` | | `part_size` | no | 1 to 10, default 10: pairs per part. A free trial call takes at most the size in `extensions.trial.info.max_batch_items` of the 402 offer | | `part` | no | 0-based part to check, default 0. Ignored by the preview | Unknown fields are refused with 400. Every URL in the part is checked before payment, as on verify/citations: a blocked or robots-disallowed URL refuses the call with 400 or 403, `details.field` naming the pair (for example `pairs[3].url`), and nothing is charged. ## Preview `POST /v1/verify/document/preview` is free. Send the same body; nothing is fetched or charged. ```json { "document_sha256": "9f2c…", "extractor": "doc-v1", "pairs_total": 1, "part_size": 10, "parts": 1, "price": { "usd_per_part": "0.10", "sats_per_part": 100, "usd_total": "0.10", "sats_total": 100 }, "pairs": [ { "pair": 0, "part": 0, "quote": "Air pollution kills an estimated seven million people every year", "url": "https://www.who.int/health-topics/air-pollution", "doc_start": 10, "doc_end": 74, "source": "inline_link" } ], "skipped": [], "skipped_truncated": 0, "skipped_counts": {}, "limit_reached": false } ``` - `doc_start`/`doc_end`: code-point offsets of the quoted words in `document` as sent. A pair found more than once (same quote, same URL) is one pair with `doc_spans`, checked and charged once. `doc_spans` holds the first 20 places; `doc_spans_truncated` counts the rest. - `source`: `inline_link`, `reference`, `footnote`, `numbered`, `bare_url`, `blockquote` or `sentence` (passages). - `skipped[]`: `{ doc_start, doc_end, reason, text }`; reasons `too_short`, `too_long`, `no_url`, `bad_url`, `beyond_limit`. It lists the first 100; `skipped_truncated` counts the rest and `skipped_counts` gives every skip by reason. - The preview contacts no URL, so a dead or blocked URL shows up only on the paid call. The preview takes from the same per-address limit as the paid routes and works on the edge server too. ## Response The verify/citations response for this part, plus the document fields: ```json { "document_sha256": "9f2c…", "extractor": "doc-v1", "part": 0, "parts": 1, "pairs_total": 1, "results": [ { "pair": 0, "quote": "Air pollution kills…", "url": "https://www.who.int/health-topics/air-pollution", "doc_start": 10, "doc_end": 74, "page": 0, "final_url": "…", "http_status": 200, "content_kind": "real", "match": "fuzzy", "score": 0.96, "start": 412, "end": 478, "context": "…", "occurrences": 1 } ], "pages": [ "as verify/citations" ], "summary": { "exact": 0, "fuzzy": 1, "none": 0, "failed": 0 }, "receipt": "eyJ…", "tier": "paid" } ``` - `doc_start`/`doc_end` are offsets in your document; `start`/`end` are offsets on the page, as in [Verify a quote](/docs/verify-quote). Read `content_kind` before relying on `match`. - The receipt (kind `document`) signs `document_sha256`, the extractor, the options, the part and, per pair, its page, the SHA-256 of the quote, its document offsets (with `doc_spans` and `doc_spans_truncated` for a repeated pair) and its verdict. It does not contain the quote text. Whoever holds the document can tie every part's receipt to it. Check it with [`POST /v1/receipt/verify`](/docs/receipt-verify). ## How pairs are found The rules are fixed and named by `extractor` (`doc-v1`) in every answer and receipt; a change gets a new name. - Code blocks and inline code are ignored. With `format` `markdown`, links are inline links, reference links, footnotes, `[n]` references with a numbered list and bare URLs; with `text`, only bare URLs and `[n]` references; with `html`, `` links, `
` (its `