Skip to content
Last updated

Rate Limiting

The Sprinklr API implements rate limiting to ensure fair usage and maintain optimal performance for all users. Understanding how rate limits work and how to work within them is essential for building reliable, production-ready integrations.

Why rate limiting matters

Rate limiting protects the API infrastructure and ensures that all customers get consistent, reliable performance. Without rate limits, a single integration making too many requests could degrade performance for everyone.

For your integration, rate limits mean:

  • You need to design for efficiency from the start
  • You should implement retry logic and backoff strategies
  • You'll want to monitor your usage to avoid hitting limits
  • You may need to cache data and batch operations strategically

The good news? With the right approach, you'll rarely hit rate limits, and when you do, your application will handle it gracefully and automatically recover.

Think of rate limiting like traffic lights on a busy highway. They prevent congestion and keep everything moving smoothly for everyone. Your job is to time your acceleration and plan your route efficiently.

How Sprinklr API rate limiting works

The Sprinklr API uses a sliding window rate limit algorithm. This means your rate limit is calculated based on your requests over a rolling time period, not a fixed window that resets at specific times.

Example of sliding window:

If your limit is 60 requests per minute:
- At 10:00:00, you make 60 requests
- At 10:00:30, you can make more requests (oldest ones are now outside the 1-minute window)
- You don't have to wait until 10:01:00 to make more requests

Compare to fixed window:
- At 10:00:00, you make 60 requests
- You must wait until 10:01:00 before making any more requests

This sliding window approach is more forgiving and allows for smoother request distribution.

Understanding rate limit headers

Every API response includes three headers that tell you your current rate limit status:

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1709564820
Content-Type: application/json
HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in the current time window1000
X-RateLimit-RemainingNumber of requests you can still make in this window847
X-RateLimit-ResetUnix timestamp (seconds) when the limit fully resets1709564820

Reading rate limit headers in code

Node.js:

const response = await fetch('https://api-v3.sprinklr.com/api/v3/account', {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Key': apiKey
  }
});

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

console.log(`Rate limit: ${remaining}/${limit} requests remaining`);
console.log(`Resets at: ${new Date(reset * 1000).toISOString()}`);

// Check if you're getting close to the limit
if (remaining < limit * 0.1) {  // Less than 10% remaining
  console.warn('Approaching rate limit! Consider slowing down requests.');
}

const data = await response.json();

Python:

import requests
from datetime import datetime

response = requests.get(
    'https://api-v3.sprinklr.com/api/v3/account',
    headers={
        'Authorization': f'Bearer {access_token}',
        'Key': api_key
    }
)

limit = int(response.headers['X-RateLimit-Limit'])
remaining = int(response.headers['X-RateLimit-Remaining'])
reset = int(response.headers['X-RateLimit-Reset'])

print(f'Rate limit: {remaining}/{limit} requests remaining')
print(f'Resets at: {datetime.fromtimestamp(reset).isoformat()}')

# Check if approaching limit
if remaining < limit * 0.1:
    print('WARNING: Approaching rate limit!')

data = response.json()

PHP:

<?php
$ch = curl_init('https://api-v3.sprinklr.com/api/v3/account');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $accessToken,
    'Key: ' . $apiKey
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);

$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);

// Parse headers
preg_match('/X-RateLimit-Limit: (\d+)/', $headers, $limitMatch);
preg_match('/X-RateLimit-Remaining: (\d+)/', $headers, $remainingMatch);
preg_match('/X-RateLimit-Reset: (\d+)/', $headers, $resetMatch);

$limit = $limitMatch[1];
$remaining = $remainingMatch[1];
$reset = $resetMatch[1];

echo "Rate limit: {$remaining}/{$limit} requests remaining\n";
echo "Resets at: " . date('Y-m-d H:i:s', $reset) . "\n";

if ($remaining < $limit * 0.1) {
    echo "WARNING: Approaching rate limit!\n";
}

$data = json_decode($body, true);
?>

Java:

import java.net.http.*;
import java.time.Instant;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api-v3.sprinklr.com/api/v3/account"))
    .header("Authorization", "Bearer " + accessToken)
    .header("Key", apiKey)
    .build();

HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

int limit = Integer.parseInt(
    response.headers().firstValue("X-RateLimit-Limit").orElse("0"));
