---
name: dogpay-agent
description: >
  Discover and call DogPay Agent Pay with an Agent Key and its funded wallet.
  Capabilities: web.search.v1, web.fetch.v1, web.crawl.v1, maps.places.v1,
  maps.reviews.v1, amazon.products.v1, youtube.videos.v1, llm.chat.v1,
  image.generate.v1, video.generate.v1.
  Use when the user mentions Agent Pay, DogPay, /agent/v1, agk_, or 调 Agent Pay.
---

# DogPay Agent Pay

DogPay 让调用方发现付费能力、查看 schema、列出可用模型、发起调用，并跟踪每次调用的状态与账单。

## 1. 服务地址

从提供本文件的源站推导，不要写死示例主机，也不要假定各环境共用密钥、钱包或记录。

```text
Skill:     https://dogrouter.ai/agent/SKILL.md
Base URL:  https://api.dogrouter.ai
Dashboard: https://dogrouter.ai/dashboard
Agent Pay: https://dogrouter.ai/agent-pay
API:       https://api.dogrouter.ai/agent/v1
```

`GET /agent/SKILL.md` 公开。所有 `/agent/v1/*` 都要带 Agent Key，并且必须打到提供本文件的同一个源站。

```bash
curl -fsS "$DOGPAY_BASE_URL/agent/SKILL.md"
```

把 `DOGPAY_BASE_URL` 和 `DOGPAY_AGENT_KEY` 放在进程环境里。不要把完整密钥写进对话、规则或技能文件。本仓库里，本文件是合同正文；各工具的安装说明在 `.agents/skills/dogpay-agent/SKILL.md`。

## 2. 第一次配置

用户要配置 DogPay，或说「引导我在本地安全配置 DogPay Agent Key」时，按这个顺序做。不要让对方把完整密钥贴进对话。

1. 从将要调用的主机拉取本文件。
2. 先说明需要什么：一只专用钱包、要开通的能力、可选的日限额（UTC 日）、可选的单次上限。
3. 对方登录 `<Base URL>/dashboard`，打开 **Agent Pay**（`<Base URL>/agentpay`），创建钱包（可在同一次提交里从主余额划入），再创建密钥。`agk_...` 只显示一次，存进 `DOGPAY_AGENT_KEY`。控制台不另建身份，一把密钥就够。
4. 用 `GET /agent/v1/key` 和 `GET /agent/v1/wallet` 预检。确认密钥可用、`available` > 0、目标能力在列表里。
5. 然后才发起调用。

```text
说明需求 → 有余额的钱包 → 密钥（只显示一次）→ GET /key → /agent/v1/*
```

**能力目录**列出可调用能力，可按关键词和分类筛选，并给出 schema、单价和默认冻结额。密钥只能勾这些能力 id。**调用记录**看每次调用、账单和错误。日限额在 **00:00 UTC** 重置。

## 3. 保护密钥

Agent Key 是可以花绑定钱包的 bearer 凭据。

- 不要在对话里索要或复述完整密钥。
- 不要放进源码、Git、提示词、工单、截图、浏览器或移动端代码、日志。
- 引用时写成 `agk_abcd...wxyz`。
- 本地用环境变量，生产用密钥管理。

```bash
export DOGPAY_BASE_URL='https://<host>'
export DOGPAY_AGENT_KEY='agk_...'
```

```powershell
$env:DOGPAY_BASE_URL = "https://<host>"
$env:DOGPAY_AGENT_KEY = "agk_..."
```

每个 `/agent/v1` 请求：

```http
Authorization: Bearer <Agent Key>
```

通用的「打开这个 URL」工具通常不能设置 `Authorization`，还可能缓存或摘要响应。每次 `/agent/v1` 调用用 `curl`（或等价工具），从 `$DOGPAY_AGENT_KEY` 读取 Bearer。不要把密钥展开进命令行。已认证请求不要用 `curl -v` 或 `--trace*`，它们会打印 `Authorization`。`-i` 或 `-D -` 可以用来读 `Retry-After`。JSON 里也有 `retryable`。

### 撤销与轮换

密钥出现在对话、Git、日志、截图、客服系统或失陷设备上时，立刻在 **Agent Pay → 密钥** 里撤销，或使用 **轮换**。轮换在**同一把**密钥上更换 secret，保留当日已用额度。旧 secret 立即失效，没有重叠窗口。轮换后确认旧密钥返回 `agent_key_invalid`。暂停中的密钥也可以轮换；在恢复之前，`GET /agent/v1/key` 仍是 `agent_key_paused`。

计划中的切换、要求不中断：新建一把密钥，部署并验证后，再撤销旧的。新密钥的日限额从零计。不要把轮换当成无缝切换。

## 4. 控制花费

- 不同程序、不同环境用不同的钱包和密钥。
- 只划入预计要用的余额。
- 只开通需要的能力。需要封顶时设置日限额（UTC）和单次上限。
- 单次上限比对的是**冻结额**，不是最终实扣。日限额在调用未结时按冻结额计，结算后按实扣计。
- 冻结额 = min(本次 `max_charge` 经密钥单次上限收紧后的值, 按请求上限算出的最坏价)。`max_charge` 低于最坏价时，服务端**收紧执行**（条数或时长变少），不是直接拒绝。低于能执行的最小值时返回 403，错误体带 `min_charge`。
- 结算按实际成本。设了单次上限时，实扣不超过该上限。实扣高于冻结额且钱包可用余额不够补差额时，按冻结额封顶，差额由平台承担。钱包余额足够、且单次上限高于冻结额时，实扣可以高于冻结额，但不会高于单次上限。没设单次上限时没有这道上限，余额不够补差额时仍按冻结额封顶。
- 未使用的钱包余额可以在控制台提回主余额（`held` 不能提）。链上充进钱包的 USDC 留在钱包里花费或提回主余额，不能提回链上。
- 定期看调用记录，撤销不用的密钥。

付费调用前先 `GET /agent/v1/key`。核对任务、能力、模型、预计价格、剩余日限额和钱包 `available`。用户只是在问文档、模型、schema 或余额时不要发起调用。

