Rate Limits
Limits protect the API and ensure fair usage.
Rate Limit Summaryโ
The API enforces both daily and per-minute limits. You must stay within both.
Daily Limits (Rolling 24-Hour Window)โ
| Tier | Daily Calls | How to Get |
|---|---|---|
| Anonymous | 100 | Default |
| SIWE Authenticated | 200 | Sign in with Ethereum |
| API Key | 500 | Purchase credits |
Daily limits use a rolling 24-hour window โ each request expires exactly 24 hours after it was made.
Per-Minute Limits (Rolling 60-Second Window)โ
| Tier | Requests/Minute | Burst Buffer |
|---|---|---|
| Anonymous | 30 | โ |
| SIWE | 30 | โ |
| API Key | 60 | Higher burst allowed |
Per-minute limits use a rolling 60-second window โ the limit is always based on requests made in the last 60 seconds.
Example: Even with 100 daily calls, you cannot make more than 30 requests in any 60-second window.
API Key holders get 60 requests/minute (2x the standard limit) in addition to 500 daily calls.
Rate Limit Trackingโ
By IP vs By Identityโ
| Tier | Tracking Method |
|---|---|
| Anonymous | By IP address |
| SIWE Authenticated | By wallet address |
| API Key | By API key |
Anonymous rate limits are tracked per IP. If you're behind a shared NAT or proxy, you may share limits with other users.
Rate Limit Headersโ
Every API response includes rate limit headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1707440400
Content-Type: application/json
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed in period | 100 |
X-RateLimit-Remaining | Requests remaining in current window | 73 |
X-RateLimit-Reset | Unix timestamp when limit resets | 1707440400 |
Parsing Reset Timeโ
const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'));
const resetDate = new Date(resetTime * 1000);
const secondsUntilReset = Math.ceil((resetTime * 1000 - Date.now()) / 1000);
console.log(`Rate limit resets at ${resetDate.toISOString()}`);
console.log(`Seconds until reset: ${secondsUntilReset}`);
Check Your Usageโ
curl https://api.web3identity.com/api/usage
{
"used": 27,
"limit": 100,
"remaining": 73,
"resetsAt": "2026-02-09T00:00:00Z",
"tier": "anonymous",
"perMinute": {
"used": 5,
"limit": 30,
"remaining": 25
}
}
429 Too Many Requestsโ
When rate limited, the API returns a 429 status with details:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1707440400
{
"error": true,
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Please wait before making more requests.",
"retryAfter": 60,
"limit": 100,
"remaining": 0,
"resetAt": "2026-02-09T00:00:00Z",
"limitType": "per_minute"
}
Response Fieldsโ
| Field | Type | Description |
|---|---|---|
retryAfter | number | Seconds to wait before retrying |
limit | number | Your rate limit |
remaining | number | Requests remaining (always 0 when limited) |
resetAt | string | ISO timestamp when limit resets |
limitType | string | Which limit was hit: per_minute or daily |
Retry-After Headerโ
The Retry-After header indicates the minimum seconds to wait:
const response = await fetch(url);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;
console.log(`Rate limited. Retrying in ${retryAfter} seconds...`);
await sleep(retryAfter * 1000);
return fetch(url); // Retry
}
Always respect the Retry-After header. Making requests before this time will result in continued 429 responses and may trigger stricter limits.
Implementing Rate Limit Handlingโ
Basic Retry Logicโ
async function fetchWithRateLimit(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
if (attempt === maxRetries) {
throw new Error('Rate limit exceeded after max retries');
}
const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;
console.log(`Rate limited. Waiting ${retryAfter}s (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response;
}
}
Proactive Rate Limitingโ
class RateLimitedClient {
constructor() {
this.remaining = null;
this.resetAt = null;
}
async fetch(url, options = {}) {
// Check if we know we're out of quota
if (this.remaining === 0 && this.resetAt) {
const waitMs = this.resetAt - Date.now();
if (waitMs > 0) {
console.log(`Waiting ${Math.ceil(waitMs/1000)}s for rate limit reset...`);
await new Promise(resolve => setTimeout(resolve, waitMs));
}
}
const response = await fetch(url, options);
// Update rate limit tracking from headers
this.remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
const resetTimestamp = parseInt(response.headers.get('X-RateLimit-Reset'));
this.resetAt = resetTimestamp ? resetTimestamp * 1000 : null;
return response;
}
}
Tipsโ
- Authenticate โ SIWE doubles daily limits
- Use batch endpoints โ One call for multiple items
- Cache responses โ Price data valid for 30s
- Use API Keys โ For high-volume needs
- Monitor headers โ Track remaining requests proactively
Free Endpointsโ
These endpoints never count against rate limits:
| Endpoint | Purpose |
|---|---|
/api/health | Server health check |
/api/usage | Check your rate limit status |
/api/sources/health | External source status |
/api/cache/stats | Cache statistics |
/docs | Swagger documentation |
/.well-known/x402 | x402 payment discovery |
/api/free/* | All free-tier endpoints |
# These never count against limits
curl https://api.web3identity.com/api/health
curl https://api.web3identity.com/api/usage
Rate Limit Tiers Summaryโ
| Tier | Daily | Per Minute | Tracking | Payment After Limit |
|---|---|---|---|---|
| Anonymous | 100 | 30 | By IP | x402 micropayment |
| SIWE | 200 | 30 | By wallet | x402 micropayment |
| API Key | 500 | 60 | By key | Prepaid credits |
Relatedโ
- Error Handling โ Understanding 429 and other errors
- API Keys โ Higher limits with prepaid credits
- x402 Payments โ Pay-per-request after limits