Errors & rate limits¶
| Status | Meaning |
|---|---|
401 |
Missing or invalid key. |
402 |
Broker SDK — seat_limit_reached. Provisioning a new user_ref at your seat cap. Deprovision an inactive client to free a seat, or raise the cap. Re-provisioning an existing user never hits this. |
403 |
Key revoked, account suspended, or — on the Broker SDK — the feature isn't enabled for your account ({"error": "feature_not_enabled", "feature": "stats"}). |
404 |
The user_ref was never provisioned. The most common day-one error: every per-user endpoint returns it. |
422 |
Request body or query validation failed. The response names the offending field. |
429 |
Rate limit exceeded — see below. |
500 |
Server error — log it and retry. |
503 |
ML inference is down. Pre-trade still returns a valid rules-only verdict with HTTP 200, so you rarely see this; method tells you which path answered. |
Rate limits¶
The ceiling depends on which credential you are using.
| Credential | Default | Scope |
|---|---|---|
Personal API key (tlyt_) |
100 / minute | Per caller |
Broker subscription key (tlyt_sk_) |
3,000 / minute | Across your whole book, not per client |
A broker account's limit is contracted and can be raised — GET /sdk/v1/account
reports effective_rate_limit, which is the number actually enforced, so read
that rather than assuming the default.
A pre-trade check is one call per intended order. At 3,000/minute that is 50 orders a second across your entire client base, so normal order flow does not approach it. What does approach it is a retry storm or a polling loop.
There is no Retry-After header
A 429 returns {"detail": "Rate limit exceeded. Try again later."} and no
timing header. Use your own backoff schedule — do not read Retry-After,
because it is not sent and your fallback value is what will actually be used.
On 429: exponential backoff, starting at 1s, doubling to a 30s cap.
import time, requests
def call_with_backoff(fn, max_attempts=5):
delay = 1.0
for attempt in range(max_attempts):
try:
return fn()
except requests.HTTPError as e:
if e.response.status_code != 429:
raise
# No Retry-After is sent, so the schedule is entirely ours.
time.sleep(delay)
delay = min(delay * 2, 30.0)
raise RuntimeError("rate limit not cleared after retries")
Never block an order on us
Pre-trade is decision support, not an order gate. On any error — 429, 500,
a timeout — let the order through. Your client's ability to trade must
never depend on our availability.