int remaining = Integer.parseInt(
    response.headers().firstValue("X-RateLimit-Remaining").orElse("0"));
long reset = Long.parseLong(
    response.headers().firstValue("X-RateLimit-Reset").orElse("0"));

System.out.println("Rate limit: " + remaining + "/" + limit +
    " requests remaining");
System.out.println("Resets at: " +
    Instant.ofEpochSecond(reset).toString());

// Check if approaching limit
if (remaining < limit * 0.1) {
    System.out.println("WARNING: Approaching rate limit!");
}

String data = response.body();

Rate limit tiers

Your rate limit depends on your Sprinklr account tier. Different tiers get different request allowances to match their usage needs.

TierRequests per MinuteRequests per HourDaily RequestsBest For
Standard603,00072,000Small teams, development/testing
Professional1207,200172,800Mid-size integrations, scheduled jobs
Enterprise30018,000432,000High-volume production systems
CustomCustomCustomCustomContact your account manager

How to check your tier

Your tier is determined by your Sprinklr account settings. Check with your account administrator or contact your Sprinklr account manager to:

  • Find out your current tier
  • Understand what usage patterns fit your tier
  • Upgrade your tier if you're consistently hitting limits

Note: Rate limits are applied per access token, not per account. If you have multiple integrations, each with its own token, they each get the full rate limit.

What happens when you hit the rate limit

When you exceed your rate limit, you'll receive an HTTP 429 Too Many Requests response:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1709564880
Retry-After: 15
Content-Type: application/json
{
  "success": false,
  "errors": [
    {
      "code": "RATE_LIMIT_EXCEEDED",
      "message": "Rate limit exceeded. You have made too many requests in the current time window.",
      "details": {
        "limit": 60,
        "window": "1 minute",
        "resetAt": "2024-03-04T16:48:00Z"
      }
    }
  ]
}

What to do when you get a 429:

  1. Don't panic - This is a normal part of API usage
  2. Check the Retry-After header - Tells you how many seconds to wait
  3. Wait the recommended time - Or use the X-RateLimit-Reset timestamp
  4. Retry the request - It should succeed after waiting
  5. Review your request patterns - Consider optimization strategies

Strategies to avoid hitting rate limits

1. Use batch operations (most effective)

The single most effective way to stay under rate limits is to use batch endpoints whenever possible.

❌ Don't make individual requests:

// 50 API calls, takes 50 of your rate limit
const accounts = [];
for (const accountId of accountIds) {
  const response = await fetch(
    `https://api-v3.sprinklr.com/api/v3/account?accountId=${accountId}`,
    { headers: { 'Authorization': `Bearer ${token}`, 'Key': key } }
  );
  const data = await response.json();
  accounts.push(data.data);
}

✅ Do use batch endpoints:

// 1 API call, takes 1 of your rate limit
const response = await fetch(
  'https://api-v3.sprinklr.com/api/v3/account/batch',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Key': key,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      accountIds: accountIds  // Up to 50 IDs
    })
  }
);
const data = await response.json();
const accounts = data.data;

Impact: 50x reduction in API calls. If you're on the Standard tier (60 requests/minute), that's the difference between processing 60 accounts per minute vs 3,000 accounts per minute.

2. Implement intelligent caching

Cache responses for data that doesn't change frequently. Not everything needs to be fetched in real-time.

Node.js caching example:

class SprinklrCache {
  constructor() {
    this.cache = new Map();
    this.ttls = new Map();
  }

  set(key, value, ttlSeconds = 300) {  // Default 5 minutes
    this.cache.set(key, value);
    this.ttls.set(key, Date.now() + (ttlSeconds * 1000));
  }

  get(key) {
    const ttl = this.ttls.get(key);

    // Check if expired
    if (!ttl || Date.now() > ttl) {
      this.cache.delete(key);
      this.ttls.delete(key);
      return null;
    }

    return this.cache.get(key);
  }

  clear() {
    this.cache.clear();
    this.ttls.clear();
  }
}

// Usage
const cache = new SprinklrCache();

