エージェント型言語モデルの時代にヘッドレスコマースを整える
買い手はエージェントになりつつある。人が介在しなくても大規模言語モデルが読め、署名でき、支払えるようにするために必要なことと、Hanzoが今日そのために出しているもの。

このページの内容
ヘッドレスコマースは店頭をエンジンから切り離し、どのフロントエンドからでも一つの API で売れるようにした。そのフロントエンドは、いつも人が見るものだった。サイトであり、アプリであり、キオスクだった。
次のフロントエンドは見ない。読む。買い手はエージェントになりつつある。目標と予算とツールのループを持つプログラムで、プランや GPU や決済 API を探しに送り出される。理解でき、人に頼らず支払える相手から買う。ページを読む、クッキーの告知を閉じる、確認メールを開く、カード番号を打つ、そのどれかに人が要る事業なら、エージェントは次の結果を取る。
それが論点だ。事業は、人が介在しなくても LLM が読んで取引できるものでなければならない。 読めるとは、何を売り、いくらで、どう始めるかをエージェントが知れることだ。取引できるとは、API だけで始め、払い、品物を受け取れることだ。
そのために要ることを、一点ずつ、Hanzo が今日出しているものと合わせて書く。下のどのアドレスもアカウントなしで答える。主張は自分で確かめられる。
すべてのページを Markdown で出す
エージェントは読むトークンごとに対価を払う。hanzo.ai ではページのアドレスに .md を足すと、そのページが Markdown で返る。hanzo.ai/pricing.md、hanzo.ai/about.md。各ページは head のなかで <link rel="alternate" type="text/markdown"> により対になるページを名指しするので、エージェントは推測しなくてよい。
10月26日、私たちのエージェントのページを両方の形で取った。HTML で 528,810 バイト、Markdown で 17,465 バイト。それからサイトマップの12個に1つの URL、829 のうち 70 を取り、その対を求めた。70 のすべてが 200 で text/markdown として返った。
エージェントが起点にできる索引を出す
あなたを知らないエージェントには、どこへでも続くアドレスが一つ要る。私たちは三つ出している。
-
hanzo.ai/llms.txt はサイトのプレーンテキストの地図だ。Hanzo が何かを述べ、それから「For agents」の節で鍵を得る手順をそのまま示し、ページを一行ずつ並べる。docs.hanzo.ai/llms.txt はドキュメントについて同じことをする。
-
api.hanzo.ai/v1 は API が提供する能力をすべて列挙し、鍵は要らない。api.hanzo.ai/v1/openapi.json には各操作とそのスキーマがある。販売については、索引に
commerceという能力がある。チェックアウト、サブスクリプション、請求書、支払いのウェブフック、店頭のカタログだ。 -
hanzo.ai/.well-known/api-catalog はクライアントが最初に見る標準の場所(RFC 9727)だ。OpenAPI 文書とドキュメントとステータスページを指す。
クローラを意図して迎える
robots.txt は、どのクローラも最初に求めるファイルだ。私たちのもの は十二の AI クローラを一つずつ名指しし、そのなかに GPTBot、ClaudeBot、Claude-User、PerplexityBot、Google-Extended があり、それらを許可する。例外は他の誰とも同じだ。サインイン、アカウント、支払い、そして API のパスである。
AI クローラを拒むのは正当な選択だ。デフォルトがそう言ったから拒むのは、選択ですらない。
価格をデータにする
エージェントは比べる。画像のなかの価格や、「お問い合わせ」ボタンの向こうの価格は比べられない。api.hanzo.ai/v1/billing/plans は鍵なしで JSON を返す。Free は 0、Pro は月 2000、Team は席あたり 2500 で、各プランの上限が並ぶ。金額の単位はセントだ。
ウォレットに登録させる
エージェントに受信箱も電話もカードもない。鍵の対がある。そこで hanzo.id はウォレットで入る。ウォレットのアドレスに対する一度きりの nonce を求め、それが示すメッセージに署名し、署名を送り、戻ってきたコードをトークンと引き換える。
Hanzo が見たことのないウォレットは、自分のアカウントと自分の組織を得る。ウォレットが管理者で、プランは Free だ。トークンは七日もつ。メールを開く人も、端末を承認する人もいない。
一度の呼び出しで鍵を、試すための小さな枠を
そのトークンで POST /v1/account/keys は、トークンより長生きする sk-live- の鍵を返す。保管すること。ドキュメントは、一度しか表示されないと書いている。
Free プランは zen-free で一日 50 リクエスト、カードは要らない。応答のたびに残りが x-ratelimit-remaining に入る。最初の呼び出しは 200 で、残りは 49 だった。
人を介さず受け取る。API 越しのステーブルコイン
POST /v1/billing/crypto/deposit はチェーンとトークンと金額を受け取り、24 時間有効な入金アドレスを返す。GET /v1/billing/crypto/options は受け付けるものを列挙する。チェーンは Base、Ethereum、Solana、トークンは USDC と USDT だ。
私はアドレスで止まり、お金は送らなかった。チェーンが確定したときに残高へ入金される、というのはドキュメントの記述であり、私が実行したことではない。プランとサブスクリプションは、ブラウザでのカード決済のままである。エージェントが私たちに要るのは残高で、残高には API がある。
流れの全体を、一つのファイルに
これが hanzo.ai/agents#headless で、短縮してある。各行はそのページが印字するとおりだ。二つ目のヘッダコメントと、オプションの呼び出しと、鍵の接頭辞を印字する行は省いた。Node 22.18 以降と npm i viem が要り、そのあと node headless.mts を実行する。このブロックを、新しいウォレットで本番に対して走らせた。トークン、鍵、無料の呼び出し、USDC の入金アドレスが返った。
// No human required: sign in with a wallet, get a key, call the API, top up in USDC.
// npm i viem · node headless.mts (Node 22.18+; set HANZO_WALLET_KEY to use your own wallet)
import { createHash, randomBytes } from 'node:crypto'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
const ID = 'https://hanzo.id'
const API = 'https://api.hanzo.ai'
const CLIENT = 'hanzo-ai'
const REDIRECT = 'http://localhost:3000/callback'
const account = privateKeyToAccount((process.env.HANZO_WALLET_KEY as `0x${string}`) ?? generatePrivateKey())
const json = async (r: Response) => {
const body = await r.json()
if (!r.ok || body?.status === 'error') throw new Error(`${r.url} ${r.status} ${JSON.stringify(body)}`)
return body
}
// 1. A one-time challenge for this wallet.
const { data: c } = await json(await fetch(`${ID}/v1/iam/web3/nonce?chain=evm&address=${account.address}`))
// 2. The CAIP-122 message the challenge describes, signed with EIP-191 personal_sign.
const message = [
`${c.domain} wants you to sign in with your Ethereum account:`,
account.address,
'',
...(c.statement ? [c.statement, ''] : []),
`URI: ${c.uri}`,
`Version: ${c.version}`,
`Nonce: ${c.nonce}`,
`Issued At: ${c.issuedAt}`,
...(c.expirationTime ? [`Expiration Time: ${c.expirationTime}`] : []),
].join('\n')
const signature = await account.signMessage({ message })
// 3. Verify: a first-seen wallet gets an account and an organization of its own.
const verifier = randomBytes(32).toString('base64url')
const challenge = createHash('sha256').update(verifier).digest('base64url')
const { data: code } = await json(
await fetch(`${ID}/v1/iam/web3/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: CLIENT, chain: 'evm', scheme: 'secp256k1-eip191', address: account.address, message, signature,
type: 'code', redirectUri: REDIRECT, scope: 'openid profile email', codeChallenge: challenge, codeChallengeMethod: 'S256',
}),
}),
)
// 4. The authorization code for a token.
const token = await json(
await fetch(`${ID}/v1/iam/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ grant_type: 'authorization_code', client_id: CLIENT, code, code_verifier: verifier, redirect_uri: REDIRECT }),
}),
)
const bearer = { Authorization: `Bearer ${token.access_token}`, 'Content-Type': 'application/json' }
// 5. A key that outlives the token. Shown once: keep it in your secret store.
const minted = await json(await fetch(`${API}/v1/account/keys`, { method: 'POST', headers: bearer, body: JSON.stringify({ name: 'agent', type: 'secret' }) }))
const key = { Authorization: `Bearer ${minted.key}`, 'Content-Type': 'application/json' }
// 6. A first call, free.
const chat = await fetch(`${API}/v1/chat/completions`, {
method: 'POST', headers: key,
body: JSON.stringify({ model: 'zen-free', messages: [{ role: 'user', content: 'Reply with the single word: ready' }] }),
})
console.log('chat', chat.status, chat.headers.get('x-hanzo-served'), 'free left today:', chat.headers.get('x-ratelimit-remaining'))
console.log((await chat.json()).choices?.[0]?.message?.content)
// 7. Top up in USDC: ask for a deposit address, then send to exactly that address.
console.log(await json(await fetch(`${API}/v1/billing/crypto/deposit`, { method: 'POST', headers: key, body: JSON.stringify({ chain: 'base', token: 'usdc', amountCents: 500 }) })))
x402 は来る。まだここにはない
チャージの先で、エージェントに合う形はリクエストごとの支払いだ。サーバは価格とともに 402 Payment Required を返し、クライアントは払ってからやり直す。それが x402 だ。私たちの API では動いていない。その形で価格を示す Hanzo のエンドポイントはまだないので、あるとは言わない。それまでは、API 越しにチャージした残高が、エージェントが私たちへ払う方法だ。
クローラのエラーがゼロであることがなぜ要るか
このどれも、音を立てては失敗しない。サイトマップのリンクから 404 に着いたエージェントはチケットを切らない。次の結果を取る。クローラのエラーは、店が壊れたレジを扱うように扱え。
同じ 70 ページの標本を、ClaudeBot の user-agent 文字列で取り、もう一度 Googlebot ので取った。どれも両方とも 200 だった。標本であって保証ではない。user-agent 文字列はクローラ自身のアドレスではないので、本物のクローラが見るものを見せることはできない。自分のサイトでも同じことをするといい。招いているエージェントでサイトマップを取り、200 でない応答を数える。
私たちは以前にもここにいた
この会社は 2014 年、Crowdstart として始めた。2016 年に Hanzo と改めた。最初の顧客 SKULLY は、Indiegogo で約 45 時間のうちに $1 million を集め、TechCrunch はそれをIndiegogo で最も早く満額に達したキャンペーンと呼んだ。私に言わせれば、Hanzo は最初から AI を前提にした商いだった。まず自前の分析があり、それからプラットフォーム上の企業のデータで学ぶモデルがあった。2017 年に Techstars へ入った(その年の6月の記事)。作っていると言っていたのは、知的な商いだった。
2020 年、Damon Motorcycles は CES で発表し、キャンペーンは Hanzo が回していた。30 日で、広告費 $15,532 に対しプラットフォームは $8.4M のバイクを売り、541:1 の回収だった。オーディエンスは顧客データから 12 時間ごとに組み直され、AI が書いた広告セットは 36 時間以内に Facebook で Above Average と評価された。残りは事例にある。
あれは売る側の機械で、買った相手を見つけ、広告を書いていた。買う側が次だ。同じループが逆の端から回り始め、事業は機械に読まれて払われるか、機械に通り過ぎられる。
エージェントを hanzo.ai/agents に向けるとよい。
続きを読む

Hanzo Payments: Unified Payment Processing for Global Commerce
Launching Hanzo Payments — unified payment processing with intelligent routing, fraud detection, and multi-currency support.

Introducing the Hanzo Platform
Crowdstart evolves into Hanzo: a complete commerce platform for the modern era.

ルーターがレジストリである
Hanzo Cloud にチェックインされた OpenAPI ファイルはない。生きているルート表がソースであり、三通りに射影され、どの射影がずれても全単射のテストがビルドを落とす。963 パスにわたる 1,362 操作が、リクエスト時にプロセスごとに生成される。
