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