# How an agent proves its own output existed.

A loop for autonomous timestamping: hash the bytes, pay the 402, keep the proof, and find out when Bitcoin confirms it. No account, no API key, no human in the loop — and nothing about your output leaves the agent except its fingerprint.

## What the agent needs

- **The exact bytes** it is about to hand on: a file, a JSON body exactly as serialised, a model response. Not a parsed object that will be re-serialised later.
- **SHA-256**, which every language has: `sha256sum`, `crypto.subtle.digest`, `createHash`, `hashlib`.
- **A wallet** holding a little USDC on Base. Gas is paid by the facilitator, so the wallet needs no ETH.
- **Somewhere to write** the `.ots` proof and one line of metadata: hash, stamp id, time, and what the file was.
- **A way to learn the proof completed**: a webhook if it can receive one, otherwise the status URL, or a verification call at read time.

## The loop, in six steps

1. **Hash the output** before anything else touches it. Store the hash with the artifact, not in a comment inside it.
2. **Ask the price.** `POST /v1/stamps` without a payment returns `402` with the amount for that exact request in the body and in the `PAYMENT-REQUIRED` header. Asking is free.
3. **Pay.** Sign an EIP-3009 `TransferWithAuthorization` for that amount and repeat the request with `PAYMENT-SIGNATURE`. Any x402 client does this in a few lines.
4. **Stamp.** The `200` carries one `.ots` proof per hash (base64), a status URL per hash, and a signed receipt for the batch. Write them down before doing anything else.
5. **Keep the proof** next to the artifact. A pending proof is already valid; it names the calendars that will commit it to Bitcoin.
6. **Complete it.** When Bitcoin confirms, the status turns `complete`, the webhook fires, and downloading `ots_url` again returns the proof with the Bitcoin attestation inside. A `POST /v1/verify` will also finish a pending proof for you.

## The requests

The whole protocol is one endpoint, one price, one proof. A probe, without payment:

```
$ curl -s -i -X POST https://stampthisfile.com/v1/stamps \
    -H 'content-type: application/json' \
    -d '{"hashes":["9f2c…"]}'

HTTP/2 402
payment-required: eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2Ui…

{"x402Version":2,
 "error":"Payment required",
 "accepts":[{"scheme":"exact","network":"eip155:8453",
             "asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
             "amount":"20000","payTo":"0x…","maxTimeoutSeconds":300,
             "extra":{"name":"USD Coin","version":"2"}}],
 "extensions":{"bazaar":{…}}}
```

`amount` is in atomic units: `20000` is 0.02 USDC at six decimals. The same request with `PAYMENT-SIGNATURE: <base64 PaymentPayload>` returns `200`, the proofs, and a `PAYMENT-RESPONSE` header with the transaction. Retrying with the same payment and the same body returns the same proofs and charges nothing more; the same payment with a different body is refused.

## Paying from code

Use an x402 client rather than hand-rolling the signature. `wrapFetchWithPayment` from `x402-fetch` (or the official `@x402/fetch`) wraps `fetch` and handles the 402, the EIP-712 signature and the retry:

```
import { createHash } from 'node:crypto'
import { writeFile } from 'node:fs/promises'
import { privateKeyToAccount } from 'viem/accounts'
import { wrapFetchWithPayment } from 'x402-fetch'

const BASE = 'https://stampthisfile.com'
const account = privateKeyToAccount(process.env.AGENT_WALLET_KEY as `0x${string}`)
const pay = wrapFetchWithPayment(fetch, account)
const sha256 = (b: Uint8Array) => createHash('sha256').update(b).digest('hex')

const output = new TextEncoder().encode(JSON.stringify(report))
const res = await pay(`${BASE}/v1/stamps`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ hashes: [sha256(output)] }),
})
if (!res.ok) throw new Error(`stamp failed: ${res.status} ${await res.text()}`)

const { batch_id, stamps, receipt } = await res.json()
await writeFile(`runs/${batch_id}.ots`, Buffer.from(stamps[0].ots, 'base64'))
await writeFile(`runs/${batch_id}.json`, JSON.stringify({ hash: stamps[0].hash, receipt }, null, 2))
```

A batch is the same call with more hashes: up to 100 per request, one payment, one receipt, one proof per hash. That is the cheapest shape for an agent that writes many outputs: hash them all, send them together, keep the batch id.

## MCP, if your agent speaks it

`POST /mcp` is a stateless MCP server (Streamable HTTP, no key) with four tools: `stamp_hashes` (paid), `get_stamp`, `get_proof`, `verify_proof`. It follows the x402 MCP transport: without a payment, `stamp_hashes` returns a tool error whose `structuredContent` is the PaymentRequired; retry with the payment payload in `_meta["x402/payment"]`, and the settlement comes back in `_meta["x402/payment-response"]`.

```
$ claude mcp add --transport http stampthisfile https://stampthisfile.com/mcp
```

That is the shortest path for Claude Code and anything else that can attach an HTTP MCP server: the agent gets the tools, reads the price from the error, and pays with whatever wallet its x402 transport is configured with.

