Guides

Rate Limits

API rate limits and how to handle 429 responses

halfin enforces rate limits to ensure fair usage and platform stability.

Limits

ScopeLimitApplies to
Per-merchant (API key)300 requests/minAll /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/rates and /v1/currencies responses (they change infrequently)
  • Batch operations server-side rather than making one API call per user action
  • Implement exponential backoff using the Retry-After header value