Answers

If my agent retries a tool call after a timeout, will I be 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:

  • 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 at $0.02 a page, or any other endpoint such as X post search; the more expensive the endpoint, the more a missing key costs.

Code

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.

Updated