Skip to main content

Overview

Rate limiting protects our API infrastructure by controlling how many requests you can make within specific time periods. Think of it like having three different speed limits on the same road - you need to respect all of them to keep moving smoothly. Our system uses three-tier rate limiting to ensure fair usage and optimal performance for all developers:

Per-Second Limits

Prevents burst traffic spikes

Per-Minute Limits

Controls sustained request rates

Daily Limits

Manages overall daily usage

How Rate Limiting Works

Three-Tier Protection

Every API request must pass through all three rate limiting tiers:
  1. Second-level: Allows quick bursts of requests (typically 5-50 per second)
  2. Minute-level: Permits sustained usage (typically 100-3000 per minute)
  3. Daily-level: Sets overall usage bounds (typically 10,000-100,000 per day)
Your specific limits depend on your TitanX plan. Use the /api/public/quota endpoint to see your current limits.

Fixed Time Windows

Our rate limits use fixed time windows that reset at exact intervals:
  • Per-Second
  • Per-Minute
  • Daily
Resets every second at the exact second boundary.Example: If it’s currently 14:23:45.750, your second quota will reset at 14:23:46.000

How Quotas Are Consumed

Each successful API request consumes 1 quota from all three tiers simultaneously. Failed requests (4xx/5xx errors) still consume quota to prevent abuse.
If ANY tier is exceeded, your request will be blocked with a 429 Too Many Requests response, even if the other tiers have remaining quota.

Rate Limit Headers

Every API response includes detailed information about your current rate limit status through HTTP headers:

Complete Headers Reference

HeaderDescriptionExample
X-RateLimit-Limit-SecondMaximum requests allowed per second10
X-RateLimit-Remaining-SecondRemaining requests this second7
X-RateLimit-Reset-SecondWhen second quota resets (Unix timestamp)1704110401
X-RateLimit-Limit-MinuteMaximum requests allowed per minute300
X-RateLimit-Remaining-MinuteRemaining requests this minute287
X-RateLimit-Reset-MinuteWhen minute quota resets (Unix timestamp)1704110460
X-RateLimit-Limit-DayMaximum requests allowed per day50000
X-RateLimit-Remaining-DayRemaining requests today49713
X-RateLimit-Reset-DayWhen daily quota resets (Unix timestamp)1704153600

Special Headers

When you’re rate limited, additional headers help you retry effectively:
HeaderDescriptionExample
Retry-AfterSeconds to wait before retrying1
Convert Unix timestamps to readable dates: new Date(timestamp * 1000) in JavaScript

Example Response Headers

HTTP/1.1 200 OK
X-RateLimit-Limit-Second: 10
X-RateLimit-Remaining-Second: 7
X-RateLimit-Reset-Second: 1704110401
X-RateLimit-Limit-Minute: 300
X-RateLimit-Remaining-Minute: 287
X-RateLimit-Reset-Minute: 1704110460
X-RateLimit-Limit-Day: 50000
X-RateLimit-Remaining-Day: 49713
X-RateLimit-Reset-Day: 1704153600

Checking Your Quota

Use the /api/public/quota endpoint to check your current limits without consuming quota:
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     https://app.titanx.io/api/public/quota

Handling Rate Limits

When You Hit a Limit

When any rate limit is exceeded, you’ll receive a 429 Too Many Requests response:
{
  "error": "Rate limit exceeded",
  "message": "Too many requests. Rate limit exceeded for second tier.",
  "tier": "second",
  "retryAfter": 1704110401
}

Best Practices for Retries

1

Check the Retry-After Header

Always respect the Retry-After header value before making another request.
2

Implement Exponential Backoff

If you don’t receive a Retry-After header, use exponential backoff starting with 1 second.
3

Monitor All Tiers

Watch the remaining quota for all three tiers, not just the one that was exceeded.
4

Spread Your Requests

Avoid sending bursts of requests. Distribute them evenly across time.

Example Retry Logic

async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
        
        console.log(`Rate limited. Waiting ${waitTime}ms before retry ${attempt}/${maxRetries}`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }
      
      return response;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
}

Monitoring Your Usage

Proactively monitor your rate limits to avoid hitting them:
// Check if you're approaching any limits
function checkRateLimitStatus(headers) {
  const secondRemaining = parseInt(headers.get('X-RateLimit-Remaining-Second'));
  const minuteRemaining = parseInt(headers.get('X-RateLimit-Remaining-Minute'));
  const dayRemaining = parseInt(headers.get('X-RateLimit-Remaining-Day'));
  
  // Warn when approaching limits
  if (secondRemaining <= 1) {
    console.warn('⚠️ Very low second-tier quota remaining');
  }
  if (minuteRemaining <= 10) {
    console.warn('⚠️ Low minute-tier quota remaining');
  }
  if (dayRemaining <= 100) {
    console.warn('⚠️ Low daily quota remaining');
  }
}

Common Integration Patterns

Batch Processing

When processing multiple items, respect rate limits:
async function processBatch(items, apiCall) {
  const results = [];
  
  for (const item of items) {
    const response = await makeRequestWithRetry(apiCall, item);
    results.push(response);
    
    // Check remaining quotas and pace accordingly
    const remaining = parseInt(response.headers.get('X-RateLimit-Remaining-Second'));
    if (remaining <= 2) {
      // Slow down when approaching limits
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  
  return results;
}

Real-time Applications

For real-time apps, check quotas before making requests:
async function smartApiCall(endpoint, payload) {
  // Check quota first
  const quotaResponse = await fetch('/api/public/quota', { 
    headers: { 'Authorization': `Bearer ${token}` }
  });
  
  const secondRemaining = parseInt(quotaResponse.headers.get('X-RateLimit-Remaining-Second'));
  
  if (secondRemaining <= 1) {
    console.log('Quota low, waiting for reset...');
    const resetTime = parseInt(quotaResponse.headers.get('X-RateLimit-Reset-Second'));
    const waitTime = (resetTime * 1000) - Date.now();
    await new Promise(resolve => setTimeout(resolve, waitTime));
  }
  
  return fetch(endpoint, {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: { 'Authorization': `Bearer ${token}` }
  });
}

Troubleshooting

Common Issues

Cause: Sending requests in tight loops can exceed per-second limits even with low overall volume.Solution: Add small delays between requests or implement proper retry logic.
Cause: Rate limits are enforced per client ID. Using multiple API keys may have different limits.Solution: Check your client configuration and ensure you’re using the correct authentication token.
Cause: Daily limits reset at midnight UTC, which may differ from your local timezone.Solution: Convert the X-RateLimit-Reset-Day timestamp to your local timezone to see the actual reset time.

Getting Help

If you’re experiencing persistent rate limiting issues:
  1. Check your plan limits in the TitanX dashboard
  2. Review your request patterns for burst behavior
  3. Verify your authentication tokens are correct
  4. Contact support with your client ID and example requests
Need higher limits? Upgrade your plan in the TitanX dashboard or contact our sales team for enterprise options.