Rate Limiting
Learn how the API implements fair-use rate limits and how to work within them.
Overview
The Web3 Identity API uses endpoint-specific rate limiting to ensure fair access for all users while protecting infrastructure.
Key points:
- Different endpoint tiers have different limits
- Both per-minute and per-day limits apply
- Rate limit headers tell you exactly where you stand
- Exceeded limits return 429 status code
Rate Limit Tiers
Endpoints are categorized into tiers based on computational cost:
| Tier | Per Minute | Daily Multiplier | Use Case |
|---|---|---|---|
| Light | 120 | 2.0x | Simple lookups (ENS, price, gas) |
| Medium | 60 | 1.0x | Standard operations (most endpoints) |
| Heavy | 10 | 0.25x | Expensive operations (batch, analytics) |
| Auth | 20 | 0.5x | Authentication endpoints |
| Write | 5 | 0.1x | State-changing operations |
Daily limits:
- Free tier: 100 requests/day (base)
- SIWE authenticated: 200 requests/day (base)
- API key: 500+ requests/day (base)
- x402 payments: Unlimited
Daily limit per tier = Base limit × Daily multiplier
Example: Free tier user on Medium endpoints = 100 requests/day
Rate Limit Headers
Every API response includes headers showing your current usage:
HTTP/2 200 OK
X-RateLimit-Limit: 100 ← Daily limit (this tier)
X-RateLimit-Remaining: 95 ← Requests left today (this tier)
X-RateLimit-Reset: 2026-02-17T00:00:00.000Z ← When limit resets
X-RateLimit-Tier: medium ← Current endpoint tier
X-RateLimit-Tier-Limit: 60 ← Per-minute limit (this tier)
X-RateLimit-Tier-Remaining: 59 ← Requests left this minute
X-RateLimit-Global-Used: 6 ← Total requests today (all tiers)
X-RateLimit-Global-Limit: 300 ← Global daily cap
Reading Rate Limit Headers
In JavaScript:
const response = await fetch('https://api.web3identity.com/api/ens/resolve/vitalik.eth');
const remaining = response.headers.get('X-RateLimit-Remaining');
const limit = response.headers.get('X-RateLimit-Limit');
const reset = response.headers.get('X-RateLimit-Reset');
console.log(`${remaining}/${limit} requests remaining until ${reset}`);
if (remaining < 10) {
console.warn('Running low on requests!');
}
In curl:
curl -I https://api.web3identity.com/api/ens/resolve/vitalik.eth | grep X-RateLimit
Tier Examples
Light Tier (120/min)
Simple, cacheable lookups:
GET /api/ens/resolve/vitalik.eth # ENS resolution
GET /api/price/ethereum # Token price
GET /api/gas/ # Gas prices
GET /api/block/latest # Latest block
Why higher limits: Results are often cached, minimal computation.
Medium Tier (60/min)
Standard API operations:
GET /api/nft/vitalik.eth # NFT holdings
GET /api/farcaster/user/3 # Farcaster profile
GET /api/defi/protocol/aave # Protocol stats
GET /api/token/ethereum/0x1234... # Token info
Why moderate limits: Requires external API calls, some computation.
Heavy Tier (10/min)
Computationally expensive:
POST /api/batch # Batch operations
GET /api/analytics/ # Analytics queries
GET /api/profile/full/vitalik.eth # Full profile aggregation
GET /api/search/ # Search operations
Why strict limits: High computational cost, multiple data sources.
Auth Tier (20/min)
Authentication operations:
GET /api/auth/nonce # Get SIWE nonce
POST /api/auth/verify # Verify SIWE signature
Why moderate limits: Prevent brute force attacks.
Write Tier (5/min)
State-changing operations:
POST /api/subnames/aboutme.eth # Register subname
POST /api/ccip/register # CCIP registration
Why very strict: Writes are expensive, prevent abuse.
Handling Rate Limits
1. Check Headers Before Making Requests
class APIClient {
constructor() {
this.remaining = Infinity;
this.resetTime = null;
}
async request(url) {
// Wait if rate limited
if (this.remaining === 0) {
const waitMs = this.resetTime - Date.now();
if (waitMs > 0) {
console.log(`Rate limited, waiting ${waitMs}ms`);
await new Promise(resolve => setTimeout(resolve, waitMs));
}
}
const response = await fetch(url);
// Update rate limit state
this.remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
this.resetTime = new Date(response.headers.get('X-RateLimit-Reset')).getTime();
return response;
}
}
2. Handle 429 Responses Gracefully
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.warn(`Rate limited, retrying after ${retryAfter}s`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
3. Use Exponential Backoff
async function fetchWithBackoff(url) {
let delay = 1000; // Start with 1 second
while (true) {
const response = await fetch(url);
if (response.status !== 429) {
return response;
}
console.warn(`Rate limited, waiting ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, 60000); // Max 60 seconds
}
}
4. Batch Requests (When Possible)
Instead of:
// ❌ Multiple requests (uses 10 quota)
for (const name of names) {
await fetch(`/api/ens/resolve/${name}`);
}
Use batch endpoint:
// ✅ Single batch request (uses 1 quota)
await fetch('/api/batch', {
method: 'POST',
body: JSON.stringify({
requests: names.map(name => ({ path: `/api/ens/resolve/${name}` }))
})
});
Note: Batch endpoints are Heavy tier (strict limits) but more efficient overall.
Increasing Limits
Option 1: SIWE Authentication
Free, no credit card:
import { SiweMessage } from 'siwe';
// 1. Get nonce
const { nonce } = await fetch('/api/auth/nonce').then(r => r.json());
// 2. Create and sign message
const message = new SiweMessage({
domain: window.location.host,
address: walletAddress,
statement: 'Sign in to Web3 Identity API',
uri: window.location.origin,
version: '1',
chainId: 1,
nonce,
});
const signature = await wallet.signMessage(message.prepareMessage());
// 3. Verify and get session
const { success } = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature }),
}).then(r => r.json());
// Now all requests have 2x limits (200/day instead of 100/day)
Benefit: Doubles daily limit (100 → 200 requests/day)
Option 2: API Key
Register for API key:
// 1. Authenticate with SIWE (required)
// 2. Register API key
const { apiKey } = await fetch('/api/auth/apikey', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'My App' }),
}).then(r => r.json());
// 3. Use API key in requests
const response = await fetch('/api/ens/resolve/vitalik.eth', {
headers: { 'X-API-Key': apiKey }
});
Benefit: 5x daily limit (500+ requests/day)
Option 3: x402 Micropayments
Pay per request with USDC:
See x402 Payments for full integration guide.
Benefit: Unlimited requests, pay only what you use (~$0.01/request)
Best Practices
1. Cache Responses
const cache = new Map();
async function fetchCached(url, ttlMs = 60000) {
const cached = cache.get(url);
if (cached && Date.now() - cached.time < ttlMs) {
return cached.data;
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, { data, time: Date.now() });
return data;
}
// Use cached ENS resolution (valid for 1 minute)
const profile = await fetchCached('/api/ens/resolve/vitalik.eth', 60000);
2. Respect Tier Limits
- Use Light tier endpoints when possible (higher limits)
- Avoid Heavy tier in tight loops
- Batch requests when available
3. Monitor Your Usage
async function checkUsage() {
const response = await fetch('/api/usage');
const data = await response.json();
console.log(`Used: ${data.used}/${data.limit} (${data.remaining} remaining)`);
if (data.remaining < 20) {
console.warn('Running low on requests, consider upgrading');
}
}
4. Handle Errors Gracefully
try {
const response = await fetch('/api/ens/resolve/vitalik.eth');
if (response.status === 429) {
// Rate limited - wait and retry
const remaining = response.headers.get('X-RateLimit-Tier-Remaining');
if (remaining === '0') {
console.log('Per-minute limit hit, waiting 60s');
} else {
console.log('Daily limit hit, upgrade or wait until tomorrow');
}
}
const data = await response.json();
return data;
} catch (error) {
console.error('Request failed:', error);
// Fall back to cached data or show error to user
}
FAQ
Why do I see "Free Tier Exhausted"?
You've hit your daily limit for that endpoint tier. Options:
- Wait until midnight UTC (check
X-RateLimit-Resetheader) - Authenticate with SIWE (doubles limit)
- Register for API key (5x limit)
- Use x402 micropayments (unlimited)
Why are some endpoints stricter than others?
Endpoints that require expensive computation, external API calls, or write operations have stricter limits to ensure fair access and protect infrastructure.
Do rate limits apply to x402 payments?
No, paid requests (x402) are unlimited. Only free-tier requests count toward rate limits.
Can I check my usage without making requests?
Yes:
curl https://api.web3identity.com/api/usage
Returns your current usage stats without consuming quota.
What happens if I exceed both per-minute and per-day limits?
You'll receive a 429 response. Check the X-RateLimit-* headers to see which limit you hit:
X-RateLimit-Tier-Remaining: 0= Per-minute limit (wait 1 minute)X-RateLimit-Remaining: 0= Per-day limit (wait until reset time)
Error Response
When rate limited, you'll receive:
{
"error": true,
"code": "RATE_LIMITED",
"message": "Rate limit exceeded for medium operations. Limit: 60/min.",
"retryAfter": 42,
"tier": "medium",
"limit": 60,
"suggestion": "Please slow down and try again.",
"timestamp": "2026-02-16T12:00:00.000Z"
}
HTTP Status: 429 Too Many Requests
Retry-After header: Seconds until retry
Next Steps
- SIWE Authentication - Double your limits for free
- x402 Micropayments - Unlimited requests
- API Keys - Register for higher limits
Last updated: 2026-02-16