Rate Limits
The Transfer API implements rate limiting to ensure fair usage and maintain service quality for all clients.
Overview
Rate limits control the number of API requests you can make within a specific time window. Exceeding these limits results in temporary request rejection until the limit resets.
Why Rate Limits?
- Prevent abuse - Protect against excessive API usage
- Ensure availability - Maintain service quality for all clients
- Fair resource allocation - Distribute API capacity equitably
- System stability - Prevent overload and service degradation
Design your integration to stay well below rate limits with efficient request patterns and caching.
Rate Limit Values
Standard Endpoints
| Endpoint Category | Limit | Window | Per |
|---|---|---|---|
| Standard Operations | 100 requests | 1 minute | Client ID |
| Batch Operations | 10 requests | 1 minute | Client ID |
| Transfer Queries | 100 results | 1 page | Request |
Detailed Breakdown
- Standard Endpoints
- Batch Operations
- Query Limits
Limit: 100 requests per minute
Applies to:
GET /transfer/{id}- Get single transferPUT /transfer/{id}/reserve- Reserve transferPUT /transfer/{id}/provide-password- Provide passwordPUT /transfer/{id}/sign- Sign transferPUT /transfer/{id}/register- Register transferDELETE /transfer/{id}- Revoke transferPOST /transfer- Create single transfer
Example:
Window: 12:00:00 - 12:00:59
Allowed: 100 requests maximum
Reset: 12:01:00 (new window starts)
Calculation:
- Per client ID
- Rolling 60-second window
- Resets every minute
Limit: 10 requests per minute
Applies to:
POST /transfers- Create multiple transfersGET /transfers- List/query transfers (with filters)
Why lower limit?
- Batch operations are more resource-intensive
- Each request can affect multiple transfers
- Query operations can return large datasets
Example:
Window: 12:00:00 - 12:00:59
Allowed: 10 batch requests maximum
Each request can create up to 100 transfers
Total transfers per minute: up to 1,000
Best practice:
// ✅ Good: Create 500 transfers with 5 batch requests
const batches = chunkArray(transfers, 100);
for (const batch of batches) {
await createBatchTransfer(batch);
await sleep(6000); // 6 seconds between batches
}
// ❌ Bad: Create 500 transfers individually
for (const transfer of transfers) {
await createTransfer(transfer); // Will hit rate limit!
}
Limit: 100 results per page
Applies to:
GET /transferswith pagination
Pagination parameters:
limit(max: 100)offset(starting position)
Example request:
GET /transfer/rest/v1/transfers?limit=100&offset=0 HTTP/1.1
Example response:
{
"_links": {
"self": {"href": "/transfers?limit=100&offset=0"},
"next": {"href": "/transfers?limit=100&offset=100"}
},
"_embedded": {
"transfers": [
// ... 100 transfers
]
},
"total": 250,
"count": 100
}
Retrieving all results:
async function getAllTransfers(filters) {
const allTransfers = [];
let offset = 0;
const limit = 100;
let hasMore = true;
while (hasMore) {
const response = await getTransfers({ ...filters, limit, offset });
allTransfers.push(...response.transfers);
offset += limit;
hasMore = response.transfers.length === limit;
// Respect rate limits
if (hasMore) await sleep(1000);
}
return allTransfers;
}
Rate Limit Headers
Response Headers
Every API response includes rate limit information in headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1729426860
Content-Type: application/json
{...response body...}
Header Details
| Header | Type | Description | Example |
|---|---|---|---|
X-RateLimit-Limit | integer | Maximum requests allowed in window | 100 |
X-RateLimit-Remaining | integer | Requests remaining in current window | 87 |
X-RateLimit-Reset | integer | Unix timestamp when limit resets | 1729426860 |
Always check X-RateLimit-Remaining to avoid hitting limits.
Monitoring Rate Limits
async function makeRequestWithRateLimitCheck(url, options) {
const response = await fetch(url, options);
// Extract rate limit headers
const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
const reset = parseInt(response.headers.get('X-RateLimit-Reset'));
// Log rate limit info
console.log(`Rate limit: ${remaining}/${limit} remaining`);
// Warn if approaching limit
if (remaining < limit * 0.1) { // Less than 10% remaining
console.warn('Approaching rate limit!');
const waitTime = (reset - Math.floor(Date.now() / 1000)) * 1000;
console.warn(`Limit resets in ${waitTime}ms`);
}
return response;
}
Rate Limit Exceeded
Error Response
When you exceed the rate limit, you receive a 429 Too Many Requests response:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1729426920
Retry-After: 45
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"error_description": "API rate limit exceeded. Try again in 45 seconds.",
"retry_after": 45
}
Error Details
| Field | Type | Description |
|---|---|---|
error | string | Error code: rate_limit_exceeded |
error_description | string | Human-readable message |
retry_after | integer | Seconds to wait before retrying |
HTTP Status Code
- Status:
429 Too Many Requests - Retry-After header: Seconds until you can retry
- X-RateLimit-Reset header: Unix timestamp of reset time
Handling Rate Limits
Strategy 1: Respect Retry-After
- JavaScript
- PHP
- Python
async function makeRequestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limit exceeded
const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;
console.warn(`Rate limit exceeded. Waiting ${retryAfter}s...`);
if (attempt < maxRetries - 1) {
await sleep(retryAfter * 1000);
continue; // Retry
} else {
throw new Error('Rate limit exceeded, max retries reached');
}
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}
}
function makeRequestWithRetry($url, $options, $maxRetries = 3) {
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
$response = makeHttpRequest($url, $options);
if ($response->status === 429) {
// Rate limit exceeded
$retryAfter = $response->headers['Retry-After'] ?? 60;
error_log("Rate limit exceeded. Waiting {$retryAfter}s...");
if ($attempt < $maxRetries - 1) {
sleep($retryAfter);
continue; // Retry
} else {
throw new RateLimitException('Max retries reached');
}
}
if ($response->status >= 400) {
throw new HttpException("HTTP {$response->status}");
}
return json_decode($response->body);
}
}
import time
import requests
def make_request_with_retry(url, options, max_retries=3):
for attempt in range(max_retries):
response = requests.request(**options, url=url)
if response.status_code == 429:
# Rate limit exceeded
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limit exceeded. Waiting {retry_after}s...")
if attempt < max_retries - 1:
time.sleep(retry_after)
continue # Retry
else:
raise Exception('Rate limit exceeded, max retries reached')
response.raise_for_status()
return response.json()
Strategy 2: Proactive Rate Limiting
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
async waitIfNeeded() {
const now = Date.now();
// Remove old requests outside window
this.requests = this.requests.filter(
time => now - time < this.windowMs
);
// Check if at limit
if (this.requests.length >= this.maxRequests) {
// Calculate wait time
const oldestRequest = this.requests[0];
const waitTime = this.windowMs - (now - oldestRequest);
console.log(`Rate limit reached. Waiting ${waitTime}ms...`);
await sleep(waitTime);
// Recursive call to recheck
return this.waitIfNeeded();
}
// Record this request
this.requests.push(now);
}
async execute(fn) {
await this.waitIfNeeded();
return await fn();
}
}
// Usage
const limiter = new RateLimiter(100, 60000); // 100 req/min
async function createTransfer(data) {
return await limiter.execute(async () => {
return await apiClient.createTransfer(data);
});
}
Strategy 3: Request Queuing
class RequestQueue {
constructor(rateLimiter) {
this.queue = [];
this.processing = false;
this.rateLimiter = rateLimiter;
}
async enqueue(request) {
return new Promise((resolve, reject) => {
this.queue.push({ request, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const { request, resolve, reject } = this.queue.shift();
try {
await this.rateLimiter.waitIfNeeded();
const result = await request();
resolve(result);
} catch (error) {
reject(error);
}
}
this.processing = false;
}
}
// Usage
const queue = new RequestQueue(new RateLimiter(100, 60000));
// Enqueue requests
const results = await Promise.all([
queue.enqueue(() => createTransfer(data1)),
queue.enqueue(() => createTransfer(data2)),
queue.enqueue(() => createTransfer(data3))
]);
Strategy 4: Exponential Backoff
async function exponentialBackoff(fn, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status === 429) {
// Calculate backoff time: 2^attempt * 1000ms
const backoffTime = Math.min(Math.pow(2, attempt) * 1000, 32000);
console.log(`Attempt ${attempt + 1} failed. Backing off ${backoffTime}ms`);
if (attempt < maxRetries - 1) {
await sleep(backoffTime);
continue;
}
}
throw error;
}
}
}
// Usage
const transfer = await exponentialBackoff(
() => createTransfer(transferData)
);
Rate Limit Troubleshooting
Frequently Hit Rate Limits
Problem: Consistently receiving 429 errors
Common causes:
- Too many requests in short period
- Not implementing retry delays
- Polling instead of using callbacks
- Not batching operations
Solutions:
- Implement rate limit checking before requests
- Use exponential backoff for retries
- Switch to callback-based approach
- Use batch endpoints where possible
- Add request queuing
- Cache frequently accessed data
Rate Limit Reset Not Working
Problem: Still getting 429 after waiting for reset
Possible causes:
- Incorrect reset time calculation
- Using wrong time zone
- Not waiting full period
Solutions:
- Use
X-RateLimit-Resetheader (Unix timestamp) - Calculate wait time correctly:
const resetTime = parseInt(headers['X-RateLimit-Reset']);const now = Math.floor(Date.now() / 1000);const waitSeconds = Math.max(0, resetTime - now);
- Add buffer time (extra few seconds)
- Verify system clock is synchronized
Different Limits for Different Endpoints
Problem: Confused about which limits apply
Solution:
- Standard endpoints: 100/min
- Batch endpoints: 10/min
- Query pagination: 100 results/page
Track limits separately:
const limiters = {
standard: new RateLimiter(100, 60000),
batch: new RateLimiter(10, 60000)
};
// Use appropriate limiter
await limiters.standard.execute(() => getTransfer(id));
await limiters.batch.execute(() => createBatchTransfer(data));
Testing Rate Limits
Test Script
async function testRateLimits() {
console.log('Testing rate limits...');
const results = {
successful: 0,
rateLimited: 0,
errors: 0
};
// Make 110 requests (10 over limit)
for (let i = 0; i < 110; i++) {
try {
const response = await getTransfer('test-transfer-id');
results.successful++;
// Log remaining
const remaining = response.headers['X-RateLimit-Remaining'];
console.log(`Request ${i + 1}: ${remaining} remaining`);
} catch (error) {
if (error.status === 429) {
results.rateLimited++;
console.log(`Request ${i + 1}: Rate limited!`);
} else {
results.errors++;
}
}
}
console.log('Results:', results);
// Expected: ~100 successful, ~10 rate limited
}
Related Documentation
- API Fundamentals - Core API concepts
- Error Codes - Handle errors
- Batch Transfers - Efficient bulk operations
- Callbacks - Real-time notifications