Rate Limits
Per-key and per-IP rate limits on the ZapAPI API — headers, defaults, and how to handle 429s gracefully.
ZapAPI applies rate limits to protect the platform and ensure fair use.
Limits are applied per API key (and per IP for public endpoints) and are
returned in standard RateLimit-* headers on every response.
Default limits
| Scope | Limit |
|---|---|
zap_test_ keys | 100 requests / minute |
zap_live_ keys | 600 requests / minute |
Invoice creation (POST /invoices) | 120 requests / minute |
| Public LNURL endpoints | 300 requests / minute per IP |
| Public payment-link pages | 600 requests / minute per IP |
If you need a higher limit for a legitimate workload (e.g., a high-volume marketplace), contact support — we can raise limits per project.
Rate limit headers
Every response carries the standard IETF RateLimit-* headers:
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 45RateLimit-Limit— the ceiling for the current window.RateLimit-Remaining— requests left in the current window.RateLimit-Reset— seconds until the window resets.
When you exceed the limit, the API returns 429 Too Many Requests with a
Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 23
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 23{
"status": "error",
"statusCode": 429,
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after 23 seconds."
}Handling 429s
The TypeScript SDK surfaces rate limiting via
ZapAPIError.isRateLimited:
try {
await zap.invoices.create(params);
} catch (err) {
if (err instanceof ZapAPIError && err.isRateLimited) {
const retryAfter = Number(err.payload.retryAfter ?? 1);
await sleep(retryAfter * 1000);
return retry();
}
throw err;
}For bulk workloads, proactively throttle by watching
RateLimit-Remaining and slowing down as it approaches zero, rather than
reacting to 429s after the fact.
Best practices
- Use webhooks, not polling. Polling
GET /invoices/:idto detect payment is the most common way to burn through a rate limit. Subscribe toinvoice.paidinstead — see Webhooks. - Batch where possible. List endpoints return up to 100 records per
request — prefer one
listcall over manygetcalls. - Cache aggressively. Public LNURL params and payment-link metadata are
cacheable; respect the
Cache-Controlheaders. - Use idempotency keys. Retrying a failed request with the same
Idempotency-Keydoes not count against your limit twice.
IP-based limits
Public endpoints (LNURL params, hosted checkout, public payment links) are limited per IP rather than per key. If your users share NAT egress (corporate networks, mobile carriers), they may share a budget — design public pages to degrade gracefully under 429s.