For AI

Rate limits: tiers, headers and backing off

okdata rate limits per account: requests a minute, calls in progress and daily allowances per tier, the X-RateLimit headers, rate_limited with retry_after_seconds, and backoff code.

Limits belong to the account, not the key: every API key of an account, and every MCP call made with them, draws on the same counts. A new account can send 60 requests a minute; from the first top-up it can send 600. Past a limit you get 429 rate_limited with retry_after_seconds, and nothing is charged.

The tiers

Tier When Requests a minute Calls in progress Daily spending allowance not_found answers a day
trial until the first top-up 60 5 $5 200
standard from the first top-up 600 20 $1,000 5,000
pro set by us on request 3,000 50 $10,000 20,000

GET https://api.okdata.online/v1/account shows your tier under rate, with per_minute, concurrent and free_per_minute. It is free.

Requests a minute: burst, then refill

The per-minute count is a token bucket. It starts full, so you may send the whole minute's allowance at once, and it refills steadily: one request a second on trial, ten on standard, fifty on pro. A script that sends 600 requests in a burst on standard must then wait for the refill; one that keeps to ten a second never hits the limit.

Every keyed request takes from the count, including one refused for a bad parameter. The free routes, /v1/account, /v1/calls and /v1/usage, have their own count of 120 a minute on every tier, so checking a balance or a receipt never eats into your calls. The catalog and status routes need no key.

Headers

Every answer to a request with a valid key carries:

Header Meaning
X-RateLimit-Limit The per-minute count of the route
X-RateLimit-Remaining Whole requests you can send right now
X-RateLimit-Reset When the count is full again, in Unix seconds
Retry-After Seconds to wait; sent with rate_limited and with any error that has a retry_after_seconds
curl -s -D - -o /dev/null "https://api.okdata.online/v1/douyin/trends/list" -H "Authorization: Bearer $OKDATA_API_KEY"
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1794470460

That request is a real call and costs $0.002. Watch X-RateLimit-Remaining to slow down before you are refused.

When you are refused

{
  "code": "rate_limited",
  "request_id": "0f6c2a8e-4b1d-4e0a-9a51-3c7d2b9e1f20",
  "message": "Too many requests, or too many calls in progress",
  "message_zh": "请求太多,或同时进行的调用太多",
  "charged": false,
  "retry": "yes",
  "retry_after_seconds": 1,
  "param": null,
  "problems": ["Too many requests for this account (600 a minute); retry in 1 s."],
  "reason": "rate",
  "docs_url": "https://okdata.online/docs/errors/#rate_limited"
}
  • reason: rate: past the per-minute count. Wait retry_after_seconds, which is also the Retry-After header.
  • reason: concurrency: the account already has as many calls running as its tier allows. retry_after_seconds is null; retry when one of your calls finishes.
  • cost_cap_exceeded: the account's charges today plus what this call could cost would pass the daily spending allowance.
  • uncharged_cost_limit: the account has had as many not_found answers today as its tier allows.

Both allowances open again at 00:00 UTC, and their retry_after_seconds counts down to it, so do not sleep on them in a loop. Every one of these is 429 with charged: false. The errors page has the full list.

Backing off

Retry rate_limited only. Wait retry_after_seconds when it is given, back off exponentially when it is null, and add a little jitter so parallel workers do not retry in step. Keep the same Idempotency-Key for every attempt so a retry can never be billed twice:

import os
import random
import time
import uuid
import requests

URL = "https://api.okdata.online/v1/xiaohongshu/notes/search"
HEADERS = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}

def search(query, attempts=5):
    headers = {**HEADERS, "Idempotency-Key": str(uuid.uuid4())}
    for attempt in range(attempts):
        body = requests.get(URL, headers=headers, params={"query": query}, timeout=60).json()
        if body["code"] != "rate_limited":
            return body
        wait = body["retry_after_seconds"] or min(2 ** attempt, 30)
        time.sleep(wait + random.uniform(0, 0.5))
    raise RuntimeError("still rate limited; slow down or ask for a higher tier")

print(search("sunscreen")["meta"]["billing"])

For steady volume, pacing beats retrying: on standard, ten requests a second with at most 20 in flight stays inside the per-minute and in-progress limits.

MCP shares the limit

The MCP server at https://api.okdata.online/mcp uses the same key, so its call tool takes from the same per-minute count and the same calls-in-progress slots as your HTTP calls, and account takes from it when a key is sent. find and describe need no key and are not counted. An agent and a script on one account therefore share one budget: a busy agent can slow a script down, and the other way round.

Asking for more

The pro tier, or limits that suit your workload, are set by hand. Write to hello@okdata.online with your account ID (from GET https://api.okdata.online/v1/account), the endpoints you call and the peak rate you expect. How keys and accounts relate is on the authentication page.

Updated

Connect once, then just ask.

Every new key comes with $0.10 to try it. One sentence connects your agent.

Start free
Connect your AI

Paste this into your agent

Read https://okdata.online/agents.md and set up okdata for me; my key is in the OKDATA_API_KEY environment variable.

Claude Code, Cursor, Codex and other agents that can open a link set themselves up from it.

MCP serverhttps://api.okdata.online/mcp