Rate limits & quotas

Two independent limits protect the engine: a per-minute burst limit and a daily quota, both enforced per key and both reported on every response so your client never has to guess.

Limits exist so one runaway integration cannot degrade the data everyone else trades on. They are generous for real workloads (a screener refreshing every few seconds, a bot watching dozens of pairs), and the API is designed so that hitting them usually means there is a cheaper way to get the same data (see the strategies below).

The two limits

LimitDeveloper planWindowOn breach
Burst120 requests / minute / keyrolling 60 s429 with Retry-After seconds
Quota50,000 requests / day / keycalendar day (UTC)429 until the day rolls over

Both numbers are plan attributes, not hard-coded constants; higher tiers can carry higher limits. Your current values are always readable from the response headers, so write clients against the headers rather than the numbers on this page.

Reading your position

Every successful response reports where you stand on both limits:

Response headers on every call
X-RateLimit-Limit: 120        # burst ceiling, per minute
X-RateLimit-Remaining: 118    # left in the current minute
X-Quota-Daily-Limit: 50000    # daily ceiling
X-Quota-Daily-Used: 1204      # used today (UTC)
A 429 tells you exactly how long to wait
HTTP/1.1 429 Too Many Requests
Retry-After: 21

{ "error": "Rate limit exceeded: 120 requests per minute." }

Spending your budget well

50,000 requests is a lot (polling one endpoint every 2 seconds all day is ~43,000), but the point of the API surface is that you rarely need tight polling loops at all:

  • Prefer the WebSocket for anything live. One socket connection delivers live candles for up to 200 pair/timeframe pairs and live screener rankings, and it costs one REST call per 5 minutes (the connection token) instead of thousands of polls. See WebSocket stream.
  • Batch with the bulk endpoints. /v1/metrics?symbols=… returns up to 50 pairs per request, so a 50-pair watchlist sweep costs 1 request, not 50. /v1/levels and /v1/densities return the entire market in one call.
  • Match your poll rate to the data's cadence. Metrics recompute every 30 s and the market snapshot every 10 s; polling faster than that returns identical bytes. The natural rates: densities every 2 s, market every 10 s, metrics/rankings every 30 s, levels every 60 s.
  • Let alerts do the watching. If the question is "tell me when X happens", don't poll for it. Create an alert with a webhook and spend zero requests until it fires.

Handling 429 correctly

JavaScript: honor Retry-After, then resume
async function osiris(path) {
    while (true) {
        const res = await fetch("https://osiristrader.com/api" + path, {
            headers: { Authorization: "Bearer " + KEY },
        });
        if (res.status !== 429) return res.json();
        const wait = Number(res.headers.get("Retry-After") ?? 5);
        await new Promise(r => setTimeout(r, wait * 1000));
    }
}
Do not retry a 429 immediately or with fixed 1-second sleeps; you will spend your remaining daily quota on rejected requests. The Retry-After value is exact; honoring it costs nothing.
Limits are per key. If you genuinely operate several independent systems, give each its own key; they get separate budgets and separate usage dashboards. Creating many keys to multiply one system's budget is against the terms of service.