# 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](/docs/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:

- `kind` `fetch`, `iss` (the issuer, `https://still-rapids-9yt7.here.now`) and `iat` (the signing time, in Unix seconds);
- `url`, the address you sent, and `final_url` after redirects, with `http_status`;
- `retrieved_at`, the time of the fetch;
- `content_sha256`, the SHA-256 of the full page text, and `raw_sha256`, the SHA-256 of the body as received;
- `content_kind`: `real`, `bot_wall`, `js_shell`, `paywall`, `http_error` and others;
- `server_ip`, `tls` and a short list of response `headers`, where our fetcher could read them;
- `request_sha256`, the SHA-256 of your request body, so you can show which request it answers;
- `tier` and `payment`, the payment that covered the call.

## 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](https://vehcdj664efetfrsolne5umanq.srv.us/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:

```sh
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
```

```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:

```sh
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:

```sh
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](/docs/examples/fetch-x402) shows this with plain `fetch`, and the [MCP server](/docs/mcp) 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`:

```sh
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_kind` | What the receipt shows |
|---|---|
| `real` | The page loaded as content. The receipt records that content. |
| `bot_wall`, `js_shell`, `paywall` | The site served a challenge, an empty JavaScript shell or a login wall. The receipt records that, not the article. |
| `off_site` | The URL redirected to another site. `final_url` says where. |
| `http_error`, `timeout` | The 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](/privacy)).

## 5. Check the receipt

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

```sh
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:

```sh
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](/docs/verify-offline). The keys are at [/.well-known/jwks.json](https://vehcdj664efetfrsolne5umanq.srv.us/.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:

```sh
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:

```json
{
  "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](/docs/mcp).

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:

```sh
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
```

```json
{
  "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](/docs/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](/docs/attest) (US$0.002) signs a timestamp over a SHA-256 you send. We never see the file.
