Skip to content

Rate limits

Requests are throttled per client IP address, across all endpoints and all API keys used from that address.

Limit Value
Sustained rate 5 requests per second
Burst 20 requests
Scope Client IP
Exempt GET /v1/health

The allowance is a token bucket: it refills at 5 per second up to a ceiling of 20, so a client that has been idle can issue 20 requests at once and then settles to 5 per second.

Over the allowance, the API returns 429 with application/problem+json:

{
"type": "https://api.cotes.ai/problems/rate-limited",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Retry shortly."
}

with these headers:

Header Example Meaning
Retry-After 1 Seconds to wait before retrying
X-RateLimit-Limit 20 Burst ceiling
X-RateLimit-Remaining 0 Requests left in the current allowance

These three headers are sent only on 429 responses — successful responses carry no rate-limit headers, so you cannot track remaining allowance from a 200. They are named in Access-Control-Expose-Headers, so browser clients can read them.

Wait for Retry-After seconds, then retry with exponential backoff and jitter. Treat 429 as retryable; do not count it as a signal that your key is invalid.

Terminal window
curl -sS --retry 5 --retry-delay 1 \
-H "Authorization: Bearer $COTES_API_KEY" \
"https://api.cotes.ai/v1/contests/active?series=KXBTC15M"

A contest gains at most one signal per minute, so polling /v1/contests/active every few seconds already sees every change. Faster polling returns the same payload and spends allowance.

For continuous updates, prefer streaming. Opening a stream costs one request against the allowance; the events that follow on that connection do not.