## Or install the skill

The [stampthisfile/skills](https://github.com/stampthisfile/skills) repository is an agent skill with a script that hashes, pays, stamps, upgrades and verifies in one go, plus the instructions an agent needs to use it:

```
$ npx skills add stampthisfile/skills
```

Read [llms.txt](https://stampthisfile.com/llms.txt) for the full request and response shapes, the webhook format and the error codes. It is written to be read by a model, not skimmed by a person.

## What to store, and why

| Keep | Because |
| --- | --- |
| The artifact, unchanged | The proof is about these bytes. Keep them read-only; a re-save changes the hash. |
| The `.ots` proof | The proof is the evidence. The receipt is not a substitute and does not contain it. |
| The stamp id and status URL | Lets you re-download the proof, or fetch the Bitcoin height and block time later. |
| The signed receipt | Shows the service received that hash at that time, paid by that address. Signature verifiable against `/.well-known/stampthisfile-keys`. |
| The batch id and manifest | Ties a set of outputs to one commitment; makes a whole run provable with one proof. |
| The canonicalisation recipe | If you hash a normalised form (sorted keys, stripped metadata), record how, or nobody can reproduce the hash. |

## Six ways agents get this wrong

- **Stamping a re-serialised file.** `JSON.stringify` of a parsed object, a zip instead of its contents, a file re-saved by an editor: the hash changes and the proof no longer matches. Hash the bytes you actually ship.
- **Treating pending as complete.** A pending proof is valid but still depends on the calendars. Download it again after Bitcoin confirms, or let `/v1/verify` upgrade it, before you archive it as final.
- **Losing the proof.** Storing the stamp id and calling `get_proof` later works only while the service does. Store the `.ots` file itself, in the same place as the artifact.
- **Paying twice on a retry.** A retry with the *same* payment and body is idempotent. A new payment for the same body is a new charge. Persist the payment payload until you know the request landed.
- **Hashing a digest of a digest.** `sha256(sha256(file))`, or a Merkle root you built yourself, is fine, but say so: the proof is about the outer hash and a verifier has to know the recipe.
- **Leaking the wallet.** The key that pays is a wallet with money in it. It belongs in a secret store, never in a prompt, a log line or a tool result.

## Verify on read

The cheapest integrity check is the one you run when you use the artifact. Hash it again, send the hash and the stored proof, and let the answer decide whether the pipeline is allowed to proceed:

```
const hash = sha256(await readFile(artifact))
const out = await fetch(`${BASE}/v1/verify`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ hash, ots: (await readFile(proof)).toString('base64') }),
}).then((r) => r.json())

if (out.status === 'invalid') throw new Error(`integrity check failed: ${out.reason}`)
if (out.status === 'complete') {
  if (out.ots) await writeFile(proof, Buffer.from(out.ots, 'base64')) // it upgraded a pending proof
  console.log(`existed by block ${out.bitcoin.height} (${out.bitcoin.block_time})`)
}
```

The call is free and stores nothing. It returns `complete` with the block height and time, `pending` with the calendars still working, or `invalid` with a reason: the three answers a pipeline actually needs.

## Cost control

At $0.05 a hash, 100 outputs cost $5.00 and 10,000 cost $500.00. Two habits keep it small: stamp a manifest of many outputs rather than each file separately when the set is what matters, and cap the price the agent will accept in its x402 client so a misconfigured endpoint cannot drain the wallet. A batch of 100 hashes settles in one payment, so there is no per-call overhead beyond the hashes themselves.

## Questions

### Can an agent pay without a human?

Yes. There is no account and no API key: the agent holds a wallet with USDC and signs an EIP-3009 authorization per request. Gas is paid by the facilitator, so the wallet needs no ETH. If your policy forbids hot wallets, use a subscription API with a key a human provisions, and accept that the key is now the agent’s to leak.

### What happens if the calendars are down?

If fewer than two of the four calendars answer, the request returns `503` and nothing is charged. There is no half-stamped state to clean up: either the batch was submitted and you paid, or you did not.

### Does the agent need to keep the output?

Yes. The proof is about the hash, and the hash is only meaningful next to the bytes. Keep the artifact and the `.ots` together, and keep the artifact read-only: editing it, or even re-saving it, breaks the pair.

### How does the agent know the proof completed?

Pass a `webhook` with the stamp request and you get one signed POST per batch when it completes or fails. Otherwise poll `GET /v1/stamps/{id}`, or call `/v1/verify` at read time, which completes a pending proof and returns the upgraded `.ots` when the calendars have caught up.

### Is the signed receipt enough on its own?

No. The receipt is our statement that we received the hash at a time, signed with our key; it depends on trusting us. The `.ots` proof is the part that stands alone, because it is checked against Bitcoin rather than against our signature.

_Checked October 2026. HTML: https://stampthisfile.com/guides/agents · Agent docs: https://stampthisfile.com/llms.txt_
