AttestPage · Docs · Pricing · OpenAPI · llms.txt

How to get a signed receipt of a web page, step by step with curl

Last updated: 2026-10-10

To get a signed, timestamped receipt of what a web page showed, send its URL to POST /v1/fetch (US$0.002 per call). AttestPage loads the page and returns an Ed25519-signed receipt with the final URL, HTTP status, fetch time, a hash of the page text and a verdict on what came back.

This guide gets one receipt with curl and jq against https://vehcdj664efetfrsolne5umanq.srv.us, checks it, then shows the same call as an MCP tool. For what a receipt is and when an agent needs one, see How to get a signed receipt of a web page.

What the receipt records

The receipt is a compact JWS (alg EdDSA, typ attestpage-evidence+jws). Its payload has:

Price

US$0.002 per call, paid in USDC with x402 v2 on Base Sepolia (a test network). No account, key or sign-up. A request that fails validation gets 400 before payment and is never charged. All prices: pricing.json.

1. See a receipt for free

The free sample runs the same route on a demo page and signs it with the sample key:

curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/fetch > sample.json
jq '{kind: .page.content_kind, status: .page.http_status, retrieved_at: .page.retrieved_at, content_sha256: .page.content_sha256, tier}' sample.json
{
  "kind": "real",
  "status": 200,
  "retrieved_at": "2026-10-10T12:09:00.731Z",
  "content_sha256": "0e429607fdb16c005ef662414e758c53edb82e1cb24c3cbec239e4c1d6a7dfa0",
  "tier": "sample"
}

A sample receipt is signed with a separate key and says tier sample, so it can never pass as evidence about a real page.

2. Read the price from the 402 offer

A request with an empty body gets the payment offer without being charged. The PAYMENT-REQUIRED header is base64 JSON:

curl -si -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/fetch -H 'content-type: application/json' -d '{}' \
  | grep -i '^payment-required:' | cut -d' ' -f2 | tr -d '\r' | base64 -d \
  | jq '.accepts[0] | {scheme, network, amount, payTo}'

amount is in USDC base units (6 decimals), so 2000 is US$0.002.

3. Get the receipt

Put the page URL in a file. return_text also returns up to 20,000 characters of clean text, which you need if you want to match the hash later:

cat > body.json <<'EOF'
{ "url": "https://www.iana.org/help/example-domains", "return_text": true }
EOF

Pay with any x402 v2 client: it reads the offer from step 2, signs it and sends the same request again with a PAYMENT-SIGNATURE header. One x402 payment step by step shows this with plain fetch, and the MCP server does it for you. Save the answer as answer.json for the steps below.

A free trial is on: send the header quoteproof-trial: 1 with a paid route and no payment, and the call runs without charge, 5 calls per IP address per UTC day and 200 a day across all callers. Trial receipts have tier "trial" and payment null. Over the limit you get 429 trial_exhausted with Retry-After. A trial call to verify/citations, verify/quotes or check/links covers at most 3 citations, quotes or URLs; a larger batch is not run as a trial but answered with the normal 402 offer, details.reason trial_too_large.

On the trial, the same call runs from curl:

curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/fetch -H 'content-type: application/json' \
  -H 'quoteproof-trial: 1' -d @body.json > answer.json
jq '{kind: .page.content_kind, status: .page.http_status, final_url: .page.final_url, tier}' answer.json

4. Read what was fetched

Read page.content_kind before relying on the receipt:

content_kindWhat the receipt shows
realThe page loaded as content. The receipt records that content.
bot_wall, js_shell, paywallThe site served a challenge, an empty JavaScript shell or a login wall. The receipt records that, not the article.
off_siteThe URL redirected to another site. final_url says where.
http_error, timeoutThe site answered with an error status, could not be reached or did not answer in time.

advice gives the same verdict in one sentence. Keep answer.json yourself: pages fetched for you are not stored (Privacy).

5. Check the receipt

The receipt field is the signed record. Check it with the free verify route:

jq '{receipt}' answer.json \
  | curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'content-type: application/json' -d @- \
  | jq '{valid, tier, url: .payload.url, retrieved_at: .payload.retrieved_at}'

Or with no call to us at all, using the single-file checker pinned to our issuer:

curl -O https://vehcdj664efetfrsolne5umanq.srv.us/dl/verify-receipt.mjs
jq -r .receipt answer.json | node verify-receipt.mjs - --issuer https://still-rapids-9yt7.here.now

It prints {valid, reason?, kid, tier, payload} and exits 0 when the receipt is valid. Details: Verify receipts offline. The keys are at /.well-known/jwks.json.

6. Match the text to the receipt

When text_truncated is false, the SHA-256 of text equals content_sha256 in the receipt:

jq -j .text answer.json | shasum -a 256
jq -r .page.content_sha256 answer.json

The two hashes match, so anyone holding that text and the receipt can see it is what our fetcher read at retrieved_at.

With MCP

The same receipt is the MCP tool attestpage_fetch, with the body from step 3 as its arguments. To use it from an MCP client with nothing to install, add the remote endpoint:

{
  "mcpServers": {
    "attestpage": { "type": "http", "url": "https://vehcdj664efetfrsolne5umanq.srv.us/mcp" }
  }
}

Or run the local server, which pays from your own wallet key within limits you set: MCP server.

One call to the remote endpoint with curl. _meta["attestpage/trial"] set to false asks for the payment offer rather than a trial call, so nothing is charged:

curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/mcp -H 'content-type: application/json' -d @- <<'EOF' \
  | jq '.result.structuredContent.accepts[0] | {scheme, network, amount}'
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "attestpage_fetch",
    "arguments": { "url": "https://www.iana.org/help/example-domains", "return_text": true },
    "_meta": { "attestpage/trial": false } } }
EOF
{
  "scheme": "exact",
  "network": "eip155:84532",
  "amount": "2000"
}

The result has isError: true and the x402 payment requirements in structuredContent, with the same amount as step 2. Sign one of accepts, put the payload in _meta["x402/payment"] and call again: structuredContent is then the same JSON as answer.json, and _meta["x402/payment-response"] holds the settlement.

Questions

How much does one receipt cost?

US$0.002 per page with POST /v1/fetch. For up to 10 URLs under one receipt, POST /v1/check/links costs US$0.005 per batch: Check links.

Does the receipt show the page is accurate?

No. It records what our fetcher received at that time. Pages change and can show different content to different visitors.

Can I get a receipt for a file instead of a URL?

Yes. Attest a hash (US$0.002) signs a timestamp over a SHA-256 you send. We never see the file.