Answers
How do I retry failed data API calls in a batch job without being billed twice?
Short answer
Give every request its own retry key, derived from the endpoint and parameters, and resend with that same key when a call times out or the connection drops. On okdata a repeat within 24 hours returns the stored result at no charge, so a 1,500-page Douyin comment job costs $3.00 however many retries it takes. Retry only errors marked as retryable, and never retry a not-found.
A nightly job pulls three pages of comments for each of 500 Douyin videos. Somewhere in those 1,500 calls a few will time out or lose the connection. The dangerous case is the one where you never saw a response: the call may have completed and been billed, or not. Retrying blindly can pay twice; not retrying leaves a hole in the data. Idempotency keys remove the dilemma.
How the key works
- Send an
Idempotency-Keyheader with each request. - The same key with the same request within 24 hours returns the stored result:
meta.replayed: true, a charge of"0", and anIdempotent-Replayed: trueheader. - If the first attempt failed, nothing was stored, so the retry simply runs again. Failed calls were never charged.
- If the first attempt is still running, you get
rate_limitedwithretry_after_seconds: 1. Wait and resend. - The same key with a different request is rejected as
invalid_params, so derive one key per request, not one per job.
How long a key lasts is a common question with every API that uses them, as in this one about payment keys. Here the answer is 24 hours.
Which outcomes to retry
| Code | Retry? |
|---|---|
rate_limited |
yes, after retry_after_seconds |
upstream_busy, upstream_failed, internal_error |
yes, with backoff |
timeout, or no response at all |
yes, with the same key |
not_found, invalid_params, unauthorized, insufficient_balance |
no, fix or skip |
A retry function to paste
import hashlib, json, os, time, requests
KEY = os.environ["OKDATA_API_KEY"]
def call(path, **params):
idem = hashlib.sha256(json.dumps([path, params], sort_keys=True).encode()).hexdigest()
h = {"Authorization": f"Bearer {KEY}", "Idempotency-Key": idem}
for attempt in range(6):
try:
body = requests.get(f"https://api.okdata.online{path}", headers=h, params=params, timeout=90).json()
except requests.RequestException:
time.sleep(2 ** attempt) # no response: resend with the same key
continue
if body["code"] == "ok":
return body
if body.get("retry") not in ("yes", "same_key"):
raise RuntimeError(f"{body['code']}: {body['message']}")
time.sleep(body.get("retry_after_seconds") or 2 ** attempt)
raise RuntimeError(f"gave up after retries: {path} {params}")
page = call("/v1/douyin/videos/comments/list", id="7301234567890123456", sort="hot")
The same function works for TikTok video comments and every other endpoint; Douyin video comments is $0.002 a page, and a replay is free.
Two more habits
- Keep each response's
request_id.GET https://api.okdata.online/v1/calls/{request_id}tells you how any single call settled. - Cap the attempts. A call that still fails after six tries usually means an outage; check the status and rerun the job later with the same keys.
The full list of codes, with which are charged and which are worth retrying, is on the errors page.
Updated