Rate limits
Design clients for Daily's enforced per-key and per-IP edge limits.
Daily enforces rate limits in Nginx before the application:
| Limit | Sustained rate | Burst capacity | Key |
|---|---|---|---|
| API key | 5 requests per second | 20 immediate excess requests | Public key identifier |
| Client IP | 20 requests per second | 40 immediate excess requests | Source IP |
Both limits apply. Requests within the burst are accepted without deliberate delay; once either bucket is exhausted, Nginx returns 429.
HTTP/2 429
Content-Type: application/json
Retry-After: 1
X-Request-ID: req_edgeratelimit01
Cache-Control: private, no-store{
"error": {
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"message": "Too many requests."
},
"request_id": "req_edgeratelimit01"
}The edge response uses Retry-After: 1. Temporary upstream failures use 503 service_unavailable with Retry-After: 5; treat them separately.
Retry strategy
Honor Retry-After, then use capped exponential backoff with random jitter. Retry 429 and transient 503 responses only. Do not retry authentication, permission, filter, or not-found errors automatically.
const apiKey = process.env.DAILY_API_KEY;
if (!apiKey) throw new Error('DAILY_API_KEY is required');
async function getWithBackoff(url: string, maxAttempts = 6): Promise<Response> {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const response = await fetch(url, {
headers: { Accept: 'application/json', Authorization: `Bearer ${apiKey}` },
});
if (response.status !== 429 && response.status !== 503) return response;
if (attempt === maxAttempts - 1) return response;
const retryAfter = Number(response.headers.get('retry-after'));
const baseMs = Number.isFinite(retryAfter) && retryAfter >= 0
? retryAfter * 1_000
: Math.min(30_000, 500 * 2 ** attempt);
const jitterMs = Math.floor(Math.random() * Math.max(1, baseMs * 0.25));
await new Promise((resolve) => setTimeout(resolve, baseMs + jitterMs));
}
throw new Error('Unreachable retry state');
}
const response = await getWithBackoff('https://api.godaily.co.il/v1/customers?limit=100');
const body = await response.json();
if (!response.ok) throw new Error(`Daily API ${response.status} (${body.request_id})`);
console.log(body.data.length);What counts
Every request reaching the public /v1 Nginx location consumes capacity, including each pagination page, browser preflight, and request that later fails authentication, permission, filtering, or lookup. Production integrations should use server-to-server requests and avoid browser preflights.
Prefer limit=100, keep concurrency below the sustained limits, and coordinate workers sharing a key or NAT address.

