Mercury · Cited Batch
GET /buy/batch
What it does
List of URLs (≤20) → clean content for each + ONE signed receipt committing to a MERKLE ROOT over every page's contentHash. Tamper-evident multi-page snapshot with per-page membership proofs (selective disclosure). Deterministic (no LLM): same URL set + same source bytes ⇒ byte-identical root + proofs. SSRF-guarded per url; keyless x402, USDC on Base mainnet.
The goal it serves: give an agent provable web content — the bytes PLUS a portable, offline-verifiable proof of what/where/when — so RAG stores, research pipelines and downstream agents can cite evidence, not hearsay.
Schemas & output preview
Input schema — the exact request shape the route validates.
{
"type": "object",
"properties": {
"urls": {
"type": "string",
"maxLength": 65536,
"description": "comma- OR newline-separated list of pages to snapshot (http/https), ≤20 (deduped, capped). Each is fetched through the shared SSRF-guarded engine and becomes one Merkle leaf."
},
"format": {
"type": "string",
"enum": [
"text",
"markdown"
],
"description": "clean text (default) or structure-preserving markdown for every page"
}
},
"required": [
"urls"
],
"additionalProperties": false
}Output schema — the exact response shape the handler returns.
{
"type": "object",
"properties": {
"ok": {
"type": "boolean",
"description": "true on success; false on an honest failure (503-before-charge)"
},
"url": {
"type": "string",
"description": "synthetic batch id (mercury-batch:<merkleRoot>) — there is no single url"
},
"status": {
"type": "integer",
"description": "200 when at least one page resolved (the batch is deliverable)"
},
"text": {
"type": "string",
"description": "the canonical BATCH COMMITMENT string the attestation signs over: the Merkle root + counts + every (index,url,status,contentHash,leafHash). This is the proof, not the page bodies."
},
"fetchedAt": {
"type": "string",
"description": "ISO-8601 capture time (also folded into the signed receipt)"
},
"data": {
"type": "object",
"description": "the structured deliverable the buyer consumes",
"properties": {
"merkleRoot": {
"type": "string",
"description": "0x… sha256 Merkle root committing to ALL pages at once"
},
"leafCount": {
"type": "integer",
"description": "number of pages that resolved into a leaf (successful fetches)"
},
"requested": {
"type": "integer",
"description": "number of urls accepted into the batch (after dedupe/cap)"
},
"okCount": {
"type": "integer",
"description": "successful fetches (== leafCount)"
},
"failCount": {
"type": "integer",
"description": "fetches that returned an honest ok:false (excluded from the tree)"
},
"leafTag": {
"type": "string",
"description": "domain separator used for leaf hashing (verifier needs it)"
},
"nodeTag": {
"type": "string",
"description": "domain separator used for internal-node hashing (verifier needs it)"
},
"pages": {
"type": "array",
"description": "per-page result, in input order. Successful pages carry text + contentHash + a Merkle proof.",
"items": {
"type": "object",
"properties": {
"index": {
"type": "integer",
"description": "leaf index in the snapshot (part of the signed leaf)"
},
"url": {
"type": "string",
"description": "final URL after redirects (or the requested url on failure)"
},
"ok": {
"type": "boolean"
},
"status": {
"type": "integer",
"description": "upstream HTTP status"
},
"title": {
"type": "string",
"description": "page <title> (HTML pages only)"
},
"text": {
"type": "string",
"description": "cleaned page text/markdown (omitted on failure)"
},
"contentHash": {
"type": "string",
"description": "0x… sha256 of the page text (the leaf's content commitment)"
},
"leafHash": {
"type": "string",
"description": "0x… sha256 leaf node = H(leafTag|index|url|status|contentHash)"
},
"proof": {
"type": "array",
"description": "Merkle audit path: fold this leafHash with each {side,hash} to recompute merkleRoot. Proves THIS page was a member of the signed batch WITHOUT disclosing the other pages.",
"items": {
"type": "object",
"properties": {
"side": {
"type": "string",
"enum": [
"left",
"right"
]
},
"hash": {
"type": "string"
}
}
}
},
"bytes": {
"type": "integer",
"description": "raw upstream body size"
},
"contentType": {
"type": "string"
},
"redirects": {
"type": "array",
"items": {
"type": "string"
}
},
"error": {
"type": "string",
"description": "present only when this page's ok:false"
}
}
}
},
"dropped": {
"type": "array",
"items": {
"type": "string"
},
"description": "urls dropped for exceeding the ≤20 cap (honest: we tell you what we capped)"
}
},
"additionalProperties": false
},
"error": {
"type": "string",
"description": "present only when ok:false (e.g. no urls supplied, or every page failed)"
}
},
"required": [
"ok",
"url"
],
"additionalProperties": false
}Output preview — a real example response, shown free (you only pay when you call the route).
{
"ok": true,
"url": "mercury-batch:0x9f2c…",
"status": 200,
"text": "mercury-x402:cited-batch:v1\nmerkleRoot=0x9f2c…\nleafCount=2\nokCount=2\nfetchedAt=2026-06-04T00:00:00.000Z\nleafTag=mercury-x402:cited-batch:leaf:v1\nnodeTag=mercury-x402:cited-batch:node:v1\nleaf[0] url=https://example.com/ status=200 sha256=0xaa… leafHash=0xbb…\nleaf[1] url=https://www.iana.org/ status=200 sha256=0xcc… leafHash=0xdd…",
"fetchedAt": "2026-06-04T00:00:00.000Z",
"data": {
"merkleRoot": "0x9f2c…",
"leafCount": 2,
"requested": 2,
"okCount": 2,
"failCount": 0,
"leafTag": "mercury-x402:cited-batch:leaf:v1",
"nodeTag": "mercury-x402:cited-batch:node:v1",
"pages": [
{
"index": 0,
"url": "https://example.com/",
"ok": true,
"status": 200,
"title": "Example Domain",
"text": "Example Domain This domain is for use in illustrative examples in documents.",
"contentHash": "0xaa…",
"leafHash": "0xbb…",
"proof": [
{
"side": "right",
"hash": "0xdd…"
}
],
"bytes": 513,
"contentType": "text/html; charset=utf-8",
"redirects": []
},
{
"index": 1,
"url": "https://www.iana.org/",
"ok": true,
"status": 200,
"title": "Internet Assigned Numbers Authority",
"text": "Internet Assigned Numbers Authority …",
"contentHash": "0xcc…",
"leafHash": "0xdd…",
"proof": [
{
"side": "left",
"hash": "0xbb…"
}
],
"bytes": 4200,
"contentType": "text/html; charset=utf-8",
"redirects": []
}
],
"dropped": []
}
}Pay & call
Your agent calls the route; the 402 challenge carries the exact price ($0.02, USDC on Base mainnet); the x402 client settles via the CDP facilitator and retries. No key, no signup.
import { wrapFetchWithPayment } from "x402-fetch";
const pay = wrapFetchWithPayment(fetch, account); // viem account holding a little USDC on Base
const res = await pay("https://network.mercury-hq.com/buy/batch?urls=https://example.com,https://example.org");
const out = await res.json(); // the result + `attestation` (the signed receipt)Prepaid alternative — the same route accepts an API key:
# Same route, prepaid API-key rail (Bearer mk_live_…) — get a key at https://network.mercury-hq.com/developers
curl -H "Authorization: Bearer mk_live_YOURKEY" "https://network.mercury-hq.com/buy/batch?urls=https://example.com,https://example.org"Verify the receipt
Recover the EIP-191 signature over sha256(content)‖url‖status‖fetchedAt‖nonce and confirm the signer equals the pinned attestation key 0xACB40253BD71Bb9a5d491b2c6EFF755F2A33Fc75 (published at /.well-known/mercury-attestation). No callback to Mercury — the receipt verifies offline, forever. Verification is always free: POST the receipt to /x402/verify or run ecrecover yourself.
| Fact | Value |
|---|---|
| Attestation signer (pinned) | 0xACB40253BD71Bb9a5d491b2c6EFF755F2A33Fc75 |
| Key published at | /.well-known/mercury-attestation |
| Live verifier (free) | /x402/verify |
| Settlement | real USDC on Base mainnet (eip155:8453) via CDP — auditable on BaseScan |
Related
Verifiable Web Fetch
$0.003Cited Markdown
$0.005Cited Readability
$0.005More: all services · /catalog · the headline web-fetch · agent twin of this page: GET /university/docs/cited-batch?format=md