Skip to main content

Rate Limits

The API enforces rate limits at the application layer so a single misbehaving client cannot starve others. Limits are grouped into tiers, applied as middleware on the relevant routes.

Tier summary​

TierLimitKeyed byApplies to
Auth10 requests / 15 minutesIPLogin, register, password reset
Password change3 requests / hourIPPUT /api/v1/auth/password, MFA disable, MFA backup-code regenerate
MFA verify10 requests / 15 minutesIPPOST /api/v1/auth/mfa/verify
Token refresh100 requests / 15 minutesIPPOST /api/v1/auth/refresh
Default200 requests / minutePrincipal (token prefix)Every other /api/v1/* endpoint

The default tier is sized to absorb realistic signal-write bursts (up to about 80 signals when a user pushes to both paper and live accounts) on top of concurrent reads. There is no signal-specific tier.

Authentication endpoints​

Authentication endpoints have strict limits to protect against brute-force and credential-stuffing attacks.

EndpointLimitWindow
POST /api/v1/auth/register10 requestsper 15 minutes
POST /api/v1/auth/login10 requestsper 15 minutes
POST /api/v1/auth/forgot-password10 requestsper 15 minutes
POST /api/v1/auth/reset-password10 requestsper 15 minutes
PUT /api/v1/auth/password (change password)3 requestsper hour
POST /api/v1/auth/mfa/verify10 requestsper 15 minutes
POST /api/v1/auth/mfa/disable3 requestsper hour
POST /api/v1/auth/mfa/backup-codes/regenerate3 requestsper hour
POST /api/v1/auth/refresh100 requestsper 15 minutes

Other authenticated endpoints​

Every endpoint under /api/v1 is covered by the default tier of 200 requests per minute per principal, regardless of whether the caller authenticates with a JWT, a device token, or an API key. The principal key is derived from the credential the caller presents, so each session, device, and API key has its own bucket.

Rate limit headers​

All rate-limited endpoints return standard headers on every response:

HeaderDescription
RateLimit-LimitMaximum requests allowed in the window
RateLimit-RemainingRequests remaining in the current window
RateLimit-ResetSeconds until the window resets

When the limit is exceeded, the API also sets Retry-After to the number of seconds before the window resets.

Rate limit exceeded​

When you exceed a rate limit the API returns 429 Too Many Requests with a structured body:

{
"error": "rate_limited",
"message": "Too many requests. Limit is 200 requests per minute.",
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 60
}

The error field is a machine-readable identifier (rate_limited). Use message for any text shown to a user, and retryAfter (in seconds) to compute when to retry.

Best practices​

Implement exponential backoff​

When receiving a 429 response, wait before retrying. Prefer the Retry-After header when present.

async function makeRequestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);

if (response.status === 429) {
const retryAfter = Number(response.headers.get("Retry-After")) || 0;
const delay = (retryAfter || Math.pow(2, attempt)) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}

return response;
}
throw new Error("Max retries exceeded");
}

Cache authentication tokens​

Reuse access tokens until they expire rather than re-authenticating on every request. Use POST /api/v1/auth/refresh to obtain a new access token without re-entering credentials.

Batch requests where possible​

For connected apps that need to sync data periodically, fetch all required data in one pass rather than making many small requests. For example, fetch trades and accounts together in your sync loop rather than making separate requests for each.