Skip to main content
The StratAlerts Partner API enforces rate limits on a per-key basis to keep the service stable for all users. Limits are dynamic — throughput controls adjust with burst capacity during volatile sessions, so there is some flexibility during high-activity periods, but sustained high-frequency polling will still be throttled. Design your integration around the guidelines on this page to avoid unexpected errors.

REST rate limits

Rate limits are applied per API key. When you exceed the allowed request rate, the API returns an HTTP 429 Too Many Requests response with the standard error envelope:
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "rate limit exceeded"
  }
}

Handling a 429

When you receive a 429, back off and retry with a delay. A simple exponential backoff strategy works well:
import time
import httpx

def get_with_backoff(url: str, headers: dict, max_retries: int = 5) -> dict:
    delay = 1.0
    for attempt in range(max_retries):
        response = httpx.get(url, headers=headers)
        if response.status_code == 429:
            time.sleep(delay)
            delay *= 2
            continue
        response.raise_for_status()
        return response.json()
    raise RuntimeError("Rate limit retries exhausted")

Batch requests and symbol limits

Endpoints that accept a symbols query parameter accept up to 250 symbols per request. Symbols beyond the 250-symbol limit are silently dropped. Split large symbol lists across multiple requests if your universe exceeds this limit.
# Fetch prices for up to 250 symbols in one call
curl -s \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "https://app.stratalerts.com/api/market/v1/prices/latest?symbols=AAPL,MSFT,GOOGL,AMZN,TSLA"
The /instruments endpoint has its own limit parameter (default 50, max 200 per page) that controls how many instrument records are returned per request, separate from the 250-symbol batch cap.

WebSocket connection limits

You may have one active WebSocket connection per user account at any time. Opening a new connection automatically evicts the previous one — the older connection receives close code 4409 and is terminated.
Close code 4409 — connection replaced by a newer session
This means:
  • You cannot fan out to multiple persistent WebSocket clients under the same API key or account.
  • If your process restarts and reconnects, the new connection takes over immediately and the old one closes.
  • Monitor for close code 4409 in your client and treat it as a displacement event rather than an error.

Handling displacement

import asyncio
import json
import websockets

API_KEY = "YOUR_API_KEY"
WS_URL = "wss://app.stratalerts.com/ws/market/v1"

DISPLACEMENT_CODE = 4409

async def stream_with_reconnect():
    while True:
        try:
            headers = {"Authorization": f"Bearer {API_KEY}"}
            async with websockets.connect(WS_URL, additional_headers=headers) as ws:
                await ws.send(json.dumps({
                    "op": "subscribe",
                    "topics": [{"channel": "alerts.in_force"}],
                }))
                async for message in ws:
                    print(json.loads(message))
        except websockets.exceptions.ConnectionClosedError as exc:
            if exc.code == DISPLACEMENT_CODE:
                # Another session took over — wait before reconnecting
                await asyncio.sleep(5)
            else:
                raise

asyncio.run(stream_with_reconnect())

Best practices

Following these patterns keeps your integration efficient and avoids hitting limits unnecessarily.

Use WebSocket for live data

Subscribe to the quotes, states, and alerts.* WebSocket channels instead of polling REST endpoints in a loop. WebSocket push updates are more efficient and don’t consume REST quota.

Cache REST responses

Instrument metadata (/instruments) and candle history (/candles/{symbol}) change infrequently. Cache responses locally and refresh on a schedule rather than fetching on every request.

Batch symbol lookups

Use the symbols parameter to request data for multiple instruments in a single call instead of making one request per symbol. Stay under the 250-symbol limit per call.

Filter at the API

Use query parameters like timeframe, direction, window_minutes, and limit to narrow responses server-side. Fetching more data than you need wastes quota and increases latency.
For alert ingestion workflows, combine an initial REST poll on startup (/alerts/in-force) to get current state with a WebSocket alerts.in_force subscription for subsequent updates. This avoids repeated polling while keeping your state current after a reconnect.