# Receipt demo

Last updated: 2026-10-10

A signed receipt from start to finish, using only free calls: get a receipt from a free sample, check it with `POST /v1/receipt/verify`, see a changed receipt fail, then check it offline. Step 5 adds the free trial on a real route. No payment, wallet or account is needed. The commands use `curl` and `jq`.

## 1. Get a receipt

`GET /v1/sample/fetch` runs the real [fetch](/docs/fetch) code on the [demo page](https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/page) and signs the result with the sample key.

```sh
curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/fetch > sample.json
jq '{tier, page: .page.http_status, kind: .page.content_kind, sha: .page.content_sha256}' sample.json
jq -r .receipt sample.json > receipt.txt
```

The response has the page result and a `receipt` field: a compact JWS (`header.payload.signature`). The receipt records the URL, final URL, status, content_kind, page hashes, time of the fetch and a hash of the request.

## 2. Check it

```sh
jq -n --rawfile r receipt.txt '{receipt: ($r | rtrimstr("\n"))}' > body.json
curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'Content-Type: application/json' -d @body.json
```

The answer:

```json
{
  "valid": true,
  "kid": "https://vehcdj664efetfrsolne5umanq.srv.us/.well-known/jwks.json#sample",
  "tier": "sample",
  "payload": {
    "v": 1,
    "iss": "https://vehcdj664efetfrsolne5umanq.srv.us",
    "kind": "fetch",
    "tier": "sample",
    "url": "https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/page",
    "http_status": 200,
    "content_kind": "real",
    "content_sha256": "0e429607…",
    "payment": null,
    "…": "…"
  }
}
```

`payload.content_sha256` matches `page.content_sha256` from step 1.

## 3. Change one character

```sh
jq -n --rawfile r receipt.txt '{receipt: ($r | rtrimstr("\n") | .[0:-2] + (if .[-2:-1] == "A" then "B" else "A" end) + .[-1:])}' > bad.json
curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'Content-Type: application/json' -d @bad.json
```

The answer has `"valid": false` and `"reason": "bad_signature"`.

## 4. Check it offline

The [offline checker](/docs/verify-offline) is one Node.js file. It checks the receipt against the published keys at [/.well-known/jwks.json](https://vehcdj664efetfrsolne5umanq.srv.us/.well-known/jwks.json) without calling the verify route.

```sh
curl -sO https://vehcdj664efetfrsolne5umanq.srv.us/dl/verify-receipt.mjs
node verify-receipt.mjs "$(cat receipt.txt)" --issuer https://still-rapids-9yt7.here.now
```

## 5. Try a real route

For a receipt about a URL of your choice, call a paid route such as [fetch](/docs/fetch). 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.

A sample receipt is signed with the sample key and says nothing about any page other than the demo page. Paid and trial receipts cover the URL you asked for. See [Samples](/docs/samples), [Verify a receipt](/docs/receipt-verify) and [Payments](/docs/payments).