## 5. 调用流程

```text
GET /key → Discover → Inspect → Models → Wallet → Invoke → 看 status → Billing
```

下面的路径都相对 `<Base URL>`，都要带 Authorization。

**列表都分页**（`/catalog`、`/discover`、`/models`、`/wallet/ledger`）：响应是 `{"items":[…],"total":N,"page":1,"page_size":20}`。`p` 从 1 起，`page_size` 默认 20、最大 100。非法值是 `400 invalid_input`，不会被静默改掉。要拿全量就继续请求 `p+1`，直到凑满 `total`。

### 预检密钥

```http
GET /agent/v1/key
```

```json
{
  "name": "prod",
  "fingerprint": "agk_abcd...wxyz",
  "status": "active",
  "wallet_id": "wal_…",
  "available": "1.000000",
  "held": "0.000000",
  "capabilities": ["web.search.v1"],
  "daily_limit": "5.000000",
  "max_charge": "1.000000",
  "today_used": "0.001000"
}
```

只有可用密钥会返回 200。暂停的密钥本调用是 HTTP 403 `agent_key_paused`（在控制台恢复，或换一把密钥）。`status: "paused"` 出现在管理面，不会出现在这个 200 里。`available` 为 `0`，或目标能力不在 `capabilities` 里时，不要发起调用。若 `today_used` 加上即将冻结的金额会超过 `daily_limit`，停下来告诉用户。本调用不扣费。

`daily_limit` / `max_charge` 省略表示不限。金额是美元字符串，六位小数。

### 发现能力

```http
GET /agent/v1/catalog?q=<need>&category=<category>&p=1&page_size=20
```

或：

```http
POST /agent/v1/discover
Content-Type: application/json

{"query":"I need to generate an image","category":"image","p":1,"page_size":10}
```

筛选都可选。`q`（POST 里是 `query`）按空白分词；`I`、`need`、`to`、`a` 这类词忽略。剩下的词里**任意一个**出现在 id、名称、描述或分类里即命中；命中词更多，或整句原样出现（例如直接查 `web.search.v1`），排在前面。所以关键词和整句都可以。`category` 是 `search`、`fetch`、`llm`、`image`、`video`。

查询里的 `provider`、`kind` 被忽略，不报错。目录只有可调用能力这一层。模型价格不在目录条目上，用 `GET /agent/v1/models`。

条目字段：`id`、`name`、`description`、`category`、`status`、`unavailable_reason`、`unit_price`、`default_hold`、`price_unit`、`price_detail`、`input_schema`、`output_schema`、`models`、`limits`。

- `unit_price`：标价。模型能力（`llm.chat.v1` / `image.generate.v1` / `video.generate.v1`）没有脱离 `input.model` 的单价，此字段为空。
- `default_hold`：默认 input 下的冻结额。
- `price_unit`：`call`、`result`、`compute_unit`、`image`、`1m_prompt_tokens`、`depends_on_model`。
- `price_detail`：计价公式见 `formula`。图片还可有 `multiplied_by`，模型能力还可有 `source`。没有配置键名。以响应为准，不要把目录数字缓存成长期价格。可覆盖的配置键名见运维文档。
- `models`：`{id, kind}`，`kind` 只描述模型类型（`chat` / `image` / `video`），不是目录分层。
- `limits` 只可能有 `timeout_sec`、`max_items`、`max_n`、`max_response_bytes`。零值不出现。

当前不能调用的条目 `status` 不是 `active`，并带 `unavailable_reason`：`compliance`（本地区不提供）、`provider_unconfigured`（主机没有上游凭据）、`endpoint_disabled`（运维下线）、`provider_unavailable`（暂时不可用）。列表只含未下线的条目，所以 `endpoint_disabled` 只在你拿着已知 id 去 Inspect 时出现。不要调用这些条目，也不要把它们当成短暂故障去重试。

### 查看一条能力

```http
GET /agent/v1/catalog/endpoints/<endpoint-id>
```

或：

```http
POST /agent/v1/inspect
Content-Type: application/json

{"endpoint":"web.fetch.v1"}
```

不熟悉的能力先 Inspect。示例（字段以当时响应为准）：

```json
{
  "id": "web.fetch.v1",
  "name": "Web Fetch",
  "description": "Fetch a page URL and return markdown, text, or links",
  "category": "fetch",
  "status": "active",
  "unit_price": "0.050000",
  "default_hold": "0.050000",
  "price_unit": "call",
  "price_detail": {"formula": "per call"},
  "input_schema": {},
  "output_schema": {},
  "limits": {"timeout_sec": 60, "max_items": 20}
}
```

`llm.chat.v1` 的 `limits.max_response_bytes` 约 4 MB（`4194304`）。`image.generate.v1` 的 `limits.max_n` 为 10，`max_response_bytes` 约 15 MB（`15728640`）。超过响应上限时，调用仍可能结算，结果是一截存根，见文末「大响应」。

读价格时分开看单价和冻结额。`depends_on_model` 的 `default_hold` 只是目录底价（聊天 `$0.01`、图片 `$0.04`、视频 `$0.20`），真正扣多少取决于 `input.model`。`1m_prompt_tokens` 是每百万 prompt token 的美元，不是一次调用的价格。`image` 的单价是每张，冻结额已含张数 `n` 和尺寸 / 质量倍率。

### 列出模型

```http
GET /agent/v1/models
GET /agent/v1/models?kind=chat
GET /agent/v1/models?kind=image
GET /agent/v1/models?kind=video
```

```json
{"items":[{"id":"example-model-id","kind":"chat"}],"total":1,"page":1,"page_size":20}
```

`kind` 只能是 `chat`、`image`、`video`，或省略。其它值是 `400 invalid_input`。把返回的 `id` 原样放进 `input.model`。不要猜测模型 id，不要从别的主机或本文示例里抄名字，也不要假定某个公开模型名一定存在。`invalid_input` 或渠道、密钥变化后重新拉列表。把模型名单里的 id 当成 `endpoint` 去调用也是 `invalid_input`：文案指向 `GET /agent/v1/models`，并要求改走 `llm.chat.v1`、`image.generate.v1` 或 `video.generate.v1`，把该 id 放进 `input.model`。

