Skip to main content

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:

LimiterScopeDefaultOn trip
Edge IP throttleper source IP10 req/s, burst 20429 plain text + x-ratelimit-after
Per-account limiterper account50 actions / 1000 ms429 JSON SubmitResponse, code: "rate_limited"
Two different 429 bodies

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):

ScopeLive budget per 1000 ms window (2026-09-04)
per account4,000 actions
per account, cancels64,000 (separate cancel scope)
per API key16,000 actions
per IP16,000 actions
per market64,000 actions
per shard128,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).

SubjectDefault
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 size64 KiB
Private resend range10,000 frames per request
Market-feed replay on resume10,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

SubjectLimit
Logon attempts per minute5
Order entry messages per secondProvisioned by account/tier
Resend messages per requestCapped 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.maxPerBatch
  • actions.lowLatencyMaxActions
  • actions.maxActionTsSkewMs
  • rateLimits.windowMs
  • rateLimits.maxActionsPerWindow
  • rateLimits.maxActionsPerApiKeyWindow
  • rateLimits.maxActionsPerIpWindow
  • ingress.binaryMaxBodyBytes
  • backlog.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.