async function getAccount(accountId) {
  // Check cache first
  const cacheKey = `account:${accountId}`;
  const cached = cache.get(cacheKey);
  if (cached) {
    console.log('Cache hit!');
    return cached;
  }

  // Fetch from API
  console.log('Cache miss, fetching from API...');
  const response = await fetch(
    `https://api-v3.sprinklr.com/api/v3/account?accountId=${accountId}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Key': apiKey
      }
    }
  );
  const data = await response.json();

  // Cache for 5 minutes
  cache.set(cacheKey, data.data, 300);

  return data.data;
}

What to cache and for how long:

Data TypeRecommended TTLRationale
Account metadata30-60 minutesChanges infrequently
User profiles15-30 minutesMay change but not constantly
Messages/posts1-5 minutesMore dynamic content
Cases1-2 minutesChanges as agents work on them
Configuration data1-24 hoursRarely changes

3. Implement request queuing

Queue your requests to ensure you never exceed your rate limit, even during traffic spikes.

Production-ready request queue:

class RateLimitedQueue {
  constructor(requestsPerMinute = 60) {
    this.requestsPerMinute = requestsPerMinute;
    this.queue = [];
    this.requestTimes = [];
    this.processing = false;
  }

  async add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this.process();
    });
  }

  async process() {
    // Prevent multiple process loops
    if (this.processing) return;
    this.processing = true;

    while (this.queue.length > 0) {
      // Clean up old request times (older than 1 minute)
      const oneMinuteAgo = Date.now() - 60000;
      this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);

      // Check if we can make another request
      if (this.requestTimes.length >= this.requestsPerMinute) {
        // Calculate wait time until oldest request expires
        const oldestRequest = this.requestTimes[0];
        const waitTime = 60000 - (Date.now() - oldestRequest) + 100; // +100ms buffer

        console.log(`Rate limit reached. Waiting ${waitTime}ms...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      // Process next request
      const { requestFn, resolve, reject } = this.queue.shift();
      this.requestTimes.push(Date.now());

      try {
        const result = await requestFn();
        resolve(result);
      } catch (error) {
        reject(error);
      }

      // Small delay between requests to be gentle
      await new Promise(resolve => setTimeout(resolve, 50));
    }

    this.processing = false;
  }

  getQueueSize() {
    return this.queue.length;
  }

  getRecentRequestCount() {
    const oneMinuteAgo = Date.now() - 60000;
    return this.requestTimes.filter(time => time > oneMinuteAgo).length;
  }
}

// Usage
const queue = new RateLimitedQueue(60); // Standard tier: 60 req/min

// All requests go through the queue
async function fetchAccount(accountId) {
  return queue.add(async () => {
    const response = await fetch(
      `https://api-v3.sprinklr.com/api/v3/account?accountId=${accountId}`,
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Key': apiKey
        }
      }
    );
    return response.json();
  });
}

// Make 100 requests - they'll be automatically rate-limited
const promises = accountIds.slice(0, 100).map(id => fetchAccount(id));
const results = await Promise.all(promises);
console.log(`Processed ${results.length} accounts without hitting rate limit`);

4. Implement exponential backoff

When you do hit a rate limit (or any temporary error), retry with exponential backoff.

Exponential backoff implementation:

async function withExponentialBackoff(fn, maxRetries = 5) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      // Check if it's a rate limit error
      if (error.response?.status === 429) {
        const resetHeader = error.response.headers.get('X-RateLimit-Reset');
        const retryAfter = error.response.headers.get('Retry-After');

        let waitTime;
        if (retryAfter) {
          // Use Retry-After header if provided
          waitTime = parseInt(retryAfter) * 1000;
        } else if (resetHeader) {
          // Calculate from reset timestamp
          waitTime = (parseInt(resetHeader) * 1000) - Date.now();
        } else {
          // Exponential backoff: 1s, 2s, 4s, 8s, 16s
          waitTime = Math.min(Math.pow(2, attempt) * 1000, 30000);
        }

        console.log(`Rate limit hit. Waiting ${waitTime}ms before retry ${attempt + 1}/${maxRetries}`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      // For other errors, use exponential backoff
      if (attempt < maxRetries - 1) {
        const waitTime = Math.pow(2, attempt) * 1000;
        console.log(`Request failed. Waiting ${waitTime}ms before retry ${attempt + 1}/${maxRetries}`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
      }
    }
  }

  throw lastError;
}