### 钱包

```http
GET /agent/v1/wallet
```

```json
{
  "id": "wal_…",
  "name": "ops",
  "currency": "USD",
  "available": "1.000000",
  "held": "0.000000",
  "status": "active"
}
```

`available` 可以用于新调用。`held` 是在途调用的冻结额。`status` 为 `active` 或 `frozen`。USDC 充值进入 `available`。这笔余额不能提回链上；提回主余额是登录用户在控制台做的，不是 Agent Key 的接口。

```http
GET /agent/v1/wallet/ledger?p=1&page_size=20
```

每条：`id`、`type`、`amount`、`available_after`、`held_after`、`created_at`。调用类分录带 `invocation_id`（`inv_…`），划入 / 提回主余额带 `transfer_id`（`xfr_…`），链上充值带 `deposit_id`（`dep_…`）。`type` 为 `transfer_credit`、`transfer_debit`、`reservation_hold`、`settlement_debit`、`reservation_release`、`deposit_credit`。`available_after` / `held_after` 是该笔记账后的余额。

### 充值 USDC

这是独立资源，不改变 `POST /agent/v1/invocations` 的 402 金额。服务器不持有付款私钥，只校验 `X-PAYMENT` 并核对回执。充进的 USDC 只增加钱包 `available`。

```http
POST /agent/v1/wallet/deposit
Authorization: Bearer $DOGPAY_AGENT_KEY
Content-Type: application/json

{"amount":"10.00"}
```

`amount` 是可选的美元字符串。低于 `$5`、高于单笔 `$500`、或会使这把密钥的 UTC 日累计超过 `$1000` 时直接拒绝，不会改写成另一个金额。省略 `amount` 时挑战按最低额 `$5` 签发。日累计超限是 HTTP 403 `policy_denied`，`reason` 为 `deposit_daily_limit`。签挑战里的金额，不要自己四舍五入。

响应是 HTTP 402，JSON 正文是 x402 v1 `exact` 挑战。`accepts[0].resource` 是绝对 URL，路径为 `/agent/v1/wallet/deposit/<id>`。按 `maxAmountRequired` 向 `payTo` 做 EIP-3009 签名，然后确认。不要把 `X-PAYMENT` 再打到创建充值的 URL 上。充值挑战的 `maxTimeoutSeconds` 声明仍至少 420，但 `extra.validUntil` 和充值单过期时间仍是主机配置的秒数（默认 60，范围 15–600），没有和声明对齐。确认时只要 `validBefore` 晚于现在。充值确认不经过调用那道 300–480 秒窗口。

```http
POST /agent/v1/wallet/deposit/<id>/confirm
Authorization: Bearer $DOGPAY_AGENT_KEY
X-PAYMENT: <base64 of the x402 v1 payload>
```

HTTP 200 表示 `available` 已加上这笔充值。同一个 `X-PAYMENT` 再确认仍是 200，不会入账第二次。

HTTP 202 且 `code` 为 `deposit_settlement_unknown` 表示链上结果还不明。用**同一个** `X-PAYMENT` 重试同一条 confirm URL。不要新签一笔授权：上一笔授权结算结果未定（已送出结算、settling / 待人工）时带新授权确认是 HTTP 409 `payment_duplicate`——上一笔可能已在链上成功，换新授权再结算会被划两笔。只有从未送成结算的 pending（如 facilitator 暂时不可达）才允许换新授权重试。同一份授权换了 JSON 键序重新编码（付款方 / nonce / 签名没变）不算换，按重放处理。入账完成前没有 `X-PAYMENT-RESPONSE`。

钱包余额不足（`code: insufficient_funds`，没有 `accepts`）可能带 `deposit_hint`：`path` 为 `/agent/v1/wallet/deposit`，`suggested_amount` 是美元字符串（缺额与 `$5` 的较大者，再向上取整到整美元）。这只是提示，不是挑战，也不改变你正在发起的那次调用的价格。

### 发起调用

```http
POST /agent/v1/invocations
Content-Type: application/json
Idempotency-Key: <stable-operation-id>
```

```json
{
  "endpoint": "<endpoint-id>",
  "input": {},
  "max_charge": "1.000000"
}
```

`Idempotency-Key` 必填，账户级唯一：同一账户下任意密钥重放同一个键，拿到的都是原来那次调用。`max_charge` 可选，美元字符串。它参与冻结额，并在低于最坏价时收紧执行。低于最小值时：

```json
{
  "code": "policy_denied",
  "message": "policy_denied",
  "retryable": false,
  "reason": "max_charge",
  "min_charge": "0.050000",
  "request_id": "…"
}
```

HTTP 403。用 `min_charge` 作为下一笔的 `max_charge`（或提高密钥单次上限）再开**新的**幂等键。不要用一个从错误条目抄来的过小上限反复撞同一把幂等键。

创建、`GET /agent/v1/invocations/<id>` 和取消返回同一个调用对象：

```json
{
  "id": "inv_…",
  "endpoint": "web.search.v1",
  "status": "authorized",
  "waiting_for": "upstream_capacity",
  "billing": {
    "currency": "USD",
    "reserved_amount": "0.005500",
    "charged_amount": "0.000000",
    "released_amount": "0.000000",
    "status": "authorized",
    "source": "wallet"
  },
  "key_id": "key_…",
  "wallet_id": "wal_…",
  "idempotency_key": "…",
  "request_id": "…",
  "created_at": 1790000000
}
```

`waiting_for` 只和 `authorized` 一起出现，值为 `upstream_capacity`，表示还在等上游名额、还没交给上游。`result` 有结果才出现。`error` 只在失败时出现，形状是 `{"code","message","retryable"}`。x402 调用在 `status` 变成 `succeeded` 之前不返回 `result`。

