AArtery
On this page

Rate Limits & Quotas

Artery enforces two layered budgets:

  1. Tier quota — total REST calls per key per month (apps/api/src/auth/api-key.types.ts → TIER_LIMITS).
  2. Per-SKU quota — per-minute and per-day caps on specific usage types (api.request, ws.message, webhook.delivery, backtest.compute_min, …), resolved from the org's billing plan (apps/api/migrations/003_multitenancy.sql → plans).

A request must pass both layers. Either bucket can 429.

Per-key tier (monthly REST)

Set on key creation; default is free. From TIER_LIMITS in source:

TierREST calls / monthConcurrent WSNotes
free10,0001Sandbox-friendly; no backtest
builder500,0005Auth tier for paid prototypes
pro5,000,00050Standard production
enterpriseunlimitedunlimitedHeaders report unlimited instead of a numeric limit
bash# Pick the tier at key creation
curl -X POST "https://api.artery.questflow.ai/keys" \
  -H "Content-Type: application/json" \
  -d '{"name":"prod-bot","userId":"u-1","scopes":["read","stream"],"rateLimitTier":"pro"}'

Per-SKU plan quotas

Plan-level quotas (from the plans table seeded by 003_multitenancy.sql). -1 means unlimited. The builder plan is intentionally absent at the plan layer — it's a tier alias that maps to free for SKU purposes until M3-6.

Planapi.request /minapi.request /dayws.message /daywebhook.delivery /day
free6010,00050,0001,000
pro6001,000,00010,000,000100,000
enterprise6,000unlimitedunlimitedunlimited

The api.request SKU covers every authenticated REST handler. Per-key overrides live in ApiKey.quotaOverrides and merge on top of the plan.

Note

Plans also gate features (backtest, webhook.signing, history.maxDays, sso). Backtest endpoints /v1/me/backtest/replay-arbitrage and /v1/me/backtest/replay-negation return 403 forbidden_scope on free plans even when the request quota would otherwise allow them.

Backtest costs

Replay endpoints consume the backtest.compute_min SKU in addition to api.request. Plan limits aren't yet seeded for this SKU — current behavior is unlimited for pro / enterprise and disabled for free. A pro-tier 7-day arbitrage replay typically costs 4–8 compute-minutes.

Window constraints (enforced at the controller, not via quota):

ConstraintValue
to - from≤ 30 days
fill_window_ms≤ 86,400,000 (24h)
notional[0.01, 1_000_000]
min_net_edge[0, 1]

Response headers

Every successful authenticated REST response carries the tier-bucket state:

X-RateLimit-Limit:     500000
X-RateLimit-Remaining: 499234
X-RateLimit-Reset:     1780822653
X-RateLimit-Tier:      builder

X-RateLimit-Reset is a Unix timestamp marking the next quota reset (the start of next month UTC for monthly quotas).

For enterprise keys the headers report unlimited instead of a numeric limit.

429 handling

When you hit the tier or SKU limit:

httpHTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Reset: 1780822653
 
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded for tier \"free\" (10000/month)",
    "requestId": "req_..."
  }
}
Tip

Retry-After is in seconds. Most HTTP clients honor it automatically (e.g. axios-retry, Python requests with urllib3.Retry). Don't sleep Retry-After * 1000 ms.

The plan-layer 429 differs only in the message:

"message": "Quota exceeded: api.request (perMinute 60)"

Parse error.message to distinguish — both share code: rate_limited.

WebSocket subscription limits

The /v1/stream gateway tracks subscriptions per connection.

BoundValue
Active subs / connection1,000
Concurrent WS / keyper tier table above (free: 1 ... pro: 50)
Send-queue buffered bytes5 MiB — slow consumers are closed
Heartbeat idle timeout60s (server pushes {"type":"pong"} every 30s)

Hitting the per-connection cap:

jsonc{ "type": "error", "error": "max 1000 subs/conn" }

Hitting the per-key concurrent-WS cap returns close code 1008 with a policy_violation reason during upgrade.

Upstream provider limits

Independent of Artery's quota. These surface as code: upstream_rate_limited:

ProviderLimitNotes
Polymarket~600 req/min/IPCloudflare-level; aggressive
KalshiToken-bucket; 10 tokens/reqNo Retry-After — Artery backs off automatically
Hyperliquid1200 req/min/IP for /info; 100 orders/sec/walletTrade-side is per master wallet

Artery retries upstream_rate_limited on GET calls automatically — see error handling.

See also

Edit this page on GitHubLast updated
Rate Limits & Quotas · Artery API Docs