From hubspot-pack
Optimizes HubSpot API costs by monitoring usage against daily limits, selecting plans, and reducing calls via batch reads and tracking.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin hubspot-packThis skill is limited to using the following tools:
Optimize HubSpot integration costs by reducing API call volume, monitoring usage against daily limits, and choosing the right plan.
Optimizes HubSpot CRM API performance using batch reads, minimal property requests, and caching to handle slow responses and high throughput.
Provides expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects using Node.js and Python SDKs.
Provides expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects using Node.js/Python SDKs.
Share bugs, ideas, or general feedback.
Optimize HubSpot integration costs by reducing API call volume, monitoring usage against daily limits, and choosing the right plan.
HubSpot API calls are included with your subscription tier. There is no per-call billing, but exceeding limits results in 429 Too Many Requests errors that block your integration.
| Plan | Daily API Limit | Per-Second Limit |
|---|---|---|
| Free / Starter | 250,000 | 10 |
| Professional | 500,000 | 10 |
| Enterprise | 500,000 | 10 |
| API Limit Increase Add-on | 1,000,000 | 10 |
Key insight: The daily limit is per portal (shared across all apps). A poorly written integration can consume the entire quota and block all other apps.
# Check rate limit headers on any API call
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: 487234
# X-HubSpot-RateLimit-Secondly: 10
# X-HubSpot-RateLimit-Secondly-Remaining: 9
// Programmatic usage tracking
class HubSpotUsageTracker {
private dailyCalls = 0;
private lastReset = new Date();
track(): void {
this.dailyCalls++;
// Reset counter at midnight
const now = new Date();
if (now.getDate() !== this.lastReset.getDate()) {
this.dailyCalls = 0;
this.lastReset = now;
}
}
getUsage(): { daily: number; percentUsed: number } {
const limit = parseInt(process.env.HUBSPOT_DAILY_LIMIT || '500000');
return {
daily: this.dailyCalls,
percentUsed: (this.dailyCalls / limit) * 100,
};
}
shouldAlert(): boolean {
return this.getUsage().percentUsed > 80;
}
}
// BEFORE: 100 API calls
for (const id of contactIds) {
await client.crm.contacts.basicApi.getById(id, ['email']);
}
// AFTER: 1 API call (100x reduction)
await client.crm.contacts.batchApi.read({
inputs: contactIds.map(id => ({ id })),
properties: ['email'],
propertiesWithHistory: [],
});
// BEFORE: Fetch all, filter in memory (wastes API calls + bandwidth)
let after: string | undefined;
const matches = [];
do {
const page = await client.crm.contacts.basicApi.getPage(100, after, ['lifecyclestage']);
matches.push(...page.results.filter(c => c.properties.lifecyclestage === 'customer'));
after = page.paging?.next?.after;
} while (after); // Could be hundreds of pages
// AFTER: 1 search call with server-side filtering
const results = await client.crm.contacts.searchApi.doSearch({
filterGroups: [{
filters: [{ propertyName: 'lifecyclestage', operator: 'EQ', value: 'customer' }],
}],
properties: ['email', 'firstname'],
limit: 100, after: 0, sorts: [],
});
// Pipelines and properties change rarely -- cache for 1 hour
// This saves 2 API calls per deal creation if you look up stage IDs
// BEFORE: 2 calls every time
const pipelines = await client.crm.pipelines.pipelinesApi.getAll('deals');
const properties = await client.crm.properties.coreApi.getAll('deals');
// AFTER: 2 calls per hour (from cache)
const pipelines = await getCachedPipelines('deals'); // see performance-tuning skill
// BEFORE: Poll for changes every 60 seconds (1,440 calls/day)
setInterval(async () => {
const recent = await client.crm.contacts.searchApi.doSearch({
filterGroups: [{
filters: [{
propertyName: 'lastmodifieddate',
operator: 'GTE',
value: String(Date.now() - 60000),
}],
}],
properties: ['email'], limit: 100, after: 0, sorts: [],
});
processChanges(recent.results);
}, 60000);
// AFTER: 0 polling calls (HubSpot pushes changes to you)
// Set up webhook subscription for contact.propertyChange
// See hubspot-webhooks-events skill
-- Track API usage if you log calls to a database
SELECT
DATE_TRUNC('hour', called_at) as hour,
endpoint,
COUNT(*) as calls,
COUNT(*) FILTER (WHERE status_code = 429) as rate_limited,
AVG(response_ms) as avg_latency_ms
FROM hubspot_api_log
WHERE called_at >= NOW() - INTERVAL '24 hours'
GROUP BY 1, 2
ORDER BY calls DESC;
| Issue | Cause | Solution |
|---|---|---|
| Daily limit hit | Unoptimized code | Apply batch + cache + webhook patterns |
| All apps blocked | Shared portal limit | Identify heaviest caller, optimize |
| No visibility | No tracking | Add usage counter middleware |
| Sudden spike | New feature deployed | Review new code for N+1 patterns |
For architecture patterns, see hubspot-reference-architecture.