演示模式:面向 eSIM 运营商的参考店铺。价格为实时价格,支付已关闭。 运行你自己的运营商 →
Travel eSIM · demo
中文

智能体指南

目录

本指南面向预订行程、规划旅行或代办事务的 AI 智能体开发者。你的智能体可以为某个国家找到旅行流量 eSIM, 用一笔 Base 上的 USDC x402 付款买下,然后把二维码交给旅行者。无需账户,无需 API 密钥,无需付款页面。

每个运行 eSIM 运营商套件的卖家都在 https://<卖家域名>/agents/ 响应同样的三个调用。想现在就 试用,可以使用演示节点 https://modelmarket.dev/providers/esim:它给出基于批发价的真实价格,并以 demo_mode 拒绝订单。

其他文档:概览 · 运营者指南 · 接入指南 · 集成 · SDK

三种能力

能力路由价格作用
esim.quote@v1POST /agents/quote免费覆盖某国家的套餐,从便宜到贵排列。包含该国家的区域和全球套餐也算在内。
esim.order@v1POST /agents/order套餐价格,x402购买一张 eSIM。返回激活码、二维码链接、安装链接、ICCID 和状态令牌。
esim.status@v1POST /agents/status免费状态、已用流量、到期时间,以及再次返回激活码,仅限持有状态令牌者。

GET /agents/listing.json 描述这三种能力,并附卖家的公钥。

1. 找到卖家

2. 获取报价

curl -s -X POST https://modelmarket.dev/providers/esim/quote \
  -H 'content-type: application/json' \
  -d '{"country":"FR","min_gb":3,"min_days":7,"limit":3}'
输入类型
country字符串,必填目的地的 ISO 3166-1 alpha-2 代码:FR、JP、US。
min_gb数字最少流量。
min_days整数最短有效期。
max_usd数字最高价格。
limit整数,1 到 50返回多少个套餐。

始终根据行程传入 min_gb 和 min_days。不传的话,最便宜的往往是 0.5 GB 一天的套餐。

响应(一个套餐,列表已缩短):

{
  "success": true,
  "result": {
    "seller": { "name": "modelmarket.dev demo seller", "product_id": "esim-modelmarket", "demo": true },
    "offers": [
      {
        "offer_id": "EU-33_3_15",
        "name": "Europe (33 areas) 3GB 15Days",
        "coverage": { "kind": "region", "countries": ["NO", "DE", "BE", "FI", "…"] },
        "data_bytes": 3221225472,
        "data_gb": 3,
        "days": 15,
        "speed": "3G/4G/5G",
        "networks": [{ "country": "Austria", "operators": [{ "name": "A1.net", "type": "5G" }] }],
        "top_up": true,
        "unused_valid_days": 180,
        "price": { "usd": "2.03", "currency": "USDC" },
        "usd_per_gb": 0.68
      }
    ],
    "order": { "url": "https://modelmarket.dev/providers/esim/order", "method": "POST", "payment": "x402, USDC on Base" },
    "catalogue_at": "2026-10-14T10:32:44.619Z"
  }
}

3. 验证卖家签名(可选)

免费响应带有 X-Provider-Signature:对 {capability_id, product_id, input_sha256, result} 的规范 JSON 所做的 Ed25519 签名,base64 编码。规范形式按键排序、无空格、非 ASCII 字符原样保留。input_sha256 是规范 输入的十六进制 SHA-256。公钥和 product_id 在 /agents/listing.json 中。枢纽会自行验证这个签名。直接调用 卖家时,请自己验证:

import { createHash, createPublicKey, verify } from "node:crypto";

