How To · Agent setup · Machine: index.json
How to set up an agent that can shop here
Concrete requirements and patterns so an autonomous agent can discover skills, evaluate free outlines, handle HTTP 402, settle USDC on Base, and unlock sealed packs — without a human UI.
/api/pay only.
Full sequence: Agent shopping flow ·
Docs source: AGENT-PURCHASE.md ·
Machine index: /how-to/index.json
Required capabilities
| Capability | Why |
|---|---|
| HTTP client (GET/POST, custom headers) | Shop, challenge, unlock, proof |
| Handle non-2xx (especially 402) | Challenge is not an error — it is the quote |
| Parse JSON body + optional base64 payment headers | x402Version, maxAmountRequired, payTo |
| Sign/send ERC-20 USDC transfer on Base (8453) | Exact atomic amount to payTo from the live challenge |
POST JSON + X-PAYMENT header | Unlock sealed pack after payment |
Persist sealed files + txHash | Idempotent re-download without re-paying |
Without wallet settlement, an agent can still discover and evaluate free outlines — but cannot unlock sealed packs.
Recommended patterns
- Thin tool-using agent — LLM plans steps; tools do HTTP + wallet
- Deterministic shopper — fixed script: canary first, then budget filter
- LVL JS SDK —
/sdk/agent-shop.mjs - Python helper —
/sdk/python/lvl_shop.py - MCP / A2A when you already run those stacks (see discovery below)
Recommended discovery path
Fetch in this order when bootstrapping an agent:
/llms.txt— curated map (includes How To links)/how-to/index.json— this guide set (machine summary)/api/shop—next_action, payment contract, featured/catalog.json— full inventory (authoritative skill_count)/agent.json· A2A card · MCP as needed
| Surface | URL | Use |
|---|---|---|
| llms.txt | /llms.txt | Agent-oriented site map |
| How-to index | /how-to/index.json | Guides + canary + sequence |
| Shop entry | /api/shop | next_action, featured, proof, payment contract |
| Catalog | /catalog.json | Full inventory (authoritative count) |
| Search | /api/catalog | Query / filter skills |
| Agent card | /agent.json | Discovery + endpoints |
| A2A card | /.well-known/agent-card.json | A2A-compatible |
| MCP | /api/mcp | Tool surface when enabled |
| Fleet | /fleet.json | Sisters, health paths, honesty notes |
Free evaluation (always before pay)
curl -sS "https://lvlltd.com/skills/agent-x402-first-buy/outline.json" | head -c 800 curl -sS "https://lvlltd.com/skills/agent-x402-first-buy/sample.md" | head -n 40
If outline is empty/template, skip or buy only after human review. Prefer skills tagged Deep / Execution Verified.
template-outline. Prefer free evaluation only, or pick another id.
How to read a 402 challenge correctly
GET challenge returns HTTP 402 with JSON body. Read both top-level fields and accepts[0].
payTo, amounts, or asset contracts from old docs or screenshots.
Always parse the live challenge body for that skill.
curl -si "https://lvlltd.com/api/pay?skill=agent-x402-first-buy" | head -n 60
| Field | Meaning |
|---|---|
x402Version / x402_version | Protocol version on body (apex skills use 2) |
maxAmountRequired / amount | Atomic USDC (6 decimals). $0.05 → 50000 |
payTo | Receiver (treasury on Base) |
assetContract | USDC on Base 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
network / chain_id | base / 8453 |
skill / skill_id | Must match unlock POST |
outline / sample | Free eval URLs |
PROOF_REQUIRED.
Settlement + unlock + verification
How to settle payment on Base
- Use chain id 8453 (Base mainnet)
- ERC-20 transfer of exactly
maxAmountRequiredatomic units of USDC topayTo - Wait for inclusion (server multi-RPC polls; client can wait 2–5s after broadcast)
- Keep
txHashfor unlock and re-download
USDbC is also accepted on some challenges — prefer native USDC when possible.
How to unlock the sealed pack
curl -sS -X POST "https://lvlltd.com/api/pay" \
-H "Content-Type: application/json" \
-H 'X-PAYMENT: {"txHash":"0xYOUR_TX","skill":"agent-x402-first-buy"}' \
-d '{"txHash":"0xYOUR_TX","skill":"agent-x402-first-buy"}'
Success: ok: true and sealed_pack.files (map of path → text). Persist files + txHash.
How to verify on /api/proof
curl -sS "https://lvlltd.com/api/proof?limit=10" | python3 -m json.tool | head -80
Only confirmed unlocks appear. Low or zero counts are valid. Do not invent GMV. Swarm smoke pointers are not ledger unlocks.
Recover after paid-but-failed unlock
curl -sS -X POST "https://lvlltd.com/api/recover" \
-H "Content-Type: application/json" \
-H 'X-PAYMENT: {"txHash":"0xYOUR_TX","skill":"agent-x402-first-buy"}' \
-d '{"txHash":"0xYOUR_TX","skill":"agent-x402-first-buy"}'
Same body as unlock. Idempotent — does not invent refunds; only re-verifies and re-delivers.
Common errors and how agents should handle them
| HTTP / code | Meaning | Agent action |
|---|---|---|
| 402 PAYMENT_REQUIRED | Challenge (GET) or missing proof (POST) | Parse challenge or attach txHash |
| 402 PAYMENT_VERIFICATION_FAILED | Bad amount/payTo/tx/network | Check BaseScan; wait; retry verify |
| 400 SKILL_REQUIRED | Missing skill id | Fix request |
| 403 | Not for sale / invalid license | Stop; pick another skill |
| 503 | Payments disabled / outage | Backoff; check /api/health |
Paid but no files: POST /api/recover with same txHash + skill
(same as unlock; idempotent). Human: /recover/
maxAmountRequired from the challenge (e.g. 50000 = $0.05).Idempotent re-download
Re-POST the same verified txHash + skill to re-fetch sealed files.
Does not move funds again. Store license token if returned for portable redeem.
Minimal Node example
// Pseudocode — wire your wallet for the transfer step
const BASE = "https://lvlltd.com";
const skill = "agent-x402-first-buy";
const shop = await fetch(`${BASE}/api/shop`).then((r) => r.json());
// prefer shop.next_action.for_agents
const outline = await fetch(`${BASE}/skills/${skill}/outline.json`).then((r) => r.json());
const chRes = await fetch(`${BASE}/api/pay?skill=${skill}`);
// expect 402
const challenge = await chRes.json();
const amount = challenge.maxAmountRequired || challenge.accepts?.[0]?.maxAmountRequired;
const payTo = challenge.payTo || challenge.accepts?.[0]?.payTo;
// wallet.transferUsdc({ to: payTo, amount, chainId: 8453 }) → txHash
const unlock = await fetch(`${BASE}/api/pay`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-PAYMENT": JSON.stringify({ txHash, skill }),
},
body: JSON.stringify({ txHash, skill }),
}).then((r) => r.json());
if (!unlock.ok) throw new Error(unlock.error_code || unlock.error);
// write unlock.sealed_pack.files to disk
Minimal Python example
import json, urllib.request
BASE = "https://lvlltd.com"
skill = "agent-x402-first-buy"
def get(url):
with urllib.request.urlopen(url) as r:
return json.loads(r.read().decode())
shop = get(f"{BASE}/api/shop")
outline = get(f"{BASE}/skills/{skill}/outline.json")
req = urllib.request.Request(f"{BASE}/api/pay?skill={skill}")
try:
urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
challenge = json.loads(e.read().decode())
# challenge["maxAmountRequired"], challenge["payTo"]
# settle with your web3 stack → tx_hash
# unlock:
# body = json.dumps({"txHash": tx_hash, "skill": skill}).encode()
# req = urllib.request.Request(f"{BASE}/api/pay", data=body, method="POST",
# headers={"Content-Type":"application/json",
# "X-PAYMENT": json.dumps({"txHash": tx_hash, "skill": skill})})
SDK one-liner
import { LvlAgentShop } from "https://lvlltd.com/sdk/agent-shop.mjs";
const shop = new LvlAgentShop();
const challenge = await shop.challenge("agent-x402-first-buy");
// transfer challenge.maxAmountRequired to challenge.payTo on Base
const unlock = await shop.unlock("agent-x402-first-buy", txHash);