에이전트형 언어 모델 시대에 헤드리스 커머스를 맞추다
구매자는 에이전트가 되고 있다. 사람이 끼지 않아도 거대언어모델이 읽고, 서명하고, 결제할 수 있게 하려면 무엇이 필요한지, 그리고 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바이트였습니다. 이어서 사이트맵에서 열두 번째마다의 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입니다. 토큰은 7일 동안 유지됩니다. 메일을 여는 사람도, 기기를 승인하는 사람도 없습니다.
키를 위한 호출 한 번, 시험해 볼 작은 허용량
그 토큰으로 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 개 연산이, 요청 시각에 프로세스마다 만들어진다.
