Real-Time.
One sheet.
Websocket connection shape, subscription channels, depth vs delta semantics, the rate-limit headers you’ll actually read, exponential-backoff math, and the error-class decision tree. Pin it; print it; come back to it.
Authentication: every request is HMAC-signed.
REST calls and websocket handshakes both carry the HMAC trio (lmts-api-key + lmts-timestamp + lmts-signature), computed per the scheme at docs.limitless.exchange/developers/authentication. The legacy X-API-Key / api_key shortcut is deprecated and no longer issued to new users.
HMAC handshake on the upgrade request, same three lmts-* headers as REST. See docs.limitless.exchange/developers/authentication.
// HTTP upgrade with HMAC headers wss://stream.limitless.exchange/v1 lmts-api-key: <apiKey> lmts-timestamp: 2026-05-07T18:23:11.482Z lmts-signature: <base64 HMAC-SHA256> // First server message { "type": "hello", "session": "…", "heartbeat_ms": 15000 }Heartbeat. Server sends a ping every 15s. Reply with pong. Two missed pings → close + reconnect.
One channel + market per subscribe. Multiplex by sending many subscribe ops on the same socket.
First message after subscribe: full snapshot. Subsequent: delta updates.
Sequence numbers. Each delta carries seq. If seq jumps, you missed a message → resubscribe for fresh snapshot.
X-RateLimit-Limit | req/window |
X-RateLimit-Remaining | req left |
X-RateLimit-Reset | epoch when window resets |
Retry-After | seconds; only on 429 |
TODO: confirm Limitless’ tier limits (default / signed / market-maker).
Defaults. base = 250ms, cap = 30s, maxAttempts = 8.
Jitter is non-optional. Without it, every client retries simultaneously and you create the reconnect storm you were trying to avoid.
Every state-changing POST gets a client-side nonce (UUID v4 or timestamp+random).
On 5xx: safely retry with the SAME idempotency key. The server dedupes; you don’t double-place.
Never reuse a key across different intents.
- 4xx (client). Don’t retry. Fix the request shape, log loudly.
401→ re-sign / refresh key.422→ validate. - 409 (conflict / nonce reuse). Refresh nonce, retry once. Hard-stop if it persists, you have a state bug.
- 429 (rate limited). Honour
Retry-After. Drop request rate by 50% in your local limiter for the next minute. - 5xx (server). Retry with exp backoff + jitter (idempotency key required). Cap at 8 attempts.
- Network / timeout. Retry as 5xx. If a write was in-flight, the idempotency key prevents double-execute.
- Websocket close. Reconnect with full backoff. Re-subscribe channels. Watch for sequence-number gaps in the next snapshot.
Real-time pitfalls
Cross-module- Reconnect storms. One disconnect → thousands of clients reconnect simultaneously. Always add jitter; cap per-host concurrent reconnects at 1.
- Missed sequence numbers. If
seqjumps, you missed a delta. Don’t patch silently, resubscribe to get a fresh snapshot. - Bot keeps polling after rate-limit. Honour
Retry-After. Local limiters should mirror the server’s view, not your wishful one. - Idempotency key reuse. Generate once per intent, never per retry. Reuse-across-intents is how you accidentally double-place.
- Confusing 429 with 503. 429 says “you’re too fast” (slow down). 503 says “I’m down” (back off). Treat differently.
- Buffering deltas during reconnect. Drop the buffer; the snapshot is the source of truth. Old deltas applied after a snapshot will corrupt your local book.