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.
When you are limited
Section titled “When you are limited”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.
Handling 429
Section titled “Handling 429”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.
curl -sS --retry 5 --retry-delay 1 \ -H "Authorization: Bearer $COTES_API_KEY" \ "https://api.cotes.ai/v1/contests/active?series=KXBTC15M"Staying under the limit
Section titled “Staying under the limit”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.