Rate Limits
Rate limits protect the API from abuse and ensure fair usage for all customers.
Limits by Plan
- Free - 60 requests/minute, 1,000/day, burst limit 10
- Pro - 600 requests/minute, 50,000/day, burst limit 100
- Business - Custom limits
Rate Limit Headers
Every response includes rate limit information:
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1699123456
X-RateLimit-Daily-Limit: 1000
X-RateLimit-Daily-Remaining: 850
X-RateLimit-Limit- Requests allowed per minuteX-RateLimit-Remaining- Requests remaining this minuteX-RateLimit-Reset- Unix timestamp when limit resetsX-RateLimit-Daily-Limit- Requests allowed per dayX-RateLimit-Daily-Remaining- Requests remaining today
Rate Limit Exceeded
When you exceed the rate limit:
{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "Rate limit exceeded. Try again in 45 seconds."
},
"id": 1
}
HTTP Status: 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1699123456
Handling Rate Limits
Exponential Backoff
async function fetchWithRetry(request, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch('https://mainnet.subfrost.io/v4/jsonrpc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-subfrost-api-key': API_KEY
},
body: JSON.stringify(request)
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
return response.json();
}
throw new Error('Max retries exceeded');
}
Check Before Request
// Track remaining requests from headers
let remaining = 60;
async function makeRequest(request) {
if (remaining <= 0) {
throw new Error('Rate limit reached, please wait');
}
const response = await fetch('https://mainnet.subfrost.io/v4/jsonrpc', {
/* ... */
});
remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
return response.json();
}
Optimizing API Usage
Use Batch Requests
Instead of multiple individual requests:
// Bad - 3 requests
await rpc('btc_getblockcount', []);
await rpc('btc_getbestblockhash', []);
await rpc('btc_getblockchaininfo', []);
// Good - 1 request
await rpc([
{ jsonrpc: '2.0', method: 'btc_getblockcount', params: [], id: 1 },
{ jsonrpc: '2.0', method: 'btc_getbestblockhash', params: [], id: 2 },
{ jsonrpc: '2.0', method: 'btc_getblockchaininfo', params: [], id: 3 }
]);
Use Lua Scripts
Combine multiple operations server-side:
{
"jsonrpc": "2.0",
"method": "lua_evalscript",
"params": [
"local height = _RPC.btc_getblockcount()\nlocal hash = _RPC.btc_getblockhash(height)\nlocal block = _RPC.btc_getblock(hash)\nreturn block",
[]
],
"id": 1
}
One API call, but executes 3 RPC methods internally.
Cache Responses
Many blockchain data points don't change frequently:
const cache = new Map();
const CACHE_TTL = 10000; // 10 seconds
async function cachedRpc(method, params) {
const key = JSON.stringify({ method, params });
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const data = await rpc(method, params);
cache.set(key, { data, timestamp: Date.now() });
return data;
}
Per-Key Rate Limits
Each API key has its own rate limit bucket:
- Key A: 60/min independent of Key B
- Useful for isolating applications
- Track usage per-key in dashboard
Business customers can configure per-key limits.
WebSocket Connections
For real-time data, consider WebSocket subscriptions (Business):
- Lower rate limit impact
- Push-based updates
- Efficient for high-frequency data
Requesting Higher Limits
If you need higher limits:
- Pro Plan - 10x free tier limits ($45-$55/mo)
- Business - Custom limits, SLA, dedicated support ($90-$120/mo)
Contact [email protected] for Business pricing.