Skip to Main Content

Rate Limits

Request quotas per endpoint category, throttling behavior, and how to design around them.

Overview

Shoonya enforces per-second and per-day request quotas, scoped separately for order APIs, data APIs, and the WebSocket connect handshake. Limits exist to protect exchange connectivity shared across all users, not to discourage automation — design for them from the start rather than retrofitting backoff logic later.

Limits

CategoryLimitNotes
Order placement / modify / cancel~10 req/sec, burst-limitedApplies per user, across Place, Modify, Cancel. To register for a higher rate you must have an approved SEBI Algo ID and check the corresponding box on the IP Whitelisting Guide screen.
Market data (REST)~1 req/sec per instrumentUse WebSocket instead of polling for anything continuous.
WebSocket connect1 connection per sessionMultiplex all symbol subscriptions over the single socket.
Historical DataLower burst allowanceBatch date ranges instead of looping day-by-day.

Exact numeric ceilings are enforced server-side and may be tuned without notice — treat the table above as design guidance, not a contract, and always handle Rate_Limited defensively.

How throttling responds

json
{
  "stat": "Not_Ok",
  "emsg": "Rate_Limited: too many requests, retry after backoff"
}

Best practices

  • Batch symbol lookups and quote checks; don't issue one REST call per instrument in a loop.
  • Implement exponential backoff with jitter on Rate_Limited responses — a fixed retry interval synchronizes retries across your own threads and makes bursts worse.
  • Separate your order-management traffic from your market-data traffic so a data-heavy loop never starves order placement of its share of the quota.
  • Prefer the WebSocket feed for anything that needs to observe more than a handful of instruments continuously.

Python example

python
import time

def call_with_backoff(fn, *args, max_retries=5, **kwargs):
    delay = 0.5
    for attempt in range(max_retries):
        resp = fn(*args, **kwargs)
        if resp.get("stat") != "Not_Ok" or "Rate_Limited" not in resp.get("emsg", ""):
            return resp
        time.sleep(delay)
        delay *= 2
    raise RuntimeError("Exceeded retries after repeated Rate_Limited responses")

Notes

Vendor/partner integrations with higher aggregate volume should discuss dedicated quota tiers during onboarding — see For Vendors / Partners.