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 | Default budget (per 1000 ms window) |
|---|---|
| per account | ~2,000–4,000 actions |
| 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 throughput budget (separate cancel scope).
- Quote-replace charges only net-new legs — replacing a resting quote isn't double-counted.
A 429 on this lane returns a JSON body with a rateLimit budget view:
{ "ok": false, "error": "mm rate limit exceeded scope=account max_actions_per_window=2000",
"rateLimit": { "account": "0x1111…", "windowMs": 1000, "maxActionsPerWindow": 2000,
"usedActions": 2000, "remainingActions": 0, "resetInMs": 1000 } }
WebSocket limits
| Subject | Limit |
|---|---|
| Connections per IP | 32 |
| Connections per account | 8 (4 per agent) |
| Subscriptions per connection | 32 |
| Markets per connection | 16 |
| Control messages (subscribe/unsubscribe) per minute | 60 |
A stale resume cursor returns resume_required (private) / RESYNC_REQUIRED (public) 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.