Rate Limits
Rate limit headers, 429 response format, and exponential backoff implementation
Rate Limits
505pay enforces per-tenant, per-endpoint rate limits to ensure platform stability. Limits are communicated via standard response headers on every API call.
Rate limit headers
Every API response includes these headers:
| Header | Type | Description |
|---|---|---|
X-RateLimit-Limit | integer | Maximum requests allowed in the current window |
X-RateLimit-Remaining | integer | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp | When the current window resets (UTC epoch seconds) |
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1746921600429 response
When you exceed the rate limit, the API returns 429 Too Many Requests:
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 2026-05-11T08:00:00Z.",
"request_id": "01j..."
}The response also includes the standard rate limit headers so you know exactly when to retry.
Do not poll the API in a tight loop. Repeated 429 responses may temporarily lower your rate limit bucket.
Default limits
| Endpoint category | Limit |
|---|---|
Token issuance (/token) | 60 req/min per IP |
| Payment creation | 120 req/min per tenant |
| Payment reads | 600 req/min per tenant |
| Webhook management | 60 req/min per tenant |
| Admin operations | 300 req/min per tenant |
| All other endpoints | 300 req/min per tenant |
Contact support to request higher limits for production integrations with high throughput requirements.
Exponential backoff with jitter
The recommended backoff strategy is exponential with full jitter to prevent thundering herd:
interface RateLimitError {
error: 'rate_limit_exceeded';
message: string;
request_id: string;
}
async function fetchWithBackoff(
url: string,
options: RequestInit,
maxAttempts = 5,
): Promise<Response> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
if (attempt === maxAttempts - 1) throw new Error('Rate limit exceeded after max retries');
// Prefer the server-provided reset time if available
const resetAt = res.headers.get('X-RateLimit-Reset');
let waitMs: number;
if (resetAt) {
// Wait until the reset window, plus a small jitter
const resetMs = Number(resetAt) * 1000;
const jitter = Math.random() * 1000;
waitMs = Math.max(resetMs - Date.now() + jitter, 0);
} else {
// Fallback: exponential backoff with full jitter
const base = Math.min(2 ** attempt * 1000, 60_000);
waitMs = Math.random() * base;
}
console.warn(
`Rate limited (attempt ${attempt + 1}/${maxAttempts}), ` +
`retrying in ${Math.round(waitMs / 1000)}s`
);
await new Promise(r => setTimeout(r, waitMs));
}
throw new Error('Unreachable');
}
// Usage
const res = await fetchWithBackoff(
'https://sandbox.505pay.link/v1/payments',
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
},
);import time
import random
import requests
from requests.exceptions import HTTPError
def fetch_with_backoff(
url: str,
method: str = 'GET',
max_attempts: int = 5,
**kwargs,
) -> requests.Response:
for attempt in range(max_attempts):
response = requests.request(method, url, **kwargs)
if response.status_code != 429:
response.raise_for_status()
return response
if attempt == max_attempts - 1:
raise HTTPError(f'Rate limit exceeded after {max_attempts} attempts', response=response)
# Prefer the server-provided reset time
reset_at = response.headers.get('X-RateLimit-Reset')
if reset_at:
wait = max(int(reset_at) - time.time(), 0) + random.uniform(0, 1)
else:
# Exponential backoff with full jitter
base = min(2 ** attempt, 60)
wait = random.uniform(0, base)
print(f'Rate limited (attempt {attempt + 1}/{max_attempts}), retrying in {wait:.1f}s')
time.sleep(wait)
raise RuntimeError('Unreachable')
# Usage
response = fetch_with_backoff(
'https://sandbox.505pay.link/v1/payments',
headers={'Authorization': f'Bearer {access_token}'},
)
payments = response.json()Best practices
- Read
X-RateLimit-Remainingproactively. Slow down before you hit zero rather than reacting to 429s. - Use
X-RateLimit-Resetas your retry target. It is more accurate than a fixed backoff duration. - Add jitter to any backoff. Multiple clients retrying simultaneously at the exact reset time causes a new burst.
- Batch reads where possible. Listing 100 payments in one request consumes 1 rate-limit slot instead of 100.
- Cache aggressively. Payment status, configuration, and reference data change infrequently — cache them with a short TTL instead of re-fetching on every render.
Was this page helpful?