Skip to main content
The SURCHI Developer API enforces rate limits to ensure fair access and platform stability across all integrations. Limits are applied per API key on a rolling 60-second window. If your application exceeds its limit, subsequent requests will receive a 429 Too Many Requests response until the window resets. This page covers limit tiers, the rate limit headers returned on every response, and how to implement graceful handling for limit errors.

Rate Limit Tiers

Access tier is determined by your SURCHI token stake at the time of API key registration. Staking requirements and tier thresholds are defined in the protocol governance parameters.
TierRequests / minWebSocket ConnectionsHistorical DataAccess Requirement
Explorer6017 daysFree
Sentinel300390 daysRequires SURCHI stake
Elite1,20010Full historyRequires SURCHI stake
Explorer is available to all approved developer accounts at no cost and is suitable for development, testing, and low-volume applications. Sentinel and Elite tiers unlock higher throughput and extended historical data access in exchange for a SURCHI token stake. Staked tokens remain in your custody — they are not transferred to the protocol. Tier upgrades take effect within one epoch (approximately 24 hours) after your stake is confirmed on-chain.

Rate Limit Headers

Every API response includes the following headers to help your application track consumption and anticipate resets:
HeaderDescription
X-RateLimit-LimitThe maximum number of requests allowed in the current window for your tier
X-RateLimit-RemainingThe number of requests remaining in the current 60-second window
X-RateLimit-ResetUnix timestamp (seconds) at which the current window resets and the counter returns to the limit
Example headers on a Sentinel-tier response:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 247
X-RateLimit-Reset: 1705329660
When X-RateLimit-Remaining reaches 0, the next request will return a 429 response. Your application can use these headers to implement proactive throttling rather than waiting for a rejection.

Handling 429 Errors

When a 429 Too Many Requests response is received, the response will also include a Retry-After header indicating how many seconds to wait before retrying. Always respect this value. The following TypeScript utility implements an automatic retry with exponential backoff as a fallback when Retry-After is not present:
retry.ts
async function apiRequest(
  url: string,
  options: RequestInit,
  retries = 3
): Promise<Response> {
  const response = await fetch(url, options);

  if (response.status === 429 && retries > 0) {
    const retryAfter = response.headers.get('Retry-After') || '5';
    const delayMs = parseInt(retryAfter, 10) * 1000;

    console.warn(`Rate limited. Retrying in ${retryAfter}s... (${retries} retries left)`);
    await new Promise((resolve) => setTimeout(resolve, delayMs));

    return apiRequest(url, options, retries - 1);
  }

  return response;
}
For production applications, consider a more robust queue-based approach that tracks the X-RateLimit-Remaining header proactively and paces outgoing requests before hitting the limit, rather than relying on retry-after-rejection.

429 Response Body

{
  "error": "rate_limit_exceeded",
  "error_description": "You have exceeded your request limit of 60 requests per minute. Upgrade to a Sentinel or Elite tier for higher limits.",
  "retry_after": 12,
  "status": 429
}

WebSocket Rate Limits

WebSocket connections consume from a separate connection quota rather than the per-minute request limit. Sending subscription messages or receiving pushed events does not count against your REST request quota.
TierMax Concurrent ConnectionsMax Subscriptions per Connection
Explorer15
Sentinel320
Elite10Unlimited
Exceeding your connection quota causes the oldest connection to be terminated with close code 4029 (rate limit).
Cache intelligence signals client-side. Most signals remain valid and actionable for at least 60 seconds — polling the /v1/intelligence/signals endpoint more frequently than once per minute rarely provides additional value and burns through your request quota. For truly real-time use cases, use the WebSocket stream instead.