Skip to main content

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:

TierPer MinuteDaily MultiplierUse Case
Light1202.0xSimple lookups (ENS, price, gas)
Medium601.0xStandard operations (most endpoints)
Heavy100.25xExpensive operations (batch, analytics)
Auth200.5xAuthentication endpoints
Write50.1xState-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:

  1. Wait until midnight UTC (check X-RateLimit-Reset header)
  2. Authenticate with SIWE (doubles limit)
  3. Register for API key (5x limit)
  4. 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​


Last updated: 2026-02-16