Rate Limits
Understand minute throttling, monthly quota enforcement, and response headers.
The API applies two controls to authenticated /v1/* requests.
| Control | Current free-key policy | Enforcement |
|---|---|---|
| Requests per minute | 60 | Cloudflare rate-limit binding keyed by API-key database ID |
| Requests per month | 10,000 | API-key usage fields in D1, evaluated before minute throttling |
The private key-creation CLI currently supports the free plan with those defaults. The database stores per-key policies, so another key may have different configured values.
Response headers
Accepted authenticated requests include:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-Request-Id: req_01J7EXAMPLEX-RateLimit-Remaining is best effort. The Cloudflare binding returns allow or deny, not an exact remaining count. The current middleware therefore returns limit - 1 on every accepted request and 0 when rejected. Do not treat it as a precise counter.
Minute throttling
A rejected minute-limit request returns status 429, error code RATE_LIMITED, and:
Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0The Cloudflare rate limiter is location-local and intentionally permissive. The source does not guarantee a globally exact rolling window.
Monthly quota
When monthly usage is already at or above the key's policy, the API returns status 429 with MONTHLY_QUOTA_EXCEEDED. Retry-After is the number of seconds until the first day of the next UTC month.
Monthly usage is recorded asynchronously after authenticated requests. The source does not guarantee strict single-request precision during concurrent traffic.
Retry strategy
Honor Retry-After, add a small random delay for concurrent workers, and retry only idempotent GET requests. Use the error code to distinguish a short minute throttle from an exhausted monthly quota.
Policy alignment TODO
The Worker binding is globally configured for 60 requests per 60 seconds, while response headers use each key's database policy. Behavior for a future key whose requests_per_minute differs from 60 is not defined by the current binding configuration.