Use Retry
/rate-limits-paginationBigCommerce enforces rate limits to ensure platform reliability. Understanding these limits and implementing proper pagination, batching, and retry strategies is essential for robust integrations.
<overview> BigCommerce enforces rate limits to ensure platform reliability. Understanding these limits and implementing proper pagination, batching, and retry strategies is essential for robust integrations. </overview> <ratelimits> <standardlimits> | API Type | Limit | |----------|-------| | REST API (Standard) | 20,000 requests/hour | | Payments API | 50 requests/4 seconds | | B2B Edition | 150 requests/minute | | GraphQL Storefront | Query complexity limits | </standardlimits> <b2bendpointlimits> Some B2B Edition endpoints have specific limits: - Add Company Attachment: 15 requests/minute - Check endpoint documentation for specific quotas </b2bendpointlimits> <ratelimit_headers> Monitor these response headers: `` X-Rate-Limit-Requests-Left: 19850 # Remaining requests X-Rate-Limit-Time-Reset-Ms: 3600000 # Time until reset (ms) X-Retry-After: 300 # Seconds to wait (when limited) </rate_limit_headers> <429_response> When rate limited, you receive HTTP 429: json { "status": 429, "title": "Too Many Requests", "type": "https://developer.bigcommerce.com/docs/start/about/status-codes", "detail": "Rate limit exceeded" } </429_response> </rate_limits> <retry_strategy> <exponential_backoff> python import time import random def make_request_with_retry(func, max_retries=10): base_delay = 1 # seconds for attempt in range(max_retries): try: response = func() if response.status_code == 429: # Use Retry-After header if present retry_after = response.headers.get('X-Retry-After', None) if retry_after: time.sleep(int(retry_after)) else: # Exponential backoff with jitter delay = base_delay * (2 ** attempt) + random.uniform(0, 1) time.sleep(min(delay, 300)) # Cap at 5 minutes continue return response except Exception as e: if attempt == max_retries - 1: raise delay = base_delay * (2 ** attempt) + random.uniform(0, 1) time.sleep(delay) raise Exception("Max retries exceeded") </exponential_backoff> <jitter> Add random jitter to prevent thundering herd: python # Without jitter: all retries happen at exactly 2s, 4s, 8s... # With jitter: retries spread out across time window delay = base_delay * (2 ** attempt) jitter = random.uniform(0, delay * 0.1) # 10% jitter total_delay = delay + jitter </jitter> </retry_strategy> <pagination> <cursor_vs_offset> **Cursor pagination (recommended):** - More efficient for large datasets - Consistent results during iteration - Lower computational complexity - Used by GraphQL and some REST endpoints **Offset pagination:** - Simpler to implement - Allows jumping to specific pages - Less efficient for large datasets - Data can shift between requests </cursor_vs_offset> <rest_pagination> REST APIs use offset pagination via page and limit : bash # First page GET /v3/catalog/products?page=1&limit=100 # Response includes meta object { "data": [...], "meta": { "pagination": { "total": 500, "count": 100, "per_page": 100, "current_page": 1, "total_pages": 5, "links": { "current": "?page=1&limit=100", "next": "?page=2&limit=100" } } } } # Next page GET /v3/catalog/products?page=2&limit=100 **Max limit:** 250 items per page for most endpoints </rest_pagination> <graphql_cursor_pagination> GraphQL uses cursor-based pagination: graphql query GetProducts($first: Int!, $after: String) { site { products(first: $first, after: $after) { edges { node { entityId name } cursor } pageInfo { hasNextPage endCursor } } } } **Pagination loop:** ``javascript async function getAllProducts() { let allProducts = []; let hasNextPage = true; let cursor = null; while (hasNextPage) { const response = await graphqlRequest({ query: GET_PRODUCTS, variables: { first: 50, after