Guides
Rate Limits
API rate limits and how to handle 429 responses
halfin enforces rate limits to ensure fair usage and platform stability.
Limits
| Scope | Limit | Applies to |
|---|---|---|
| Per-merchant (API key) | 300 requests/min | All /v1/* merchant endpoints |
| Per-IP (public) | 120 requests/min | /v1/rates, /v1/currencies |
Limits reset on a rolling 60-second window.
Response Headers
Rate-limited responses include:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 12{
"error": {
"code": "rate_limited",
"message": "rate limit exceeded, retry after 12 seconds"
},
"meta": { "request_id": "req_abc123" }
}Handling 429 Responses
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
if (err.status !== 429 || attempt === maxRetries) throw err;
const retryAfter = parseInt(err.headers?.['retry-after'] || '60', 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
}
throw new Error('unreachable');
}
// Usage
const invoice = await withRetry(() =>
halfin.createInvoice({ currency: 'BTC', amount: '0.001' })
);Best Practices
- Use webhooks instead of polling for payment status where possible
- Cache
/v1/ratesand/v1/currenciesresponses (they change infrequently) - Batch operations server-side rather than making one API call per user action
- Implement exponential backoff using the
Retry-Afterheader value