Stellar API Rate Limits
and Scaling Guide
Understand how Stellar Horizon and Soroban RPC rate limits affect production applications, and learn strategies to scale your access reliably.
Why Rate Limits Matter in Production
Dropped Transactions
When your app hits a 429 response, pending transaction submissions may fail silently. Wallet users see errors, payment confirmations are delayed, and reconciliation gaps appear in your records.
Stale Data
If you cannot poll Horizon frequently enough, your application shows outdated balances, missed incoming payments, and delayed analytics. For trading applications, stale data means missed opportunities.
Service Outages
The public Horizon endpoint has no SLA. During network-wide events or surges in usage, rate limits tighten and availability drops. Applications without a fallback strategy experience downtime.
Public Endpoint Limitations
Horizon (horizon.stellar.org)
- x No guaranteed uptime or SLA
- x Rate limits vary and are not documented
- x No burst capacity for traffic spikes
- x No caching layer (every request hits the server)
- x Shared infrastructure with all other users
- x No support or escalation path
Soroban RPC (soroban-rpc.stellar.org)
- x Separate rate limits from Horizon
- x Contract simulation can be expensive
- x Event streaming may disconnect during load
- x No analytics or monitoring included
- x Limited historical data availability
- x Intended for development, not production
Production Scaling Strategies
Implement these strategies to handle rate limits gracefully and keep your application responsive under load.
Response Caching
EssentialCache Horizon responses with appropriate TTLs. Network state data can be cached for 5-30 seconds. Historical ledger data can be cached for hours or days since it never changes.
Exponential Backoff
EssentialWhen you receive a 429 response, wait before retrying. Start with a 1-second delay and double it on each retry, up to a maximum of 30 seconds. Include jitter to avoid thundering herd.
Request Batching
RecommendedCombine multiple account lookups or transaction queries into fewer API calls by using Horizon's pagination and filtering parameters effectively.
Cursor-Based Pagination
RecommendedUse Horizon's cursor parameter to page through large result sets efficiently. Store the last cursor to resume from where you left off without re-fetching data.
SSE Streaming
RecommendedUse Server-Sent Events for real-time data instead of polling. A single SSE connection replaces hundreds of polling requests per minute.
Health Monitoring
Best PracticeTrack your API usage, response times, and error rates. Set up alerts when you approach rate limits so you can scale proactively rather than reactively.
Public vs LumenQuery Rate Limits
| Feature | Public Horizon | LumenQuery Free | LumenQuery Pro |
|---|---|---|---|
| Monthly Requests | Varies (no guarantee) | 10,000/month | 100,000+/month |
| Burst Capacity | None | 10 req/sec | 50 req/sec |
| Response Caching | None | Redis (30s-10m TTL) | Redis (configurable) |
| Uptime SLA | None | Best effort | 99.9% |
| Horizon API | Yes | Yes | Yes |
| Soroban RPC | Separate endpoint | Unified access | Unified access |
| Analytics Layer | None | Basic metrics | Full analytics |
| SSE Streaming | Yes (may drop) | Yes (reliable) | Yes (guaranteed) |
| Support | Community only | Priority support | |
| Rate Limit Headers | Inconsistent | Always present | Always present |
Handling Rate Limits in Code
Implement exponential backoff with jitter to handle 429 responses gracefully. This pattern works with any Stellar API provider.
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let delay = 1000; // Start with 1 second
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.ok) {
return response.json();
}
if (response.status === 429) {
// Check for Retry-After header
const retryAfter = response.headers.get('Retry-After');
const waitTime = retryAfter
? parseInt(retryAfter) * 1000
: delay + Math.random() * 1000; // Add jitter
console.warn(
`Rate limited (attempt ${attempt + 1}/${maxRetries + 1}). ` +
`Waiting ${Math.round(waitTime / 1000)}s...`
);
if (attempt < maxRetries) {
await new Promise((r) => setTimeout(r, waitTime));
delay *= 2; // Exponential backoff
continue;
}
}
throw new Error(`API request failed: ${response.status}`);
}
}
// Usage with LumenQuery API
const ledger = await fetchWithRetry(
'https://lumenquery.io/api/analytics/network?range=24h'
);
// Usage with public Horizon (more likely to hit limits)
const account = await fetchWithRetry(
'https://horizon.stellar.org/accounts/GABC...',
{ headers: { 'Accept': 'application/json' } }
);Learn More
Frequently Asked Questions
What are the rate limits on the public Stellar Horizon API?
What happens when I exceed Stellar API rate limits?
How do I scale beyond public Stellar API rate limits?
Does self-hosting Horizon remove rate limits?
What rate limits does LumenQuery offer?
How does caching help with Stellar API rate limits?
Are Soroban RPC rate limits different from Horizon?
Scale Past Rate Limits
Get reliable Stellar API access with guaranteed rate limits, built-in caching, and unified Horizon + Soroban RPC endpoints. Free tier available.