Skills Development Robust API Rate Limiting Strategies

Robust API Rate Limiting Strategies

v20260423
hubspot-rate-limits
This guide provides advanced patterns for managing API rate limits, crucial for reliable integration with platforms like HubSpot. It covers implementing exponential backoff, honoring 'Retry-After' headers, using request queuing (throttling), and optimizing throughput through batch operations to gracefully handle 429 rate limit errors.
Get Skill
489 downloads
Overview

HubSpot Rate Limits

Overview

Handle HubSpot API rate limits with proper backoff strategies. HubSpot enforces per-second and daily limits shared across all apps in a portal.

Prerequisites

  • @hubspot/api-client installed
  • Understanding of HubSpot's shared rate limit model

Instructions

Step 1: Understand HubSpot Rate Limit Tiers

Plan Per-Second Limit Daily Limit Burst
Free/Starter 10 requests/sec 250,000/day --
Professional 10 requests/sec 500,000/day --
Enterprise 10 requests/sec 500,000/day --
API Add-on 10 requests/sec 1,000,000/day --

Critical: Limits are per HubSpot portal (account), not per app. All private apps and OAuth apps in the same portal share the same limit bucket.

Step 2: Use SDK Built-in Retries

import * as hubspot from '@hubspot/api-client';

// The SDK has built-in retry for 429 responses
const client = new hubspot.Client({
  accessToken: process.env.HUBSPOT_ACCESS_TOKEN!,
  numberOfApiCallRetries: 3, // retries 429 and 5xx automatically
});

Step 3: Custom Backoff with Retry-After Header

async function withHubSpotBackoff<T>(
  operation: () => Promise<T>,
  config = { maxRetries: 5, baseDelayMs: 1000, maxDelayMs: 30000 }
): Promise<T> {
  for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error: any) {
      if (attempt === config.maxRetries) throw error;

      const status = error?.code || error?.statusCode || error?.response?.status;

      // Only retry on 429 and 5xx
      if (status !== 429 && (status < 500 || status >= 600)) throw error;

      // Honor Retry-After header from HubSpot
      let delay: number;
      const retryAfter = error?.response?.headers?.['retry-after'];
      if (retryAfter) {
        delay = parseInt(retryAfter) * 1000;
      } else {
        // Exponential backoff with jitter
        const exponential = config.baseDelayMs * Math.pow(2, attempt);
        const jitter = Math.random() * 500;
        delay = Math.min(exponential + jitter, config.maxDelayMs);
      }

      console.warn(`HubSpot rate limited (attempt ${attempt + 1}/${config.maxRetries}). ` +
        `Retrying in ${delay}ms...`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error('Unreachable');
}

Step 4: Request Queue for Throughput Control

import PQueue from 'p-queue';

// Queue that respects HubSpot's 10 req/sec limit
const hubspotQueue = new PQueue({
  concurrency: 5,        // max parallel requests
  interval: 1000,        // per second
  intervalCap: 10,       // max 10 per interval (HubSpot limit)
});

async function queuedRequest<T>(operation: () => Promise<T>): Promise<T> {
  return hubspotQueue.add(operation) as Promise<T>;
}

// Usage -- all calls automatically throttled
const results = await Promise.all(
  contactIds.map(id =>
    queuedRequest(() =>
      client.crm.contacts.basicApi.getById(id, ['email', 'firstname'])
    )
  )
);

Step 5: Batch Operations to Reduce Call Volume

// Instead of 100 individual GET calls (100 API calls):
// BAD
for (const id of contactIds) {
  await client.crm.contacts.basicApi.getById(id, ['email']);
}

// Use batch read (1 API call for up to 100 records):
// GOOD - POST /crm/v3/objects/contacts/batch/read
const batchResult = await client.crm.contacts.batchApi.read({
  inputs: contactIds.map(id => ({ id })),
  properties: ['email', 'firstname', 'lastname'],
  propertiesWithHistory: [],
});

console.log(`Fetched ${batchResult.results.length} contacts in 1 API call`);

Step 6: Monitor Rate Limit Headers

class HubSpotRateLimitMonitor {
  private dailyRemaining = 500000;
  private secondlyRemaining = 10;

  updateFromResponse(headers: Record<string, string>): void {
    if (headers['x-hubspot-ratelimit-daily-remaining']) {
      this.dailyRemaining = parseInt(headers['x-hubspot-ratelimit-daily-remaining']);
    }
    if (headers['x-hubspot-ratelimit-secondly-remaining']) {
      this.secondlyRemaining = parseInt(headers['x-hubspot-ratelimit-secondly-remaining']);
    }
  }

  shouldThrottle(): boolean {
    return this.secondlyRemaining < 2 || this.dailyRemaining < 1000;
  }

  getStatus(): { daily: number; secondly: number; warning: boolean } {
    return {
      daily: this.dailyRemaining,
      secondly: this.secondlyRemaining,
      warning: this.shouldThrottle(),
    };
  }
}

Output

  • SDK built-in retry handles basic 429s
  • Custom backoff honors Retry-After header
  • Request queue enforces 10 req/sec limit
  • Batch operations reduce API call volume by 100x
  • Rate limit monitoring prevents threshold breaches

Error Handling

Header Description Action
X-HubSpot-RateLimit-Daily Daily quota Monitor usage
X-HubSpot-RateLimit-Daily-Remaining Remaining today Alert if < 10%
X-HubSpot-RateLimit-Secondly Per-second limit Always 10
X-HubSpot-RateLimit-Secondly-Remaining Remaining this second Throttle if < 2
Retry-After Seconds to wait Always honor this

Examples

Quick Rate Limit Check

# Check current rate limit state
curl -sI https://api.hubapi.com/crm/v3/objects/contacts?limit=1 \
  -H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" \
  | grep -i ratelimit

# Output:
# X-HubSpot-RateLimit-Daily: 500000
# X-HubSpot-RateLimit-Daily-Remaining: 499800
# X-HubSpot-RateLimit-Secondly: 10
# X-HubSpot-RateLimit-Secondly-Remaining: 9

Resources

Next Steps

For security configuration, see hubspot-security-basics.

Info
Category Development
Name hubspot-rate-limits
Version v20260423
Size 6.38KB
Updated At 2026-04-28
Language