顶层 `status` 只有这 7 个值：

| `status` | 含义 | 调用方 |
|---|---|---|
| `authorized` | 已冻结或已认领，还没交给上游。在等上游名额时带 `waiting_for=upstream_capacity` | 继续轮询同一条调用。可以取消 |
| `running` | 上游在执行 | 继续轮询 |
| `succeeded` | 终态，有结果 | 读 `result` |
| `failed` | 终态。原因在 `error.code` | 看 `error.retryable`。为 true 时换**新的**幂等键重开；为 false 时不要重开 |
| `timed_out` | 终态，上游超时。账单已 `released` 时创建响应是 HTTP 408；账单还不是 `released` 时创建响应是 HTTP 202，钱未定论 | 已退款可以换新的幂等键重开。钱未定论时继续用同一个键看账单 |
| `unknown` | 上游已开始，但结果或结算还不明 | 不要重开。用同一个幂等键重放或继续轮询，等待 |
| `manual_review` | 等待人工核对。创建响应是 HTTP 200 | 不要重开。用同一个幂等键读取，等待 |

`billing.status` 只有 `authorized`（钱还没定论）、`settled`（已按实扣入账）、`released`（冻结额已退回）。

钱包支付的创建在尚未终态时返回 HTTP 202。可选地在 handler 里等到终态，不改变执行方式：

```http
Prefer: wait=30
```

`wait` 取 1–30 秒，超过按 30。缺失、小于 1 或无法解析则忽略。多个偏好里只认 `wait`，值可以带引号。在时限内进入终态（`succeeded` / `failed` / `timed_out` / `manual_review`）时带 `Preference-Applied: wait=<秒>`。HTTP 状态仍按下表，不是一律 200。`unknown` 不算终态，时限到了仍是 HTTP 202，且不带这个头。幂等重放同样遵守这个头。

创建响应的 HTTP 状态：

| 对外 `status` | HTTP |
|---|---|
| `authorized`、`running`、`unknown` | 202 |
| `succeeded`、`manual_review` | 200 |
| `timed_out` 且账单已 `released` | 408 |
| `timed_out` 且账单还不是 `released` | 202（钱未定论） |
| `failed` 且 `error.code` 为 `payment_failed` | 403 |
| `failed` 且 `error.code` 为 `insufficient_funds` | 402 |
| `failed` 且 `error.code` 为 `provider_unavailable` | 503 |
| `failed` 且 `error.code` 为 `execution_interrupted` | 503 |
| `failed` 且 `error.code` 为 `upstream_timeout` | 408 |

其它 `failed` 按错误码映射，常见是 400。轮询 `GET /agent/v1/invocations/<id>` 一律按对象返回 HTTP 200，用 `status` 判断，不要用这一次 GET 的状态码判断终态。

丢失响应后，用同一个幂等键、同一个 `endpoint`、同一份原始 `input` 和同一个 `max_charge` 重放，或 `GET` 那条调用。重放返回已存储的结果，不会把上游再跑一遍。请求体不一致是 `idempotency_conflict`。

### x402（仅 `web.search.v1`）

默认用钱包。主机可以另开 x402 v1（`exact`，一种网络、一种资产，USDC 六位小数），并且**只**接受 `web.search.v1`。其它能力带 `Payment-Method: x402` 是 `400 invalid_input`。

顺序是先验证、执行，再结算。执行失败不结算，付款方不花钱。第一次不要带 `X-PAYMENT`：

```http
POST /agent/v1/invocations
Authorization: Bearer $DOGPAY_AGENT_KEY
Idempotency-Key: <stable-operation-id>
Payment-Method: x402
Content-Type: application/json

{"endpoint":"web.search.v1","input":{"query":"x402"}}
```

HTTP 402，JSON 正文就是 v1 挑战（没有挑战响应头）。本服务只说 x402 v1：`exact`，请求头 `X-PAYMENT`。`accepts[].resource` 是绝对 URL。挑战金额等于本次冻结额。挑战声明的 `maxTimeoutSeconds` 至少 420。`extra.validUntil` 是绝对 Unix 秒，与这个声明对齐：等于签发时刻 + `maxTimeoutSeconds`（至少现在起 420 秒）。按 `maxTimeoutSeconds` 或按 `validUntil` 签都可以：`validBefore` = 签名时刻 + `maxTimeoutSeconds`，或直接等于 `extra.validUntil`。提交时授权剩余有效期须落在 300–480 秒：下限 300 秒（verify 15 秒 + 认领落库 15 秒 + 执行 180 秒 + settle 30 秒 + 余量 60 秒），上限 480 秒（挑战窗口 420 + 60）。不够 300 秒是 HTTP 402 `payment_expired`；超过 480 秒是 HTTP 402 `payment_invalid`，按当前挑战重签。按声明的 420 秒或按此时的 `validUntil` 签完马上提交，剩余不少于 420 秒、不超过 480 秒。挑战把 `maxTimeoutSeconds` 抬过 480 秒时，把 `validBefore` 签在提交时刻起 480 秒以内。充值挑战的 `extra.validUntil` 没有和声明对齐，确认也不走这道上限，见上面的「充值 USDC」。

```json
{
  "x402Version": 1,
  "code": "payment_required",
  "error": "X-PAYMENT header is required",
  "accepts": [{
    "scheme": "exact",
    "network": "base-sepolia",
    "maxAmountRequired": "5500",
    "asset": "0x…",
    "payTo": "0x…",
    "maxTimeoutSeconds": 420,
    "resource": "https://<host>/agent/v1/invocations",
    "extra": {"name": "USDC", "version": "2"}
  }],
  "request_id": "…"
}
```

`maxAmountRequired` 是资产的原子单位。按 `extra.name` / `extra.version` 做 EIP-712，向 `payTo` 签恰好这个金额。授权必须满足 `validAfter ≤ now`，且 `validBefore` 距现在不少于 300 秒、不超过 480 秒；不够是 HTTP 402 `payment_expired`，超出是 HTTP 402 `payment_invalid`，都发生在认领之前。用**同一个** URL、正文和幂等键重试：

