Rate Limits

URAPIS enforces rate limits to ensure fair usage across all consumers and protect upstream servers from excessive load.

How Rate Limits Work

Each API provider on URAPIS configures rate limits for their subscription plans. When a consumer exceeds the rate limit, the URAPIS Gateway intercepts the request before it reaches the provider’s server and returns a 429 Too Many Requests response. Because the request is blocked at the gateway level, the consumer’s wallet balance is not charged.

However, rate limits can also be enforced by the upstream provider’s own infrastructure, independently of URAPIS. These two types of 429 errors have different implications for your wallet balance.

Two Sources of 429 Errors

AspectURAPIS GatewayUpstream Provider
Where the limit is enforcedURAPIS Gateway, before the request reaches the providerThe provider’s own server, after the request passes through URAPIS
Wallet charged?No — the request never left the gatewayYes — the request reached the provider’s infrastructure
Response body formatStructured JSON: {"error":true,"code":"RATE_LIMITED","message":"..."}Provider-dependent (JSON, HTML, or plain text)
Can you control it?Yes — slow down your request rateNo — the provider defines their own internal limits

Identifying the Source of a 429

The most reliable way to distinguish between the two sources is by inspecting the response body.

URAPIS Gateway 429

If the response body contains "code": "RATE_LIMITED", the error came from the URAPIS Gateway and your wallet was not charged:

{"error": true, "code": "RATE_LIMITED", "message": "API quota or rate limit exceeded"}

Upstream Provider 429

If the response body does not contain "code": "RATE_LIMITED" (or the body is not JSON at all), the error came from the upstream provider and your wallet was charged.

Why Upstream 429s Still Deduct Balance

URAPIS performs wallet debiting atomically before forwarding a request to the upstream server. If the request successfully reaches the upstream server (i.e., it passed the URAPIS rate limiter), the transaction is counted. A 429 response from the upstream does not trigger a refund because, from URAPIS’s perspective, the request was successfully delivered.

Balance refunds only occur when:

  • A network error or timeout prevents the request from reaching the upstream server
  • The upstream server returns a 5xx (500–599) error, indicating an internal failure

Client-Side Handling

Exponential Backoff

Exponential backoff is the recommended strategy for handling both types of 429 errors. It prevents you from sending requests too quickly and reduces wasted balance from upstream 429s.

async function fetchWithBackoff(url, options = {}, maxRetries = 5) {
  const baseDelay = 1000; // 1 second

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      return response;
    }

    const body = await response.clone().json().catch(() => ({}));

    if (body.code === "RATE_LIMITED") {
      console.warn("Rate limited by URAPIS Gateway — balance safe, retrying...");
    } else {
      console.warn("Rate limited by upstream provider — balance charged, retrying...");
    }

    if (attempt === maxRetries) {
      throw new Error(`Max retries (${maxRetries}) exceeded`);
    }

    const delay = baseDelay * Math.pow(2, attempt);
    const jitter = delay * 0.1 * Math.random();
    await new Promise(resolve => setTimeout(resolve, delay + jitter));
  }
}

Rate Limit Awareness (Optional)

Some upstream providers include rate limit headers in their responses. If your provider supports these, you can adjust your request rate proactively:

async function fetchWithLimitAwareness(url, options = {}) {
  const response = await fetch(url, options);

  const remaining = response.headers.get("X-RateLimit-Remaining");
  const reset = response.headers.get("X-RateLimit-Reset");

  if (remaining !== null && parseInt(remaining, 10) < 5) {
    const waitTime = reset
      ? Math.max(0, parseInt(reset, 10) * 1000 - Date.now())
      : 1000;
    console.warn(`Rate limit low (remaining: ${remaining}), pausing ${waitTime}ms`);
    await new Promise(resolve => setTimeout(resolve, waitTime));
  }

  return response;
}

Note: URAPIS Gateway does not include X-RateLimit-* headers in its 429 responses. These headers are only available if the upstream provider explicitly includes them.

FAQ

How do I know if my balance was charged?

Check the response body of the 429 error:

  • If it contains "code": "RATE_LIMITED" → gateway 429 → balance not charged
  • If it does not contain "code": "RATE_LIMITED" → upstream 429 → balance was charged

Why was I charged even though my request failed?

From URAPIS’s perspective, your request did not fail — it successfully reached the provider’s server. Billing is based on whether the request reached the upstream, not the final HTTP status code.

What should I do if I keep getting upstream 429s?

  1. Check the provider’s API documentation for their official rate limits
  2. Reduce your request rate — use exponential backoff or a client-side rate limiter
  3. Contact the provider if you need a higher limit
  4. Cache responses for data that doesn’t change frequently to reduce total request volume

Best Practices

  1. Always implement exponential backoff in your client code to avoid wasting balance from rapid retries
  2. Inspect the response body of 429 errors to distinguish between gateway and upstream limits before deciding how to respond
  3. Monitor your logs — frequent upstream 429s indicate your request rate needs adjustment
  4. Use response caching for infrequently changing data (reference data, configuration)
  5. Review the provider’s API documentation to understand their specific rate limit thresholds