API ReferenceRate Limits

Rate Limits

The Web3.Market License API enforces rate limits on all endpoints to ensure fair usage and protect the service from abuse. Rate limits are applied per IP address on a rolling window basis.

Limits per Endpoint

EndpointMethodRate LimitReason
/activatePOST30 requests/minuteWrite operation, infrequently called
/verifyGET120 requests/minuteHot path, called by deployed dApps on every load
/deactivatePOST30 requests/minuteWrite operation, infrequently called
/statusGET60 requests/minuteRead operation, moderate usage expected

The /verify endpoint has the highest rate limit because it is called by every deployed dApp at runtime. If you are operating multiple dApps behind the same IP address (e.g., a shared server), the combined request rate across all dApps must stay within the limit.

Rate Limit Headers

Every API response includes rate limit headers so you can track your current usage:

HeaderDescription
X-RateLimit-LimitThe maximum number of requests allowed in the current window
X-RateLimit-RemainingThe number of requests remaining in the current window
Retry-AfterIncluded only on 429 responses. The number of seconds to wait before retrying

Example Response Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
Content-Type: application/json

Rate Limit Exceeded Response

When you exceed the rate limit, the API returns a 429 Too Many Requests response:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
Retry-After: 34
Content-Type: application/json
{
  "success": false,
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please try again later."
}

The Retry-After header tells you how many seconds to wait before the rate limit window resets.

Best Practices

Cache Verification Results

The most effective way to stay within rate limits is to cache the result of /verify calls. Instead of calling the API on every page load, cache the response and only re-verify at defined intervals.

const CACHE_KEY = 'w3m_license_verified';
const CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
 
async function verifyLicense(licenseKey) {
  // Check cache first
  const cached = sessionStorage.getItem(CACHE_KEY);
  if (cached) {
    const { result, timestamp } = JSON.parse(cached);
    if (Date.now() - timestamp < CACHE_TTL) {
      return result;
    }
  }
 
  // Cache miss or expired — call the API
  const domain = window.location.hostname;
  const res = await fetch(
    `https://verify.web3.market/verify?license_key=${licenseKey}&domain=${domain}`
  );
  const data = await res.json();
 
  // Cache the result
  sessionStorage.setItem(CACHE_KEY, JSON.stringify({
    result: data,
    timestamp: Date.now(),
  }));
 
  return data;
}

Using sessionStorage means the cache is cleared when the browser tab is closed. For longer persistence, use localStorage, but be aware that stale cache entries may cause a delay in detecting license changes (e.g., suspension or revocation).

Implement Exponential Backoff

If you receive a 429 response, do not immediately retry. Wait for the Retry-After duration, and implement exponential backoff for subsequent retries:

async function fetchWithBackoff(url, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url);
 
    if (response.status !== 429) {
      return response;
    }
 
    if (attempt === maxRetries) {
      throw new Error('Rate limit exceeded after maximum retries');
    }
 
    const retryAfter = parseInt(
      response.headers.get('Retry-After') || '60',
      10
    );
    const backoff = retryAfter * 1000 * Math.pow(2, attempt);
    await new Promise(resolve => setTimeout(resolve, backoff));
  }
}

Avoid Unnecessary Calls

  • Do not call /verify on every route change within a single-page application. Verify once on app initialization and cache the result.
  • Do not call /status in a tight polling loop. If you need real-time updates, consider checking at longer intervals (e.g., every 5 minutes) or wait for the webhook feature to become available.
  • Use /activate and /deactivate only when the user explicitly triggers a domain change. These are not endpoints that need to be called programmatically on a schedule.

Monitor Your Usage

Check the X-RateLimit-Remaining header in API responses to understand your current consumption. If the remaining count is consistently low, consider implementing more aggressive caching or reducing your call frequency.