x402 —— 付费调用
其他所有接口都是向人收费。这一个是在完成工作的同一个请求里,向调用方收费。
一个 agent 调用某个端点,拿到 402 Payment Required 和一份机器可读的条款,
签署一份支付授权,带着 X-PAYMENT 头重试,动作随即执行 ——
没有发票,没有套餐,事先也不需要建立任何计费关系。
密钥依然是需要的,因为这个动作操作的是某个特定客户的身份;
见支付是计量,不是认证。
这就是 x402,一个开放协议。Incarna 是收款方。
网络 eip155:84532 (Base Sepolia)
资产 USDC
Facilitator https://x402.org/facilitator
收款地址 0xCa1fBb1900e1C17Cc443e34f312720960E72a83F
测试网。 价格以真实美元计价,但今天在 Base Sepolia 的 USDC 上结算。 主网是一个单独的决定,不是翻一个开关。
支付是计量,不是认证
值得直说,因为相反的假设是一个安全漏洞:
付钱不会让你拿到别人的账号。
一次身份动作操作的是某个特定客户的身份,所以付费路由仍然要解析出调用主体。 认证回答的是这个身份可不可以行动;支付回答的是谁为它买单。 两者彼此独立,而且缺一不可。
发现价格
curl $BASE/x402/tools
{
"enabled": true,
"network": "eip155:84532",
"tools": {
"identity.x.post": "$0.05",
"identity.email.send": "$0.02",
"identity.email.inbox": "$0.01",
"identity.github.act": "$0.05",
"identity.reddit.act": "$0.05"
}
}
不在这里列出的工具无法被收费 —— 正是这一点让一个新加的端点不会悄悄地以免费形式上线。
GET /x402/quote/{tool} 会返回某个工具的完整 402 文档,不需要发起真实请求,
这样付款方可以提前查看条款。
付费端点
| 端点 | 工具 | 价格 |
|---|---|---|
POST /x402/agents/{id}/accounts/{acct}/tweet |
identity.x.post |
$0.05 |
POST /x402/agents/{id}/email/send |
identity.email.send |
$0.02 |
POST /x402/agents/{id}/inbox |
identity.email.inbox |
$0.01 |
POST /x402/agents/{id}/accounts/{acct}/github |
identity.github.act |
$0.05 |
POST /x402/agents/{id}/accounts/{acct}/reddit |
identity.reddit.act |
$0.05 |
请求体与 REST 参考中对应的免费端点完全一致。
一次完整交换
1 —— 不带支付调用
curl -X POST $BASE/x402/agents/$AGENT/email/send \
-H 'Content-Type: application/json' \
-d '{"to":"someone@example.com","subject":"Hi","body":"..."}'
{
"x402Version": 2,
"error": "payment required",
"resource": {
"url": "https://api.incarna.io/x402/agents/{agent_id}/email/send",
"description": "Send email from the agent's own inbox. Body: {\"to\", \"subject\", \"body\"}. POST with an Authorization: Bearer key for the org that owns the agent — payment meters the action, it does not authorise it.",
"mimeType": "application/json",
"serviceName": "Incarna",
"tags": ["identity", "agents", "email"]
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"amount": "20000",
"payTo": "0xCa1fBb1900e1C17Cc443e34f312720960E72a83F",
"maxTimeoutSeconds": 120
}
]
}
amount 使用该资产自身的小数位 —— USDC 是 6 位,所以 20000 就是 $0.02。
resource 这一段同时充当服务的目录条目,一个从没听说过 Incarna 的 agent 正是靠它找到我们 ——
所以 url 就是它接下来要调用的路径。它是一个模板:{agent_id} 和 {account_id} 是调用方自己的,
而 x402 的 ResourceInfo 没有可以声明它们的 schema 字段,
这就是为什么 description 里点名了它们,并说明仍然需要 bearer 令牌。
2 —— 签名并重试
from x402 import x402Client
from x402.schemas.payments import PaymentRequired
required = PaymentRequired.model_validate(r.json())
payload = await client.create_payment_payload(required)
header = base64.b64encode(
json.dumps(payload.model_dump(mode="json", by_alias=True)).encode()).decode()
r2 = await http.post(url, headers={**headers, "X-PAYMENT": header}, json=body)
这份授权是 EIP-3009 的 transferWithAuthorization —— 一个签名,不是一次转账。
在结算之前什么都不会动。
3 —— 回执
一次成功的调用返回 200、动作本身的正常响应体,外加一个 base64 编码的
X-PAYMENT-RESPONSE 头:
{
"success": true,
"transaction": "0xd8b176786a3bbbf7b5f2b9ee66512e9b0ecec8be43f075c2057f1d551579403a",
"network": "eip155:84532"
}
验证 → 执行 → 结算
这个顺序是这里最值得了解的设计决定。
验证支付 ────────► 执行动作 ────────► 结算支付
│ │ │
无效则 这里失败, 钱只在工作
直接拒绝 则不扣费 发生之后才动
支付在动作之前验证,在动作之后结算。支付无效的调用方在任何工作开始之前就被拒绝; 动作失败的调用方不会被收费。「已经干完活但还没收到钱」的那个窗口由我们承担, 而不是客户承担 —— 这个方向才是对的。
maxTimeoutSeconds 设为 120,是为了让一次真实世界里较慢的写入
(比如通过住宅连接发出的一条帖子)能在授权有效期内从容完成。
设得太短,付款方会发现结算时授权已经过期。
账本
curl "$BASE/x402/payments?agent_id=$AGENT&limit=50" \
-H "Authorization: Bearer $INCARNA_KEY"
每一笔已结算的支付都连同它的链上交易一起记录。金额以字符串存储 —— 钱永远不是浮点数。 结算按交易哈希幂等,所以重复的回执不会被重复计数。
记录一笔支付从不导致请求失败。 在写账本的时候,动作已经发生、钱已经动了;
把一个记账错误变成客户可见的 500,会让一次成功且已付费的动作看起来像失败了。
一次完整运行
针对生产环境的真实输出,为一封确实发出去了的邮件付款:
payer 0x9c0071bc0F70C45565d42a9469C05bad1dCEDc75 USDC 1.990000
payee 0xCa1fBb1900e1C17Cc443e34f312720960E72a83F USDC 0.010000
[1] unpaid → HTTP 402 terms: 20000 USDC on eip155:84532
[2] signed → X-PAYMENT 1296 bytes
[3] paid → HTTP 200 {"message_id":"<...@email.amazonses.com>"}
[4] settled → tx 0x57453d22acbb24bb4f7bce3e0f004cacc534087b942d271265314b8f976d6f0a
[5] payer USDC 1.990000 → 1.970000
payee USDC 0.010000 → 0.030000
回执是一项主张;余额才是证据。