Rate Limits
All Senticore surfaces enforce rate limits. Limits depend on the surface and the client tier.
Trading-action limits (POST /api/v1/trading/actions)
The retail submit path is guarded by two independent limiters — you can trip either one:
| Limiter | Scope | Default | On trip |
|---|---|---|---|
| Edge IP throttle | per source IP | 10 req/s, burst 20 | 429 plain text + x-ratelimit-after |
| Per-account limiter | per account | 50 actions / 1000 ms | 429 JSON SubmitResponse, code: "rate_limited" |
The edge IP throttle returns a plain-text body (Too Many Requests! Wait for {n}s) — it is not
JSON. The per-account limiter returns a JSON SubmitResponse. Handle both.
Every submit response carries these headers:
X-RateLimit-Limit: 50
X-RateLimit-Remaining: 37
X-RateLimit-Reset-Ms: 1000 # ms UNTIL reset, not an epoch timestamp
Retry-After: 1 # whole seconds, only on 429 / 503
X-RateLimit-Reset-Ms is a countdown in milliseconds, not an absolute time. A value of 1000
means the window resets in one second.
Market-maker / BSL limits
The MM/BSL order-entry lanes run a sliding-window limiter with per-scope budgets (defaults;
read your live values from GET /api/v1/mm/limits / GET /api/v1/bsl/limits):
| Scope | Live budget per 1000 ms window (2026-09-04) |
|---|---|
| per account | 4,000 actions |
| per account, cancels | 64,000 (separate cancel scope) |
| per API key | 16,000 actions |
| per IP | 16,000 actions |
| per market | 64,000 actions |
| per shard | 128,000 actions |
Two properties that make quote churn cheap:
- Cancels cost 0 against the action budget (separate cancel scope).
- Quote-replace charges only net-new legs — replacing a resting quote isn't double-counted.
Every accepted BSL response also carries the budget view (usedActions,
remainingActions, resetInMs), so a quote engine can pace itself without
waiting for a 429. A 429 on this lane returns a JSON body with the same view:
{ "ok": false, "error": "mm rate limit exceeded scope=account max_actions_per_window=4000",
"rateLimit": { "account": "0x1111…", "windowMs": 1000, "maxActionsPerWindow": 4000,
"usedActions": 4000, "remainingActions": 0, "resetInMs": 1000 } }
Batch and book limits from the same endpoint at review time: maxPerBatch
1,000 actions, lowLatencyMaxActions 32, maxActionTsSkewMs 15,000, binary
body cap 8 MiB, 50,000 open orders per account, 10,000 per account and
market, 1,000 price levels per side and book (overflow prunes the worst level
instead of rejecting the request).
WebSocket limits
Every connection's limits are advertised in its session frame; the values
below are the gateway defaults (environment-overridable on the server).
| Subject | Default |
|---|---|
Server heartbeat interval (heartbeatMs) | 20 s |
Idle timeout (idleTimeoutMs) | 90 s |
Private auth deadline (authDeadlineMs) | 5 s |
Subscriptions per connection (maxSubscriptionsPerConnection) | 32 |
Markets per public connection (publicMaxMarketsPerConnection) | 16 |
Control messages per window (maxControlMessagesPerWindow / controlMessageWindowMs) | 60 per 60 s |
| Inbound message size | 64 KiB |
| Private resend range | 10,000 frames per request |
| Market-feed replay on resume | 10,000 events per channel |
Exceeding a subscription or control-message limit closes the socket with code
4409; a slow consumer gets a warning and then 4408. A stale resume cursor
returns resume_required / gap_fill rather than a silent gap — see
WebSocket → Reconnect.
FIX rate limits
| Subject | Limit |
|---|---|
| Logon attempts per minute | 5 |
| Order entry messages per second | Provisioned by account/tier |
| Resend messages per request | Capped by FIX_RESEND_MAX_MESSAGES |
BSL order-entry limits
Read the live limits endpoint before starting a quote engine:
GET /api/v1/bsl/limits
The response includes:
actions.maxPerBatchactions.lowLatencyMaxActionsactions.maxActionTsSkewMsrateLimits.windowMsrateLimits.maxActionsPerWindowrateLimits.maxActionsPerApiKeyWindowrateLimits.maxActionsPerIpWindowingress.binaryMaxBodyBytesbacklog.prebatchedMaxBatches
Per-account throughput can also be restricted by API-agent policy and builder
plan. Builder Basic currently maps to 600/min; Builder Pro maps to
3,000/min while active.
Existing clients may still read GET /api/v1/mm/limits; it is a compatibility
route for the same BSL limit envelope.
Cross-shard credit admission (separate reject reason)
Distinct from rate limits, an order can also be rejected by the cross-shard
credit broker - a per-(account, asset) admission control. This is a
separate admission-reject reason, not a throughput limit, so it surfaces as
a reject on the action rather than an HTTP 429. The broker is default off and
is only active when the engine runs more than one market shard
(engine_market_shard_count > 1) and a credit policy is enabled; it is a no-op
in the single-shard production default. When enabled it can reject for haircut
(MM_CROSS_SHARD_CREDIT_HAIRCUT_PPM), per-account cap
(MM_CROSS_SHARD_CREDIT_MAX_ACCOUNT_LOCKED), per-market cap
(MM_CROSS_SHARD_CREDIT_MAX_MARKET_LOCKED), or the fail-closed kill-switch
(MM_CROSS_SHARD_CREDIT_KILL_SWITCH). The environment variable prefix is
legacy; the policy now belongs to the BSL/risk admission model. Rejections are
exposed in metrics by reason.
Burst behavior
All surfaces use a token-bucket model with sustained rate plus a small burst allowance.