```http
X-PAYMENT: <标准 base64（带填充）编码的支付 JSON>
```

`X-PAYMENT` 由付款钱包生成，响应里不会给一份可复制的头。正文里的 `X-PAYMENT header is required` 只是挑战文案。支付 JSON 含 `x402Version`、`scheme`、`network`、`payload.signature`、`payload.authorization`。必须是 RFC 4648 标准 base64 且带填充。裸 JSON、URL-safe 或无填充是 `payment_invalid`。

```bash
curl -sS -D - -o /tmp/402.json "$DOGPAY_BASE_URL/agent/v1/invocations" \
  -H "Authorization: Bearer $DOGPAY_AGENT_KEY" \
  -H "Idempotency-Key: $IDEM" -H "Payment-Method: x402" \
  -H "Content-Type: application/json" \
  -d '{"endpoint":"web.search.v1","input":{"query":"x402"}}'
curl -sS -D - "$DOGPAY_BASE_URL/agent/v1/invocations" \
  -H "Authorization: Bearer $DOGPAY_AGENT_KEY" \
  -H "Idempotency-Key: $IDEM" -H "Payment-Method: x402" \
  -H "X-PAYMENT: $(base64 -w0 payload.json)" \
  -H "Content-Type: application/json" \
  -d '{"endpoint":"web.search.v1","input":{"query":"x402"}}'
```

- 执行成功并结算成功：HTTP 200，`status` 为 `succeeded`，`result` 为搜索结果，`billing.source` 为 `x402`，`billing.status` 为 `settled`。可能带 `X-PAYMENT-RESPONSE`（base64 JSON：`success`、`transaction`、`network`、`payer`）。链上核对确认已收款、认领行写下这笔交易哈希之后，响应同样可以带这个头；结果没留下时调用仍是 `manual_review`，头也可以出现。`success` 表示链上结算，搜索是否成功看调用对象的 `status`。
- 执行失败（含上游超时、上游已开始但没有可交付结果）：不结算。`status` 为 `failed` 或 `timed_out`，`billing.status` 为 `released`，`charged_amount` 为 `0`。付款方不花钱。按 `error.retryable` 决定是否换新幂等键。结果超过可存储上限（约 15 MB）同样不结算，`error.code` 为 `result_too_large`（不是 `invalid_input`），`retryable` 为 false。`timed_out` 而账单还不是 `released` 时，创建响应是 HTTP 202，钱未定论。
- 结算结果不明：HTTP 202，`status` 为 `unknown`，不返回 `result`。不要新签授权。用同一个幂等键重放；服务端先补结算，成功后才交付。结算在途统一是这个 202，没有另外的在途错误码。另一种结局是执行中断：`status` 为 `failed`，`error.code` 为 `execution_interrupted`，`billing.status` 为 `released`，实扣 `0`。原因是执行没完成，或服务端在结算发起前中断（授权过期时认领仍是 claimed）。付款方没花钱，不判 `payment_failed`。`error.retryable` 为 true。创建或重放是 HTTP 503。可以换新的 `Idempotency-Key` 重开。不暂停这把密钥。之后 `GET` 仍是 200。
- 结算明确失败之前，服务端会核对链上授权状态。查到 `AuthorizationUsed` 还会取该交易的回执，核对其中有一条转给认领收款地址、金额恰好相等的 USDC `Transfer` 才算已收款——付款方可以用同一个 nonce 另签一张转给自己的授权并自行上链，事件照样命中、平台没收到钱。链上已用且回执核对收款成立、结果已落库：按成功收口并交付。链上已用但结果没留下：`status` 为 `manual_review`，`error.code` 为 `settlement_unknown`，钱已收，不要重开，不要新签，没有链上退款。Used 事件在、回执里有付款方的 USDC 转出但收款方或金额对不上（nonce 被别的授权用掉）：平台没收到钱，按结算明确失败处理（HTTP 403，`error.code` 为 `payment_failed`，error 说明写 nonce consumed by a different authorization），与撤销同一套暂停。回执取不到、确认数不足、或回执里没有付款方的 USDC 转出，按「查不到」处置，不会误判成已收款。付款方在链上撤销授权，按结算明确失败处理：HTTP 403，`status` 为 `failed`，`error.code` 为 `payment_failed`，`error.retryable` 为 false，不返回结果，付款方没花钱。这把密钥的 x402 暂停 24 小时。已经发起过结算、链上未用、且结算方明确拒绝或授权已过最终核对窗口，同样是这个码和这 24 小时暂停。还没有结果时不走这个暂停：认领从未广播，或宽限过后链上仍未用，是上面的 `execution_interrupted`。暂停期内新的 x402 挑战是 HTTP 403 `policy_denied`，`reason` 为 `x402_suspended`。钱包路径不受这 24 小时影响。不要为同一笔工作再签一份授权。查不到链上状态（含非 hex 的 nonce 无法编码）：最终核对时转为 `manual_review`，`error.code` 为 `settlement_unknown`，`billing.status` 仍是 `authorized`，不暂停这把密钥，也不会把可能已收的钱判成未收款。facilitator 报 nonce 已用、但还没到 `validBefore` 之后 5 分钟时，已用或已撤销就地定局；链上未用或查不到则保持 `unknown`（HTTP 202），先不要当成 `payment_failed`。另一路正在结算时，重放同样是 202 + `unknown`，不要新签。
- 同一链上授权（network、asset、payer、nonce）配了不同的付款或不同的请求：HTTP 409 `payment_duplicate`。认领还在，或已经绑上一次调用时，同一把 `Idempotency-Key` 重放换了新签的付款证明，同样是 409 `payment_duplicate`，钱不动；补结算仍用第一次认领的那份证明。例外：`/verify` 不可达时认领会先留下来，还没有调用对象。`validBefore` 已过，或停留约 10 分钟仍没完成验证，这份未绑定的认领会被释放。之后同一个 `Idempotency-Key` 可以带新付款（新 nonce）重开。在释放之前换新证明仍是 409；同一份 `X-PAYMENT` 重放会继续验证。
- 当日 x402 损失达到主机上限：HTTP 503 `x402_daily_loss_exceeded`。当天（UTC）不要重试。