const canonical = (v) => Array.isArray(v) ? `[${v.map(canonical).join(",")}]`
  : v && typeof v === "object" ? `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`).join(",")}}`
  : JSON.stringify(v ?? null);

export function signedBySeller({ pubkey, capabilityId, productId, input, result, signature }) {
  const input_sha256 = createHash("sha256").update(canonical(input)).digest("hex");
  const bytes = Buffer.from(canonical({ capability_id: capabilityId, product_id: productId, input_sha256, result }));
  const der = Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), Buffer.from(pubkey, "base64")]);
  return verify(null, bytes, createPublicKey({ key: der, format: "der", type: "spki" }), Buffer.from(signature, "base64"));
}

4. 购买

向 <order.url> 发送 POST,正文为 {"offer_id": "<来自报价>"}。第一次响应是 402 Payment Required, 附 x402 v2 条款:方案 exact,网络 eip155:8453(Base),USDC,套餐价格,收款方为卖家。任何 x402 v2 客户端 都能支付。你的钱包签署一份 EIP-3009 授权,不付 gas。使用 x402 包的 TypeScript 示例:

npm i @x402/fetch @x402/evm viem
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY);   // 一个在 Base 上有 USDC 的钱包

// 保留封装器发出的付款头:重试必须带同一笔付款,而不是新的一笔。
let paidHeaders = null;
const keepPayment = (input, init) => {
  const req = new Request(input, init);
  if (req.headers.has("PAYMENT-SIGNATURE")) paidHeaders = new Headers(req.headers);
  return fetch(req);
};
const payingFetch = wrapFetchWithPaymentFromConfig(keepPayment, {
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});

export async function buyEsim(orderUrl, offerId) {
  const body = JSON.stringify({ offer_id: offerId });
  let res = await payingFetch(orderUrl, { method: "POST", headers: { "content-type": "application/json" }, body });
  // 503 allocation_pending:eSIM 已下单但尚未就绪。稍后用同一笔付款重试。
  for (let i = 0; i < 6 && res.status === 503 && paidHeaders; i++) {
    await new Promise((r) => setTimeout(r, 1000 * Number(res.headers.get("retry-after") || 20)));
    res = await fetch(orderUrl, { method: "POST", headers: paidHeaders, body });
  }
  if (res.status === 402) {   // a refused payment says why in the x402 header, not in the body
    const terms = res.headers.get("payment-required");
    throw new Error(`402 ${terms ? JSON.parse(Buffer.from(terms, "base64").toString("utf8")).error : "payment required"}`);
  }
  const answer = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${answer.error}: ${answer.message}`);
  return answer.result;
}

何时扣款。 卖家验证付款、购买 eSIM,并且只在 eSIM 创建后才结算付款。被拒绝的订单不花钱。仍在准备中的订单(503 allocation_pending)尚未扣款;再次发送同一笔付款会取回同一张 eSIM,绝不会买第二张。结算失败的付款会导致 eSIM 被取消。

响应:

{
  "success": true,
  "result": {
    "order_no": "B26101410460022",
    "esim_tran_no": "26101410460022",
    "iccid": "8985…",
    "activation_code": "LPA:1$…",
    "qr_code_url": "https://…",
    "install_url": "https://…",
    "apn": "internet",
    "expires": "2027-04-12T10:46:24+0000",
    "status_token": "…",
    "offer": { "offer_id": "TR_0.5_1", "name": "Turkey 500MB 1Day", "…": "…" }
  }
}

激活码是机密。 任何拿到它的人都能安装这张 eSIM。只把它和二维码链接、安装链接交给旅行者本人。不要写入 日志,不要放进共享聊天、工单或日历事件。保存 esim_tran_no 和 status_token,以便之后查询 eSIM。

5. 错误

状态码error含义怎么做
400unknown_offer该 offer_id 不在(或已不在)目录中。重新报价。
402(x402 条款)需要付款,或付款被拒:金额不对、USDC 不足、已过期。付款,或修复钱包。
403demo_mode演示节点,不出售任何东西。换一个在售的卖家。
409supplier_<code>批发商拒绝了订单:套餐下架、价格变动。没有扣款。重新报价。
503allocation_pending已下单,尚未就绪。尚未扣款。在 Retry-After 秒后用同一笔付款重试(见上例)。
503catalogue_unavailable、payments_off卖家暂时无法销售。换个卖家,或稍后再试。
500internal_error订单失败。没有扣款。稍后再试,或换个卖家。

6. 帮助旅行者安装

7. 查询用量

curl -s -X POST https://<seller>/agents/status -H 'content-type: application/json' \
  -d '{"esim_tran_no":"26101410460022","status_token":"…"}'

响应包含 state(批发商自己的取值:GOT_RESOURCE 表示尚未安装;其他取值包括 IN_USE、USED_UP 和 CANCEL)、installed、data_used_bytes、data_total_bytes、activated、expires,以及再次返回的交付字段。错误的令牌会得到 404 not_found,与未知 eSIM 相同。

8. 良好做法

GitHub 上的套件 →