Agent Skills: HubSpot Load & Scale

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/hubspot-load-scale

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/hubspot-pack/skills/hubspot-load-scale

Skill Files

Browse the full folder contents for hubspot-load-scale.

Download Skill

Loading file tree…

plugins/saas-packs/hubspot-pack/skills/hubspot-load-scale/SKILL.md

Skill Metadata

Name
hubspot-load-scale
Description
|

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:

| Constraint | Limit | Impact | |-----------|-------|--------| | Per-second | 10 req/sec | 600 req/min maximum | | Daily | 500,000/day | ~347 req/min sustained | | Batch size | 100 records/batch | Each batch = 1 API call | | Search results | 10,000 total | Cannot page past 10K | | Associations | 500 per record | Hard 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

| Issue | Cause | Solution | |-------|-------|----------| | Load test hits 429 immediately | Testing against shared portal | Use dedicated test account | | k6 results inconsistent | HubSpot API latency varies | Run multiple iterations | | Capacity plan exceeds limit | Too many individual calls | Convert to batch operations | | Batch aggregator data loss | App crash before flush | Add persistence to buffer |

Resources

Next Steps

For reliability patterns, see hubspot-reliability-patterns.