# How to get a signed timestamp that shows a file did not change, step by step with curl

Last updated: 2026-10-13

To show later that a file, answer or record existed unchanged, send its SHA-256 hash to `POST /v1/attest` (US$0.002 per call). AttestPage returns a signed receipt that names the hash and the time it was issued. A later copy that differs by one byte has a different hash and no longer matches.

This guide hashes a file, attests it with `curl` and `jq` against `https://vehcdj664efetfrsolne5umanq.srv.us`, retries, checks the file again later, and shows the same call as an MCP tool. For every field and error, see [Attest a hash](/docs/attest).

## What the call does

- **Takes only a hash.** You send 64 hex characters, never the file. We cannot read it and do not keep it.
- **Signs a receipt.** An Ed25519 receipt covers the hash, our running count `seq` and `issued_at`, the time we signed it. Anyone can check it against our published keys.
- **Keeps a note's hash, not the note.** An optional `note` of up to 280 characters goes in as its own SHA-256, so the receipt shows a note was sent without revealing it.
- **Returns the same receipt on a retry.** With an `idempotency_key`, sending the same request again gets the byte-identical receipt and no new record.

The receipt is evidence that the hash existed at `issued_at`. It does not say who made the file or that its contents are true.

## Price

US$0.002 per call, paid in USDC with x402 v2 on Base mainnet (real USDC). No account, key or sign-up. A request with a malformed hash, note or key gets 400 before payment and is never charged, and an `idempotency_key` reused with a different hash or note gets 409 and is not charged. All prices: [pricing.json](https://vehcdj664efetfrsolne5umanq.srv.us/pricing.json).

## 1. See an answer for free

The free sample runs the same route on a fixed demo page:

```sh
curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/attest | jq '{sha256: .attestation.sha256, seq: .attestation.seq, first_seen: .attestation.first_seen, tier}'
```

```json
{
  "sha256": "179b9ac481c5d31db5b6493fdf77b3c741c944afd4845677f798d19b428efb08",
  "seq": 1,
  "first_seen": true,
  "tier": "sample"
}
```

## 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/attest -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. Attest the file

Take the hash of the file: `shasum -a 256 report.txt` on macOS or `sha256sum report.txt` on Linux. This body attests a file that holds the word `hello` and a line break, with a note:

```sh
cat > body.json <<'EOF'
{ "sha256": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03",
  "note": "report v3 delivered to order 77" }
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`.

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/document, verify/quotes, check/links or fetch with urls covers at most 3 citations, document pairs, quotes or URLs; a larger batch is not run as a trial but answered with the normal 402 offer, `details.reason` trial_too_large.

## 4. Read the answer

```sh
jq '{sha256: .attestation.sha256, seq: .attestation.seq, issued_at: .attestation.issued_at, first_seen: .attestation.first_seen}' answer.json
```

| Field | What it means | What to do |
|---|---|---|
| `attestation.sha256` | The hash you sent, in lower case. | Check it is the hash of your file. |
| `attestation.issued_at` | When we signed the receipt. | Keep it: it is the time the hash is shown to have existed. |
| `attestation.seq` | Our running count of attestations. | Keep it with the receipt. |
| `attestation.note_sha256` | The SHA-256 of your note, or null if you sent none. | Compare it with the hash of the note you kept. |
| `attestation.first_seen` | `true` for a new record, `false` when an `idempotency_key` returned an earlier receipt. | Treat `false` as a repeat of the earlier call. |
| `receipt` | The signed receipt, a compact JWS. | Store it next to the file. |

## 5. Retry without a second record

If a call times out, you cannot tell whether it was recorded. Send an `idempotency_key` of 1 to 128 printable ASCII characters without spaces (for example `order-77-delivery`) on the first call. If that call times out, send the same request again with the same key:

```json
{ "sha256": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03",
  "note": "report v3 delivered to order 77", "idempotency_key": "order-77-delivery" }
```

The same key with the same hash and note returns the byte-identical receipt and `first_seen: false`. The same key with a different hash or note gets 409 `idempotency_conflict`; the payment is checked first and then released, so you are not charged. Keys belong to the paying address, so two payers can use the same key. They need an x402 payment: a trial call, an L402 payment or a credit key gets 400 if it sends one. We keep only the SHA-256 of the key.

## 6. Check the file later

Hash the file again and compare it with the receipt. Check the receipt 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, sha256: .payload.result.sha256}'
```

`valid: true` with a `sha256` equal to the hash of the file you hold now means the file is unchanged since `issued_at`. If the hashes differ, the file is not the one that was attested. You can also check the receipt offline: [Verify receipts offline](/docs/verify-offline).

## With MCP

The same call is the MCP tool `attestpage_attest`, 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_attest",
    "arguments": { "sha256": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", "note": "report v3 delivered to order 77" },
    "_meta": { "attestpage/trial": false } } }
EOF
```

```json
{
  "scheme": "exact",
  "network": "eip155:8453",
  "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 it cost to attest a hash?

US$0.002 per call with `POST /v1/attest`, whatever the file size, because only the hash is sent.

### Do you see or keep my file?

No. We receive 64 hex characters. A `note` and an `idempotency_key` are kept only as SHA-256 hashes.

### Does the receipt show who wrote the file?

No. It shows that this hash was presented to us at `issued_at`. It does not name an author and does not say the contents are true. For signed evidence of what a web page said, use [a signed receipt of a web page](/guides/signed-page-receipt).

### What if I retry after a timeout?

Send the same request again with the same `idempotency_key`: you get the earlier receipt and no second record. The retry is a new payment unless you resend it with the same payment-identifier ([Payments](/docs/payments)). Without a key, each call is a new record and is charged.