// Usage
const account = await withExponentialBackoff(async () => {
  const response = await fetch(
    `https://api-v3.sprinklr.com/api/v3/account?accountId=${accountId}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Key': apiKey
      }
    }
  );

  if (!response.ok) {
    const error = new Error(`HTTP ${response.status}`);
    error.response = response;
    throw error;
  }

  return response.json();
});

5. Reduce polling frequency

If you're polling for updates, consider these alternatives:

❌ Don't poll aggressively:

// Polls every 5 seconds - uses 720 requests/hour
setInterval(async () => {
  const cases = await fetchNewCases();
  processNewCases(cases);
}, 5000);

✅ Do use webhooks (best option):

// Webhooks push updates to you - uses 0 API requests
app.post('/webhooks/sprinklr/case-created', (req, res) => {
  const caseData = req.body;
  processNewCase(caseData);
  res.sendStatus(200);
});

✅ Or increase polling interval:

// Polls every 60 seconds - uses 60 requests/hour (12x reduction)
setInterval(async () => {
  const cases = await fetchNewCases();
  processNewCases(cases);
}, 60000);

✅ Or use smart polling with caching:

// Only poll if data might have changed
let lastModified = null;

setInterval(async () => {
  const response = await fetch(
    'https://api-v3.sprinklr.com/api/v3/cases/search',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Key': apiKey,
        'If-Modified-Since': lastModified || ''
      },
      body: JSON.stringify({ status: 'OPEN' })
    }
  );

  // 304 Not Modified - no new data, no processing needed
  if (response.status === 304) {
    console.log('No new cases');
    return;
  }

  lastModified = response.headers.get('Last-Modified');
  const cases = await response.json();
  processNewCases(cases.data);
}, 30000);

6. Distribute requests over time

Instead of making all your requests at once, spread them out evenly.

Spread requests over time:

async function processInBatches(items, processFn, batchSize = 10, delayMs = 1000) {
  const results = [];

  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);

    console.log(`Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(items.length / batchSize)}`);

    // Process batch in parallel
    const batchResults = await Promise.all(
      batch.map(item => processFn(item))
    );

    results.push(...batchResults);

    // Wait before next batch (except for the last one)
    if (i + batchSize < items.length) {
      await new Promise(resolve => setTimeout(resolve, delayMs));
    }
  }

  return results;
}

// Process 1000 accounts in batches of 10, with 1 second between batches
// Total time: ~100 seconds, stays well under rate limit
const accounts = await processInBatches(
  accountIds,
  id => fetchAccount(id),
  10,   // 10 at a time
  1000  // 1 second between batches
);

Building a production-ready API client

Here's a complete example that combines all these strategies:

class SprinklrAPIClient {
  constructor(accessToken, apiKey, options = {}) {
    this.accessToken = accessToken;
    this.apiKey = apiKey;
    this.baseUrl = 'https://api-v3.sprinklr.com';

    // Configuration
    this.requestsPerMinute = options.requestsPerMinute || 60;
    this.cacheTTL = options.cacheTTL || 300000; // 5 minutes
    this.maxRetries = options.maxRetries || 5;

    // Components
    this.cache = new Map();
    this.queue = new RateLimitedQueue(this.requestsPerMinute);
    this.metrics = {
      totalRequests: 0,
      cacheHits: 0,
      cacheMisses: 0,
      rateLimitHits: 0
    };
  }

  async request(method, endpoint, options = {}) {
    const { useCache = true, cacheTTL = this.cacheTTL, ...fetchOptions } = options;
    const cacheKey = `${method}:${endpoint}:${JSON.stringify(fetchOptions.body || {})}`;

    // Check cache for GET requests
    if (method === 'GET' && useCache) {
      const cached = this.getCached(cacheKey);
      if (cached) {
        this.metrics.cacheHits++;
        console.log('Cache hit:', endpoint);
        return cached;
      }
      this.metrics.cacheMisses++;
    }

    // Queue the request
    const response = await this.queue.add(async () => {
      return withExponentialBackoff(async () => {
        this.metrics.totalRequests++;

        const response = await fetch(`${this.baseUrl}${endpoint}`, {
          method,
          headers: {
            'Authorization': `Bearer ${this.accessToken}`,
            'Key': this.apiKey,
            'Content-Type': 'application/json',
            ...fetchOptions.headers
          },
          ...fetchOptions
        });

        // Log rate limit status
        const remaining = response.headers.get('X-RateLimit-Remaining');
        const limit = response.headers.get('X-RateLimit-Limit');
        console.log(`Rate limit: ${remaining}/${limit}`);

        // Warning if approaching limit
        if (remaining && limit && parseInt(remaining) < parseInt(limit) * 0.1) {
          console.warn('WARNING: Less than 10% of rate limit remaining!');
        }

        if (response.status === 429) {
          this.metrics.rateLimitHits++;
          const error = new Error('Rate limit exceeded');
          error.response = response;
          throw error;
        }

        if (!response.ok) {
          const error = new Error(`HTTP ${response.status}`);
          error.response = response;
          throw error;
        }

        return response.json();
      }, this.maxRetries);
    });

    // Cache successful GET responses
    if (method === 'GET' && useCache && response.success) {
      this.setCached(cacheKey, response, cacheTTL);
    }

    return response;
  }

  async get(endpoint, params = {}, options = {}) {
    const queryString = new URLSearchParams(params).toString();
    const url = queryString ? `${endpoint}?${queryString}` : endpoint;
    return this.request('GET', url, options);
  }

  async post(endpoint, body, options = {}) {
    return this.request('POST', endpoint, {
      ...options,
      body: JSON.stringify(body)
    });
  }

  getCached(key) {
    const cached = this.cache.get(key);
    if (!cached) return null;

    if (Date.now() > cached.expiresAt) {
      this.cache.delete(key);
      return null;
    }

    return cached.data;
  }

  setCached(key, data, ttl) {
    this.cache.set(key, {
      data,
      expiresAt: Date.now() + ttl
    });
  }

  clearCache() {
    this.cache.clear();
    console.log('Cache cleared');
  }

  getMetrics() {
    const cacheHitRate = this.metrics.totalRequests > 0
      ? (this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) * 100).toFixed(2)
      : 0;

    return {
      ...this.metrics,
      cacheHitRate: `${cacheHitRate}%`,
      queueSize: this.queue.getQueueSize(),
      recentRequests: this.queue.getRecentRequestCount()
    };
  }
}

// Usage
const client = new SprinklrAPIClient(accessToken, apiKey, {
  requestsPerMinute: 60,  // Standard tier
  cacheTTL: 300000,       // 5 minutes
  maxRetries: 5
});

// Make requests - all the complexity is handled automatically
try {
  // GET request with automatic caching
  const account = await client.get('/api/v3/account', { accountId: '600057112' });

  // POST request (not cached)
  const messages = await client.post('/api/v3/message/search', {
    platform: 'TWITTER',
    limit: 50
  });

  // Check metrics
  console.log('Client metrics:', client.getMetrics());
  // Output: { totalRequests: 2, cacheHits: 0, cacheMisses: 1, rateLimitHits: 0,
  //           cacheHitRate: '0.00%', queueSize: 0, recentRequests: 2 }

} catch (error) {
  console.error('API request failed:', error);
}

Monitoring your rate limit usage

Tracking your API usage helps you identify optimization opportunities before you hit limits.

Simple usage tracking:

class APIUsageTracker {
  constructor() {
    this.requests = [];
  }

  logRequest(endpoint, statusCode) {
    this.requests.push({
      endpoint,
      statusCode,
      timestamp: Date.now()
    });

    // Keep only last 24 hours
    const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
    this.requests = this.requests.filter(r => r.timestamp > oneDayAgo);
  }

  getStats(timeWindowMinutes = 60) {
    const cutoff = Date.now() - (timeWindowMinutes * 60 * 1000);
    const recent = this.requests.filter(r => r.timestamp > cutoff);

    const endpointCounts = {};
    const statusCounts = {};

    for (const request of recent) {
      endpointCounts[request.endpoint] = (endpointCounts[request.endpoint] || 0) + 1;
      statusCounts[request.statusCode] = (statusCounts[request.statusCode] || 0) + 1;
    }

    return {
      totalRequests: recent.length,
      timeWindow: `${timeWindowMinutes} minutes`,
      requestsPerMinute: (recent.length / timeWindowMinutes).toFixed(2),
      topEndpoints: Object.entries(endpointCounts)
        .sort((a, b) => b[1] - a[1])
        .slice(0, 5),
      statusCodes: statusCounts,
      rateLimitErrors: statusCounts[429] || 0
    };
  }

  exportReport() {
    const stats = this.getStats(60);
    console.log('\n=== API Usage Report (Last Hour) ===');
    console.log(`Total requests: ${stats.totalRequests}`);
    console.log(`Requests per minute: ${stats.requestsPerMinute}`);
    console.log(`Rate limit errors: ${stats.rateLimitErrors}`);
    console.log('\nTop endpoints:');
    stats.topEndpoints.forEach(([endpoint, count]) => {
      console.log(`  ${endpoint}: ${count} requests`);
    });
    console.log('\nStatus codes:');
    Object.entries(stats.statusCodes).forEach(([code, count]) => {
      console.log(`  ${code}: ${count}`);
    });
  }
}

// Usage
const tracker = new APIUsageTracker();

// Log every API request
async function makeAPIRequest(endpoint) {
  try {
    const response = await fetch(`https://api-v3.sprinklr.com${endpoint}`, {
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Key': apiKey
      }
    });

    tracker.logRequest(endpoint, response.status);
    return response.json();
  } catch (error) {
    tracker.logRequest(endpoint, error.response?.status || 0);
    throw error;
  }
}

