Claude-code-plugins-plus hubspot-load-scale

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

HubSpot Load & Scale

Overview

Load testing and capacity planning for HubSpot integrations, constrained by the 10 requests/second and 500,000 requests/day API limits.

Prerequisites

  • k6 or similar load testing tool
  • HubSpot developer test account (never load test against production)
  • Understanding of HubSpot rate limits

Instructions

Step 1: Understand HubSpot Rate Limit Constraints

Your integration's maximum throughput is bound by HubSpot's limits:

ConstraintLimitImpact
Per-second10 req/sec600 req/min maximum
Daily500,000/day~347 req/min sustained
Batch size100 records/batchEach batch = 1 API call
Search results10,000 totalCannot page past 10K
Associations500 per recordHard limit

Effective throughput with batching:

  • Individual operations: 10 records/sec
  • Batch operations: 1,000 records/sec (10 batches/sec x 100 records/batch)

Step 2: k6 Load Test Script

// hubspot-load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

const errorRate = new Rate('hubspot_errors');
const rateLimited = new Rate('hubspot_rate_limited');

export const options = {
  stages: [
    { duration: '1m', target: 2 },    // warm up (2 req/sec)
    { duration: '3m', target: 5 },    // moderate load
    { duration: '2m', target: 8 },    // approach limit
    { duration: '2m', target: 10 },   // at limit
    { duration: '1m', target: 0 },    // ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<2000'],       // 95% under 2s
    hubspot_errors: ['rate<0.05'],           // <5% errors
    hubspot_rate_limited: ['rate<0.10'],     // <10% rate limited
  },
};

const BASE_URL = 'https://api.hubapi.com';
const TOKEN = __ENV.HUBSPOT_ACCESS_TOKEN;

export default function () {
  // Test: List contacts (GET)
  const listRes = http.get(
    `${BASE_URL}/crm/v3/objects/contacts?limit=10&properties=email,firstname`,
    { headers: { Authorization: `Bearer ${TOKEN}` } }
  );

  check(listRes, { 'list contacts: 200': (r) => r.status === 200 });
  errorRate.add(listRes.status >= 400 && listRes.status !== 429);
  rateLimited.add(listRes.status === 429);

  sleep(0.1); // 100ms between requests per VU

  // Test: Search contacts (POST)
  const searchRes = http.post(
    `${BASE_URL}/crm/v3/objects/contacts/search`,
    JSON.stringify({
      filterGroups: [{
        filters: [{
          propertyName: 'lifecyclestage',
          operator: 'EQ',
          value: 'lead',
        }],
      }],
      properties: ['email'],
      limit: 10,
      after: 0,
      sorts: [],
    }),
    {
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${TOKEN}`,
      },
    }
  );

  check(searchRes, { 'search contacts: 200': (r) => r.status === 200 });
  errorRate.add(searchRes.status >= 400 && searchRes.status !== 429);
  rateLimited.add(searchRes.status === 429);

  sleep(0.1);
}
# Run against test account only
k6 run --env HUBSPOT_ACCESS_TOKEN=$HUBSPOT_TEST_TOKEN hubspot-load-test.js

Step 3: Capacity Planning Calculator

interface CapacityPlan {
  operationType: string;
  recordsPerDay: number;
  apiCallsPerDay: number;
  batchApiCallsPerDay: number;
  percentOfDailyQuota: number;
  feasible: boolean;
}

function planCapacity(operations: Array<{
  type: string;
  recordsPerDay: number;
  batchable: boolean;
}>): CapacityPlan[] {
  const DAILY_LIMIT = 500_000;
  let totalCalls = 0;

  const plans = operations.map(op => {
    const apiCallsPerDay = op.batchable
      ? Math.ceil(op.recordsPerDay / 100) // batch: 100 per call
      : op.recordsPerDay;                  // individual: 1 per call

    totalCalls += apiCallsPerDay;

    return {
      operationType: op.type,
      recordsPerDay: op.recordsPerDay,
      apiCallsPerDay: op.batchable ? op.recordsPerDay : apiCallsPerDay,
      batchApiCallsPerDay: apiCallsPerDay,
      percentOfDailyQuota: (apiCallsPerDay / DAILY_LIMIT) * 100,
      feasible: apiCallsPerDay < DAILY_LIMIT * 0.5, // leave 50% headroom
    };
  });

  console.log(`\nTotal daily API calls: ${totalCalls.toLocaleString()} / ${DAILY_LIMIT.toLocaleString()}`);
  console.log(`Quota utilization: ${((totalCalls / DAILY_LIMIT) * 100).toFixed(1)}%`);

  if (totalCalls > DAILY_LIMIT) {
    console.warn('WARNING: Exceeds daily limit! Optimize with batching or reduce volume.');
  }

  return plans;
}

// Example capacity plan
planCapacity([
  { type: 'Sync contacts (read)', recordsPerDay: 50000, batchable: true },
  { type: 'Create deals', recordsPerDay: 500, batchable: true },
  { type: 'Search contacts', recordsPerDay: 10000, batchable: false },
  { type: 'Webhook processing', recordsPerDay: 5000, batchable: false },
]);
// Total: 500 + 5 + 10000 + 5000 = 15,505 API calls
// Quota: 3.1% -- very feasible

Step 4: Scaling Patterns for High Volume

// Pattern 1: Queue-based rate limiting
import PQueue from 'p-queue';

const hubspotQueue = new PQueue({
  concurrency: 5,
  interval: 1000,
  intervalCap: 10, // HubSpot's 10/sec limit
});

// Pattern 2: Batch aggregation
class BatchAggregator<T> {
  private buffer: T[] = [];
  private timer: NodeJS.Timeout | null = null;

  constructor(
    private maxBatch: number,
    private maxWaitMs: number,
    private flush: (items: T[]) => Promise<void>
  ) {}

  add(item: T): void {
    this.buffer.push(item);
    if (this.buffer.length >= this.maxBatch) {
      this.flushNow();
    } else if (!this.timer) {
      this.timer = setTimeout(() => this.flushNow(), this.maxWaitMs);
    }
  }

  private async flushNow(): Promise<void> {
    if (this.timer) { clearTimeout(this.timer); this.timer = null; }
    if (this.buffer.length === 0) return;
    const batch = this.buffer.splice(0, this.maxBatch);
    await this.flush(batch);
  }
}

// Usage: aggregate individual creates into batch creates
const contactAggregator = new BatchAggregator(
  100,   // max batch size (HubSpot limit)
  5000,  // flush every 5 seconds max
  async (contacts) => {
    await client.crm.contacts.batchApi.create({
      inputs: contacts.map(c => ({ properties: c, associations: [] })),
    });
  }
);

Output

  • Understanding of HubSpot rate limit constraints
  • k6 load test script for realistic testing
  • Capacity planning calculator for daily operations
  • Queue-based rate limiting for production use
  • Batch aggregation pattern for high-volume writes

Error Handling

IssueCauseSolution
Load test hits 429 immediatelyTesting against shared portalUse dedicated test account
k6 results inconsistentHubSpot API latency variesRun multiple iterations
Capacity plan exceeds limitToo many individual callsConvert to batch operations
Batch aggregator data lossApp crash before flushAdd persistence to buffer

Resources

Next Steps

For reliability patterns, see

hubspot-reliability-patterns
.