AttestPage · Docs · Pricing · OpenAPI · llms.txt

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.

What the call does

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.

1. See an answer for free

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

curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/attest | jq '{sha256: .attestation.sha256, seq: .attestation.seq, first_seen: .attestation.first_seen, tier}'
{
  "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:

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:

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 shows this with plain fetch, and the MCP server 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

jq '{sha256: .attestation.sha256, seq: .attestation.seq, issued_at: .attestation.issued_at, first_seen: .attestation.first_seen}' answer.json
FieldWhat it meansWhat to do
attestation.sha256The hash you sent, in lower case.Check it is the hash of your file.
attestation.issued_atWhen we signed the receipt.Keep it: it is the time the hash is shown to have existed.
attestation.seqOur running count of attestations.Keep it with the receipt.
attestation.note_sha256The SHA-256 of your note, or null if you sent none.Compare it with the hash of the note you kept.
attestation.first_seentrue for a new record, false when an idempotency_key returned an earlier receipt.Treat false as a repeat of the earlier call.
receiptThe 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:

{ "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:

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.

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:

{
  "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_attest",
    "arguments": { "sha256": "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", "note": "report v3 delivered to order 77" },
    "_meta": { "attestpage/trial": false } } }
EOF
{
  "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.

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). Without a key, each call is a new record and is charged.