// Export report every hour
setInterval(() => {
  tracker.exportReport();
}, 60 * 60 * 1000);

Optimization checklist

Before deploying your integration to production, verify you've implemented these rate limit optimizations:

Required (Do these first)

  • Use batch endpoints wherever available (up to 50 resources per request)
  • Implement exponential backoff for retry logic
  • Monitor rate limit headers in all responses
  • Handle 429 errors gracefully with automatic retry
  • Log rate limit metrics to track usage patterns
  • Implement request caching with appropriate TTLs
  • Use a request queue to prevent bursts from hitting limits
  • Replace polling with webhooks where possible
  • Distribute requests evenly over time instead of bursting
  • Set up alerting when approaching rate limits

Advanced (Do these for high-volume systems)

  • Implement circuit breakers to prevent cascading failures
  • Use multiple access tokens if you need higher throughput
  • Optimize cache invalidation strategy
  • Monitor cache hit rates and adjust TTLs
  • Load test your integration to verify it stays under limits

Troubleshooting rate limit issues

Issue: Constantly hitting rate limits

Symptoms:

  • Frequent 429 errors
  • Slow response times due to retries
  • Requests taking much longer than expected

Solutions:

  1. Use batch operations - Are you making individual requests when you could batch?
  2. Implement caching - Are you requesting the same data repeatedly?
  3. Reduce polling frequency - Can you use webhooks or poll less often?
  4. Upgrade your tier - Contact your account manager if you've optimized but still need more capacity

