Claude-code-plugins-plus-skills hubspot-cost-tuning

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-cost-tuning" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-hubspot-cost-tuning && rm -rf "$T"
manifest: plugins/saas-packs/hubspot-pack/skills/hubspot-cost-tuning/SKILL.md
source content

HubSpot Cost Tuning

Overview

Optimize HubSpot integration costs by reducing API call volume, monitoring usage against daily limits, and choosing the right plan.

Prerequisites

  • Access to HubSpot account settings (Settings > Account > Usage & Limits)
  • Understanding of current API usage patterns

Instructions

Step 1: Understand HubSpot API Pricing Model

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.

PlanDaily API LimitPer-Second Limit
Free / Starter250,00010
Professional500,00010
Enterprise500,00010
API Limit Increase Add-on1,000,00010

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.

Step 2: Monitor Current Usage

# 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;
  }
}

Step 3: High-Impact Cost Reductions

Replace Individual Reads with Batch Reads

// 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: [],
});

Use Search Instead of List + Filter

// 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: [],
});

Cache Pipeline and Property Metadata

// 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

Use Webhooks Instead of Polling

// 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

Step 4: Usage Dashboard Query

-- 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;

Output

  • API call volume tracked and monitored
  • Batch operations replacing individual calls (100x reduction)
  • Search replacing list+filter patterns
  • Webhooks replacing polling (1,440 calls/day saved)
  • Metadata cached to avoid redundant lookups

Error Handling

IssueCauseSolution
Daily limit hitUnoptimized codeApply batch + cache + webhook patterns
All apps blockedShared portal limitIdentify heaviest caller, optimize
No visibilityNo trackingAdd usage counter middleware
Sudden spikeNew feature deployedReview new code for N+1 patterns

Resources

Next Steps

For architecture patterns, see

hubspot-reference-architecture
.