`payment_invalid` / `payment_expired`：先不带 `X-PAYMENT` 再取一次挑战，然后重新签名，幂等键不变。

### 取消

```http
POST /agent/v1/invocations/<invocation_id>/cancel
```

只在尚未交给上游时成功，包括还在等上游名额（`waiting_for` 为 `upstream_capacity`）的时候。成功则退回冻结额，调用变为 `failed`，`error.code` 为 `cancelled`，`error.retryable` 为 false。已经交给上游的调用会跑完并按实际成本结算，取消返回 HTTP 409 `run_not_cancellable`。x402 调用不能取消。排队超过 60 分钟仍未开始：`failed`，`error.code` 为 `provider_busy`，冻结额退回；稍后用新的幂等键再试。

媒体结果只给本服务保存的 URL。图片和视频在报 `succeeded` 之前会先转到本服务的存储。`result.result_urls` 是地址列表，`result.result_url` 是第一条。不会返回 base64，也不会返回大约一天就失效的上游直链。本服务的副本保留 **30 天**。

## 6. 能力

金额是默认标价。公式见 `price_detail.formula`。可覆盖的配置键名见运维文档，不在目录对象里。调用前以 Inspect 为准。

### `web.search.v1`

搜索。`query` 必填。`max_results` 为 1–10，缺省 5。`country` 为两位，`US` 存成 `us`。`language` 例如 `en`、`zh-CN`。一口价，不随条数变化，不能靠少要条数把冻结额降下来。默认单价与默认冻结额都是 `$0.005500`，`price_unit` 为 `call`。x402 只开这一条，付的就是冻结额。

```json
{"endpoint":"web.search.v1","input":{"query":"latest AI news","country":"US","language":"en","max_results":5}}
```

输出：`{query, results:[{title, url, snippet}]}`。

### `web.fetch.v1`

抓取一个 `http` 或 `https` URL。`formats` 的元素为 `markdown`、`text`、`links`。`timeout_sec` 为 10–3600，缺省 60。一口价。默认单价与默认冻结额 `$0.050000`，`price_unit` 为 `call`。不能靠少要字段把标价降下来。不支持 x402。

```json
{"endpoint":"web.fetch.v1","input":{"url":"https://example.com","formats":["markdown"],"timeout_sec":120}}
```

输出：`{url, title?, markdown?, text?, links?}`。

### `web.crawl.v1`

从 `start_url` 抓取站点正文。`max_pages` 为 1–20，缺省 5。`max_depth` 为 0–3，缺省 1。`format` 为 `markdown` 或 `text`。`timeout_sec` 缺省 300。按算力计费：内存 GB（不足 1 GB 按 1 GB）× 小时 × 算力单价 × 1.25，向上取整到分。默认算力单价 `$0.40` / GB·小时，默认 4 GB × 300 秒的冻结额约 `$0.17`。`max_charge` 收紧的是 `timeout_sec`，最短 10 秒（约 `$0.01`）。`price_unit` 为 `compute_unit`。

```json
{"endpoint":"web.crawl.v1","input":{"start_url":"https://docs.example.com","max_pages":10,"max_depth":2,"timeout_sec":300}}
```

输出：`{start_url, pages:[{url, title, content}]}`。

### `maps.places.v1`

按关键词和地点找场所。`query` 必填。可选 `location`、`language`。`max_results` 为 1–20，缺省 10。`timeout_sec` 缺省 120。单价默认 `$0.004000` / 条，默认冻结额 `$0.040000`（10 条）。公式 `unit_price * max_results`。低于最坏价时收紧 `max_results`，至少 1 条。`price_unit` 为 `result`。

```json
{"endpoint":"maps.places.v1","input":{"query":"coffee shop","location":"Austin, TX","max_results":10}}
```

输出：`{query, places:[{name, address, category, rating, reviews_count, phone, website, url, location:{lat, lng}}]}`。

### `maps.reviews.v1`

场所评论。`place_urls` 为 1–5 个地点链接。`max_reviews` 为 1–100，缺省每个地点 20。`sort` 为 `newest`、`mostRelevant`、`highestRanking`、`lowestRanking`。可选 `language`。`timeout_sec` 缺省 120。单价默认 `$0.000600` / 条。默认冻结额是 1 个地点 × 20 条 = `$0.012000`。公式 `unit_price * place_urls * max_reviews`。收紧的是 `max_reviews`，至少每个地点 1 条。不返回评论者信息。

```json
{"endpoint":"maps.reviews.v1","input":{"place_urls":["https://www.google.com/maps/place/..."],"max_reviews":20,"sort":"newest"}}
```

输出：`{reviews:[{place_url, rating, text, published_at, likes}]}`。

### `amazon.products.v1`

从商品或搜索链接读取商品信息。`start_urls` 为 1–3 个链接。`max_items` 为 1–20，缺省每个链接 10。可选 `country`（两位）。`timeout_sec` 缺省 180。单价默认 `$0.005000` / 条。默认冻结额 `$0.050000`（1 个链接 × 10 条）。公式 `unit_price * start_urls * max_items`。收紧的是 `max_items`。不含报价、卖家和配送时效。

```json
{"endpoint":"amazon.products.v1","input":{"start_urls":["https://www.amazon.com/s?k=mechanical+keyboard"],"max_items":10}}
```

输出：`{products:[{title, url, asin, price, currency, rating, reviews_count, brand}]}`。

### `youtube.videos.v1`

`search_queries` 与 `start_urls` 各最多 3 条，至少要有一边。`max_results` 为 1–20，缺省每个来源 10。`timeout_sec` 缺省 120。单价默认 `$0.004000` / 条。默认冻结额 `$0.040000`（1 个来源 × 10 条）。公式 `unit_price * (search_queries + start_urls) * max_results`。收紧的是 `max_results`。不含字幕、转写和摘要。

