# 智能体超时后重试工具调用，会不会被重复扣费？

> X、抖音等数据调用超时或断线后，智能体会自动重试。用同一个重试 key，第二次请求直接拿回第一次的结果、不再扣费，HTTP 和 MCP 都适用。

网页版：https://okdata.online/zh/answers/agent-retry-charged-twice/

**简短回答：**只要重试时带上和第一次相同的幂等 key，就不会：okdata 会把成功的结果保存 24 小时，重试时原样返回、不再扣费，所以一次 X 搜索重试几遍也只收 $0.006。失败的调用从不扣费；超时之后，用同一个 key 再发一次，就能知道第一次到底有没有成功。

典型的出错过程是这样的：智能体请求 X 搜索的第 3 页，客户端等了 60 秒放弃，服务器其实已经跑完了，智能体又请求一遍。没有保护的话，第 3 页你付了两次钱，智能体还可能把同一批帖子处理两遍。MCP 协议本身也还在讨论这件事：有个公开 issue 演示了响应丢失导致工具调用被重复执行，请求幂等的提案也还没定稿。在客户端解决之前，得靠你调用的接口来兜底。

## okdata 的重试规则

每个逻辑请求生成一个随机 key，这个请求的每次重试都用同一个：

- **HTTP：** 请求头 `Idempotency-Key`。
- **MCP：** `call` 工具的 `idempotency_key` 参数。

规则如下（详见[错误码页](/zh/docs/errors/)）：

- 24 小时内同一个 key、同样的请求：直接返回保存的结果，`meta.replayed: true`，`meta.billing.amount: "0"`，`request_id` 和第一次相同，响应头带 `Idempotent-Replayed: true`。不再扣费。
- 同一个 key、不同的请求：返回 `invalid_params`，所以 key 用错了也不会拿到别人的数据。
- 失败的调用不保存。用同一个 key 重试失败的请求，会重新执行一次；反正失败本来也没扣钱。
- 第一次还在执行时就来了重试，会收到 `rate_limited`，`retry_after_seconds: 1`。等一秒，用同一个 key 再发。

## 各种结果怎么处理

| 返回 | 是否扣费 | 下一步 |
|---|---|---|
| `ok` | 扣一次 | 直接用 |
| `timeout`（504） | 暂时未知（`charged: null`） | 用同一个 key 重发，或到 `/v1/calls/{request_id}` 查 |
| `upstream_busy`、`upstream_failed`、`internal_error` | 不扣 | 用同一个 key 重试 |
| `rate_limited`（429） | 不扣 | 等 `retry_after_seconds` 秒，用同一个 key 重试 |
| `invalid_params`、`insufficient_balance` | 不扣 | 先改请求或充值 |
| `not_found` | 不扣 | 不要重试 |

## 带不带 key 差多少

| 任务：X 搜索翻 5 页 | 计费次数 | 费用 |
|---|---|---|
| 每页重试 3 次，没带 key | 15 | $0.09 |
| 同样的重试，带 key | 5 | $0.03 |

[抖音视频搜索](/zh/apis/douyin/videos/search/)每页 $0.02，[X 帖子搜索](/zh/apis/x/posts/search/)和其他接口也是同样的道理；接口越贵，漏带 key 的代价越大。

## 代码

```python
import os, time, uuid
import requests

URL = "https://api.okdata.online/v1/x/posts/search"
H = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}

def search_page(query, cursor=None):
    key = str(uuid.uuid4())               # 这一页一个 key，下面重试都用它
    params = {"query": query, "sort": "latest", **({"cursor": cursor} if cursor else {})}
    for attempt in range(4):
        try:
            body = requests.get(URL, headers={**H, "Idempotency-Key": key},
                                params=params, timeout=60).json()
        except requests.Timeout:
            continue                       # 同一个 key：已完成的调用会免费重放
        if body["code"] == "ok":
            return body
        if body["retry"] in ("yes", "same_key"):
            time.sleep(body.get("retry_after_seconds") or 2 ** attempt)
            continue
        raise RuntimeError(f"{body['code']}: {body['message']}")
    raise RuntimeError("重试 4 次仍无结果")
```

如果你的智能体走 MCP，在系统提示词里写一句："重试 `call` 时，传和第一次相同的 `idempotency_key`。"

## 其他办法

自己在代码里按请求内容做去重缓存，可以避免同一个进程里重复处理，但如果第一次的响应根本没收到，它挡不住第二次扣费。有些接口完全不支持幂等，用那类接口时，超时设长一点，花钱的调用不要自动重试。请求格式和请求头说明见[请求与响应](/zh/docs/requests/)。

## 用到的接口

- [X 帖子搜索](https://okdata.online/zh/apis/x/posts/search/) `x.posts.search`：$0.006/次 · 每页约 20 条帖子
- [抖音视频搜索](https://okdata.online/zh/apis/douyin/videos/search/) `douyin.videos.search`：$0.02/次 · 每页约 20 条视频

更新于 2026-11-12
