Claude-code-plugins hubspot-rate-limits
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/hubspot-pack/skills/hubspot-rate-limits" ~/.claude/skills/jeremylongshore-claude-code-plugins-hubspot-rate-limits && rm -rf "$T"
manifest:
plugins/saas-packs/hubspot-pack/skills/hubspot-rate-limits/SKILL.mdsource content
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
installed@hubspot/api-client- 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
headerRetry-After - 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 |
|---|---|---|
| Daily quota | Monitor usage |
| Remaining today | Alert if < 10% |
| Per-second limit | Always 10 |
| Remaining this second | Throttle if < 2 |
| 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.