```json
{"endpoint":"youtube.videos.v1","input":{"search_queries":["golang tutorial"],"max_results":5}}
```

输出：`{videos:[{title, url, channel, views, published_at, duration}]}`。

### `llm.chat.v1`

先 `GET /agent/v1/models?kind=chat`，把返回的 id 放进 `model`。`messages` 必填，`content` 必须是字符串。`max_tokens` 可选；缺省按 4096 估算，prompt 估算至少 256。只有按模型 ratio 估价时才会收紧 `max_tokens`（至少 1）。完成倍率使成本不随 `max_tokens` 下降时，没有收紧区间，低于整笔估价即 403，并带 `min_charge`。固定模型价和目录底价回落不收紧。`price_unit` 为 `depends_on_model`。ratio 路径上 `price_detail` 之外，估价的单价单位是 `1m_prompt_tokens`。目录底价 `$0.010000` 不是单次价格。不支持 x402。没有单独的 `output_schema`；成功时 `result` 是聊天补全 JSON。

```json
{
  "endpoint": "llm.chat.v1",
  "input": {
    "model": "RETURNED_CHAT_MODEL_ID",
    "messages": [{"role": "user", "content": "Say hello"}],
    "max_tokens": 256
  }
}
```

### `image.generate.v1`

先 `GET /agent/v1/models?kind=image`。`prompt` 与 `model` 必填。`n` 为 1–10，可选 `size`、`quality`。冻结额已含 `n` 和尺寸 / 质量倍率，不能靠把 `n` 收紧来降价；`max_charge` 低于该估价时，在模型可用性检查之前返回 403 和 `min_charge`。`price_unit` 为 `depends_on_model`，`price_detail.multiplied_by` 为 `n` 与 `size_quality`。目录底价 `$0.040000`。未知字段不转发。创建后轮询；`succeeded` 时读 `result.result_urls`。

```json
{
  "endpoint": "image.generate.v1",
  "input": {
    "model": "RETURNED_IMAGE_MODEL_ID",
    "prompt": "A friendly robot holding a digital wallet",
    "n": 1
  }
}
```

### `video.generate.v1`

先 `GET /agent/v1/models?kind=video`。`model` 与 `prompt` 必填，`prompt` 最长 4000 字符。`duration`（整数秒）和 `size`（例如 `1280x720`）按该模型的参数校验，不支持的值是 `invalid_input`。未知字段拒绝。只做文生视频，一次一条。按次估价，不收紧时长。省略 `max_charge` 时冻结额就是这次估价；低于估价则 403，`min_charge` 等于该估价。算不出价直接 `invalid_input`，不会退回目录底价。目录底价 `$0.200000` 只用于没有模型信息时的展示。同一把密钥同时在飞的视频调用有上限（默认 3），超出是 `provider_busy`，可稍后重试。任务要跑几分钟，继续轮询同一条调用。`succeeded` 时读 `result.result_urls`。

```json
{
  "endpoint": "video.generate.v1",
  "input": {
    "model": "RETURNED_VIDEO_MODEL_ID",
    "prompt": "A golden retriever surfing a wave at sunset",
    "duration": 5
  }
}
```

## 7. 幂等与重试

每次调用都要 `Idempotency-Key`。缺少它是 `idempotency_key_required`。

一把键对应一次用户操作，直到终态。在第一次请求**之前**把键写进临时文件，重试时读回来。Agent 的 shell 经常每次命令都是新进程，写在命令行里的随机键下次就丢了，新键会再扣一次费。

```bash
IDEM_FILE="${TMPDIR:-/tmp}/dogpay-idem-<operation-slug>"
[ -s "$IDEM_FILE" ] || { uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid; } > "$IDEM_FILE"
```

终态之后再删这个文件。

重放返回已存储的结果，不重跑上游。`endpoint`、原始 `input`、`max_charge` 必须与第一次一致，否则 `idempotency_conflict`。收紧后的执行参数由服务端保存，重放时不要自己改 input 去「对齐收紧结果」。

`failed` 且 `error.retryable` 为 false、`unknown`、`manual_review`：不要换新键重开。`timed_out` 可以换新键。`failed` 且 `retryable` 为 true 的，换新键是一次新的付费尝试，需要用户确认。

`error.retryable` 为 true 的失败码：`provider_unavailable`、`provider_busy`、`upstream_timeout`、`insufficient_funds`、`start_lost`、`reservation_expired`、`execution_interrupted`。`cancelled`、`result_too_large`、`payment_failed` 为 false。

## 8. 账单

`billing.reserved_amount` 是冻结额。`charged_amount` 是实扣。`released_amount` 是退回的冻结额。`source` 为 `wallet` 或 `x402`。x402 在有支付记录时带 `payment_id`（`pay_…`）。facilitator 结算落链，或链上核对确认已收款，都会带 `tx_hash`。

不要把冻结额当成最终扣款。等到 `succeeded`、`failed`、`timed_out` 再向用户报告金额。`unknown` 期间钱还没定论（`billing.status` 为 `authorized`），继续等待。`manual_review` 要看账单：`billing.status` 仍是 `authorized` 时钱没定论；已经是 `settled` 时钱已收、结果没留下，不要重开。

## 9. 错误

运行时错误是顶层对象（`code`、`message`、`retryable`、`request_id`），不是 `{success, data}`。管理接口才用 HTTP 200 加 `success: false`。

带 `x402Version` 和 `accepts` 的 HTTP 402 是挑战，不是扣款。没有 `accepts` 的 HTTP 402 `insufficient_funds` 是钱包余额不足。HTTP 402 / 403 / 409 不会扣预付钱包，除非你拿到的是一条已经创建的调用对象（看 `id` 以 `inv_` 开头）：那表示调用已经发生，按它的 `status` 和 `billing` 处理，不要再付一次。