Issue: Sporadic rate limit errors

Symptoms:

  • Occasional 429 errors
  • Happens during specific operations
  • Works most of the time

Solutions:

  1. Implement request queuing - You're likely sending bursts of requests
  2. Add exponential backoff - Automatic retry will handle occasional limits
  3. Spread requests over time - Don't process large batches all at once

Issue: Different rate limits than expected

Symptoms:

  • Getting rate limited sooner than tier indicates
  • Limit headers show unexpected values

Solutions:

  1. Check your account tier - Verify with your Sprinklr administrator
  2. Check per-endpoint limits - Some endpoints may have stricter limits
  3. Verify you're not sharing tokens - Each token has its own limit counter
  4. Contact support - There may be custom limits on your account

Next steps

Now that you understand rate limiting, explore these related topics:

  • Error Handling - Handle rate limit errors and other API errors properly
  • Best Practices - More optimization strategies for production systems
  • Webhooks - Replace polling with real-time event notifications
  • API Basics - Learn about batch operations and other core patterns

Need help?

If you're consistently hitting rate limits even after optimization:

  1. Review your integration - Are you following all the strategies above?
  2. Contact your account manager - Discuss upgrading your tier
  3. Reach out to support - We can help identify optimization opportunities

Support resources:

  • Technical support: support.sprinklr.com
  • API documentation: This guide and related docs
  • Account management: Contact your Sprinklr account manager