# If my agent retries a tool call after a timeout, will I be charged twice?

> Agents retry when an X or Douyin data call times out or the connection drops. How a retry key makes the second attempt replay the first result for free, on HTTP and MCP.

Canonical page: https://okdata.online/answers/agent-retry-charged-twice/

**Short answer: **Not if the retry carries the same idempotency key as the first attempt: okdata keeps each successful result for 24 hours and hands it back with no new charge, so a retried X search costs $0.006 once. Failed calls are never charged, and after a timeout you resend with the same key to find out whether the first attempt went through.

Here is how it usually goes wrong. The agent asks for page 3 of an X search, the client gives up after 60 seconds, the server finishes the call anyway, and the agent asks again. Without protection you pay for page 3 twice and the agent may process the same posts twice. The MCP spec itself is still working on this: an open issue shows a lost response leading to a re-executed tool call, and a proposal for request idempotency is under discussion. Until clients handle it, the fix lives in the API you call.

## How retries work on okdata

Send a random key with each logical request and reuse it on every retry of that request:

- **HTTP:** an `Idempotency-Key` header.
- **MCP:** the `idempotency_key` argument of the `call` tool.

The rules, from the [errors page](/docs/errors/):

- Same key and same request within 24 hours: you get the stored result with `meta.replayed: true`, `meta.billing.amount: "0"`, the original `request_id` and an `Idempotent-Replayed: true` header. No new charge.
- Same key, different request: rejected as `invalid_params`, so a reused key can never return the wrong data.
- Failed calls are not stored. Retrying a failure with the same key simply runs it again, and a failure was free anyway.
- A retry that arrives while the first attempt is still running gets `rate_limited` with `retry_after_seconds: 1`. Wait a second and send it again with the same key.

## What to do with each outcome

| Response | Charged | Next step |
|---|---|---|
| `ok` | yes, once | use it |
| `timeout` (504) | not known yet (`charged: null`) | resend with the same key, or look it up at `/v1/calls/{request_id}` |
| `upstream_busy`, `upstream_failed`, `internal_error` | no | retry with the same key |
| `rate_limited` (429) | no | wait `retry_after_seconds`, retry with the same key |
| `invalid_params`, `insufficient_balance` | no | fix the request or top up first |
| `not_found` | no | do not retry |

## What the key saves

| Job: 5 pages of X search | Calls billed | Cost |
|---|---|---|
| Each page retried 3 times, no key | 15 | $0.09 |
| Same retries with a key | 5 | $0.03 |

The same holds for [Douyin video search](/apis/douyin/videos/search/) at $0.02 a page, or any other endpoint such as [X post search](/apis/x/posts/search/); the more expensive the endpoint, the more a missing key costs.

## Code

```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())               # one key for this page, reused below
    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                       # same key: a finished call replays for free
        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("no answer after 4 tries")
```

If your agent talks MCP, tell it in the system prompt: "When you retry `call`, pass the same `idempotency_key` as the first attempt."

## Alternatives

A dedupe cache in your own code (store results by request hash) prevents double processing inside one process, but it cannot stop a second charge if the first response never arrived. Some APIs offer no idempotency at all; with those, keep timeouts generous and avoid automatic retries on calls that cost money. Request shapes and headers are on the [requests page](/docs/requests/).

## Endpoints used

- [X post search](https://okdata.online/apis/x/posts/search/) `x.posts.search`: $0.006 per call · about 20 posts per page
- [Douyin video search](https://okdata.online/apis/douyin/videos/search/) `douyin.videos.search`: $0.02 per call · about 20 videos per page

Updated 2026-11-12
