Rendre le commerce headless lisible à l'ère des LLM agentiques
Les acheteurs deviennent des agents. Ce qu'il faut pour être lisible, signable et payable par un LLM sans humain dans la boucle, et ce que Hanzo livre aujourd'hui.

Sur cette page
Le commerce headless a séparé la vitrine du moteur, afin que n'importe quel frontal puisse vendre à travers une seule API. Ce frontal a toujours été quelque chose qu'une personne regardait : un site, une application, une borne.
Le prochain frontal ne regarde pas. Il lit. L'acheteur est en train de devenir un agent : un programme avec un objectif, un budget et une boucle d'outils, envoyé chercher une offre, un GPU ou une API de paiement. Il achète auprès de celui qu'il peut comprendre et payer sans demander d'aide à un humain. Si votre activité exige qu'une personne lise une page, ferme une bannière de cookies, ouvre un e-mail de confirmation ou saisisse un numéro de carte, l'agent prend le résultat suivant.
Telle est la thèse. Une entreprise doit être lisible et transactionnelle pour un LLM, sans humain dans la boucle. Lisible veut dire que l'agent peut apprendre ce que vous vendez, ce que cela coûte et comment commencer. Transactionnelle veut dire qu'il peut commencer, payer et recevoir la chose par une API, seul.
Voici ce que cela demande, point par point, avec ce que Hanzo livre aujourd'hui. Chaque adresse ci-dessous répond sans compte, vous pouvez donc vérifier chaque affirmation vous-même.
Servir chaque page en Markdown
Un agent paie chaque jeton qu'il lit. Sur hanzo.ai, ajoutez .md à l'adresse d'une page et vous obtenez la page en Markdown : hanzo.ai/pricing.md, hanzo.ai/about.md. Chaque page nomme sa jumelle dans son en-tête avec <link rel="alternate" type="text/markdown">, pour qu'un agent n'ait pas à deviner.
Le 26 octobre, j'ai récupéré notre page agents des deux façons : 528,810 octets en HTML, 17,465 en Markdown. J'ai ensuite pris une URL sur douze dans notre sitemap, 70 sur 829, et demandé sa jumelle. Les 70 ont répondu 200 en text/markdown.
Publier un index d'où un agent peut partir
Un agent qui n'a jamais entendu parler de vous a besoin d'une adresse qui mène partout. Nous en publions trois.
- hanzo.ai/llms.txt est une carte du site en texte brut. Il dit ce qu'est Hanzo, puis donne une section « For agents » avec les étapes exactes pour obtenir une clé, puis liste les pages, une ligne chacune. docs.hanzo.ai/llms.txt fait de même pour la documentation.
- api.hanzo.ai/v1 énumère chaque capacité que l'API sert, sans clé, et api.hanzo.ai/v1/openapi.json contient chaque opération avec son schéma. Pour la vente, l'index inclut une capacité
commerce: paiement, abonnements, factures, webhooks de paiement et le catalogue de la vitrine. - hanzo.ai/.well-known/api-catalog est l'endroit standard (RFC 9727) qu'un client consulte en premier. Il pointe vers le document OpenAPI, la documentation et la page de statut.
Accueillir les crawlers exprès
robots.txt est le premier fichier que demande n'importe quel crawler. Le nôtre nomme douze crawlers d'IA un par un, parmi eux GPTBot, ClaudeBot, Claude-User, PerplexityBot et Google-Extended, et les autorise. Ils ont les mêmes exceptions que tous les autres : connexion, compte, paiement et les chemins de l'API.
Bloquer les crawlers d'IA est un choix légitime. Les bloquer parce qu'une valeur par défaut le disait n'est pas un choix du tout.
Faire du prix une donnée
Les agents comparent. Un prix à l'intérieur d'une image, ou derrière un bouton « contact us », ne peut pas être comparé. api.hanzo.ai/v1/billing/plans n'a pas besoin de clé et renvoie du JSON : Free à 0, Pro à 2000 par mois, Team à 2500 par siège, avec les limites de chaque offre à côté. Les montants sont en centimes.
Laisser un portefeuille s'inscrire
Un agent n'a ni boîte de réception, ni téléphone, ni carte. Il a une paire de clés. hanzo.id fait donc entrer un portefeuille : demander un nonce à usage unique pour l'adresse du portefeuille, signer le message qu'il décrit, poster la signature, et échanger le code reçu contre un jeton.
Un portefeuille que Hanzo n'a encore jamais vu reçoit son propre compte et sa propre organisation, avec le portefeuille comme administrateur, sur l'offre Free. Le jeton dure sept jours. Personne n'ouvre un e-mail ni n'approuve un appareil.
Un appel pour une clé, et une petite marge pour essayer
Avec le jeton, POST /v1/account/keys renvoie une clé sk-live- qui lui survit. Gardez-la ; la documentation dit qu'elle n'est montrée qu'une fois.
L'offre Free, c'est 50 requêtes par jour sur zen-free, sans carte, et chaque réponse indique ce qu'il reste dans x-ratelimit-remaining. Mon premier appel a répondu 200 avec 49 restants.
Encaisser sans humain : des stablecoins via l'API
POST /v1/billing/crypto/deposit prend une chaîne, un token et un montant, et renvoie une adresse de dépôt valable 24 heures. GET /v1/billing/crypto/options liste ce qu'il accepte : les chaînes Base, Ethereum et Solana, et les tokens USDC et USDT.
Je me suis arrêté à l'adresse et je n'ai envoyé aucun argent. Que le solde est crédité quand la chaîne confirme, c'est ce que dit notre documentation, pas quelque chose que j'ai exécuté. Les offres et les abonnements restent un paiement par carte dans un navigateur. Ce dont un agent a besoin chez nous, c'est d'un solde, et le solde a une API.
Tout le flux, dans un seul fichier
Voici hanzo.ai/agents#headless, abrégé. Chaque ligne est telle que cette page l'imprime ; j'ai laissé de côté le second commentaire d'en-tête, l'appel des options et la ligne qui imprime un préfixe de clé. Il vous faut Node 22.18 ou plus récent et npm i viem, puis node headless.mts. J'ai exécuté ce bloc contre la production avec un portefeuille neuf : un jeton, une clé, un appel gratuit et une adresse de dépôt 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 arrive, et n'est pas encore là
Au-delà d'une recharge, la bonne forme pour un agent est le paiement à la requête. Le serveur répond 402 Payment Required avec un prix, le client paie et réessaie. C'est x402. Ce n'est pas en service sur notre API : aucun point de terminaison Hanzo ne cite un prix de cette façon pour l'instant, je ne vais donc pas l'affirmer. En attendant, un solde rechargé via l'API est la façon dont un agent nous paie.
Pourquoi zéro erreur de crawler compte
Rien de tout cela n'échoue bruyamment. Un agent qui suit un lien de votre sitemap vers un 404 n'ouvre pas de ticket. Il prend le résultat suivant. Traitez une erreur de crawler comme une boutique traite une caisse en panne.
J'ai récupéré ces mêmes 70 pages de l'échantillon avec la chaîne user-agent de ClaudeBot, puis avec celle de Googlebot. Chacune a répondu 200 les deux fois. C'est un échantillon, pas une garantie, et une chaîne user-agent n'est pas l'adresse propre du crawler, je ne peux donc pas vous montrer ce que voient les vrais crawlers. Faites de même sur votre site : demandez votre sitemap avec les agents que vous invitez, et comptez les réponses qui ne sont pas 200.
Nous sommes déjà passés par là
J'ai créé cette entreprise en 2014, sous le nom de Crowdstart ; nous l'avons renommée Hanzo en 2016. Notre premier client, SKULLY, a levé $1 million sur Indiegogo en environ 45 heures, ce que TechCrunch a appelé la campagne Indiegogo entièrement financée la plus rapide. À mon sens, Hanzo faisait du commerce pensé pour l'IA dès le départ : d'abord notre propre analytique, puis des modèles qui apprenaient à partir des données des entreprises sur la plateforme. En 2017 nous avons rejoint Techstars (le billet de ce juin-là), et ce que nous disions construire, c'était le commerce intelligent.
En 2020, Damon Motorcycles s'est lancé au CES avec Hanzo aux commandes de la campagne. En 30 jours, la plateforme a vendu $8.4M de motos pour $15,532 de dépenses publicitaires, un retour de 541:1. Les audiences étaient reconstruites à partir de nos données clients toutes les 12 heures, et des ensembles d'annonces écrits par IA ont été notés Above Average sur Facebook en moins de 36 heures. L'étude de cas raconte le reste.
C'étaient des machines du côté de la vente, qui trouvaient les audiences qui achetaient et rédigeaient les annonces. Le côté achat vient ensuite. La même boucle est sur le point de tourner depuis l'autre bout, et votre activité sera lue et payée par une machine, ou bien écartée par une machine.
Pointez votre agent vers hanzo.ai/agents.
Lire la suite

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.

Le routeur est le registre
Hanzo Cloud n'a aucun fichier OpenAPI versionné. La table de routes vivante est la source, projetée de trois façons, et un test de bijection fait échouer la compilation si une projection dérive. 1,362 opérations sur 963 paths, générées par processus au moment de la requête.