| 码 | 再试？ | 怎么做 |
|---|---|---|
| `agent_key_invalid` | 否 | 重新配置或轮换。HTTP 401 |
| `agent_key_paused` | 否 | 密钥已暂停。恢复或换一把。HTTP 403 |
| `agent_key_revoked` | 否 | 对外仍是 `agent_key_invalid` |
| `insufficient_funds` | 看 `retryable` | HTTP 402，无 `accepts`。带 `required_amount`、`available_amount`、`shortfall_amount`、`currency`、`wallet_id`、`deposit_hint`。充值或从主余额划入 |
| `policy_denied` | 否 | 读 `reason`：`endpoint`、`daily_limit`、`max_charge`（此时有 `min_charge`）、`compliance`、`x402_suspended`、`deposit_daily_limit`。HTTP 403 |
| `endpoint_not_found` | 否 | 刷新目录。HTTP 404 |
| `not_found` | 否 | 核对调用 id。HTTP 404 |
| `invalid_input` | 否 | 重新 Inspect，刷新模型。列表检查 `p` / `page_size` |
| `idempotency_key_required` | 否 | 带上 `Idempotency-Key` |
| `idempotency_conflict` | 否 | 恢复这把键对应的原始请求。HTTP 409 |
| `reservation_conflict` | 否 | 查看原来那次调用和账单。HTTP 409 |
| `run_not_cancellable` | 否 | 取消时 HTTP 409：已经交给上游，或这次调用不能取消。继续轮询 |
| `wallet_frozen` | 否 | 解冻或换钱包。HTTP 403 |
| `amount_too_large` | 否 | 降低金额 |
| `provider_unavailable` | 有界 | HTTP 503，`retryable: true`，带 `Retry-After`。若调用对象已是 `unknown` 或 `manual_review`，不要换新键。钱包路径且 `billing.status` 为 `released` 时，新尝试要新键 |
| `provider_busy` | 稍后，新键 | 上游名额在 60 分钟内没有空出来，或同步路径等过仍满。钱包路径未扣费。x402 若执行没开始，不结算 |
| `upstream_timeout` | 有界 | 先 GET 那条调用。`timed_out` 且已 `released` 可以换新键 |
| `reservation_expired` | 新键 | 冻结过期 |
| `start_lost` | 新键 | 调用被接受但没开始，未扣费 |
| `payment_required` | 签挑战 | HTTP 402 挑战体，不是普通错误对象 |
| `payment_invalid` | 新签名 | HTTP 402。`message` 说明原因。`validBefore` 距提交超过 480 秒也是这一码。重新取挑战再签，幂等键不变 |
| `payment_expired` | 新签名 | HTTP 402。`validBefore` 不够 300 秒或已过期 |
| `execution_interrupted` | 新键 | 调用对象上的 `error.code`。HTTP 503，`retryable` 为 true。执行没完成，或服务端在结算发起前中断（授权过期时认领仍是 claimed）。结果没落库，付款方没花钱（`billing.status` 为 `released`，实扣 0）。不判 `payment_failed`，不暂停密钥。换新的 `Idempotency-Key` 重开 |
| `payment_duplicate` | 否 | HTTP 409。这份授权已经绑定了另一笔付款或另一个请求。认领还在或已绑上调用时，同一把幂等键换新签的付款证明也是这一码，钱不动。例外：未绑定的认领过期释放后（`validBefore` 已过，或验证约 10 分钟仍没完成），同一个幂等键可以带新付款重开 |
| 结算在途 | 同一把幂等键 | 创建响应统一是 HTTP 202，`status` 为 `unknown`。用同一个 `Idempotency-Key` 重放，不要新签 |
| `settlement_unknown` | 同一份证明 | HTTP 402。结算结果不明。不要新签，用同一个幂等键重放 |
| `payment_failed` | 否 | 调用对象上的 `error.code`。HTTP 403。链上确认授权未用、付款方已在链上撤销授权、或 Used 事件被同 nonce 的另一笔授权用掉（回执里有付款方的 USDC 转出、但收款方 / 金额对不上），才会是这个码，这把密钥的 x402 暂停 24 小时。查不到链上状态（含回执取不到、确认数不足、回执里没有付款方的 USDC 转出）不是这个码 |
| `x402_daily_loss_exceeded` | 当日不要 | HTTP 503。当天（UTC）x402 关闭 |
| `result_too_large` | 否 | 结果超过约 15 MB，存不下。调用 `failed`。钱包路径按实际成本结算，封顶冻结额。x402 同样是这个码（不是 `invalid_input`），不结算，付款方不花钱。缩小请求，不要原样重试 |
| `cost_unknown` | 否 | 结果已交付但成本还不明。顶层 `status` 是 `unknown`，`billing.status` 仍是 `authorized`。不要换新键 |
| `cancelled` | 否 | 调用方取消，冻结额已退 |

可重试的即时错误带 `Retry-After`（通常 2 秒）和 `retryable: true`。重放一条已经落库的调用不会设置 `Retry-After`。

## 10. 大响应

HTTP 2xx 仍可能是已扣费的存根：

```json
{"agentpay_error":"chat_response_too_large","truncated":true}
```

`chat_response_too_large` 表示聊天正文超过约 4 MB。`response_invalid` 表示上游 2xx 正文无法解析。`upstream_status` 表示上游返回了非 2xx，状态在正文里。这些调用是终态并已结算；没有用户确认不要换新键重试。

完全存不下（约 15 MB 以上）时，`error.code` 为 `result_too_large`。钱包路径按实际成本结算，封顶冻结额。x402 不结算，付款方不花钱。

用控制台的调用记录核对能力、模型、调用 id、请求 id、账单和错误。不要记录完整密钥或 `Authorization`。

## 付费前必须具备

1. 从本文件的 URL 推导出的 Base URL。
2. 安全存放的可用 Agent Key（`DOGPAY_AGENT_KEY`）。
3. 绑定且有余额的钱包（`GET /agent/v1/key`）。
4. 要调用的能力在 `capabilities` 里。
5. 聊天、图片、视频使用列表返回的模型 id。
6. 用户授权或已经确定的预算。

不要索要用户的 DogRouter 密码、主账户口令或完整 Agent Key。
