hubspot-rate-limit-survival
Survive HubSpot API rate limits at production scale. Covers daily 500K portal quota, per-10s burst limits, batch API efficiency (100x), token bucket pattern, queue-based worker architecture, and Retry-After header parsing. Use when a sync job burns the daily quota before 8am, when a parallelized batch job retry-storms on 429s, when single-record reads waste 99% of available throughput, or when instrumenting a rate-limit dashboard for an Ops Hub Enterprise portal. Trigger with "hubspot rate limit", "hubspot quota", "hubspot 429", "hubspot batch API", "hubspot token bucket", "hubspot throttle", "hubspot daily limit exhausted", "hubspot Ops Hub rate limit".
What this skill does
# HubSpot Rate Limit Survival
## Overview
Rate-limit your HubSpot integration so it survives production volume without burning the portal's daily quota before lunch. This skill covers the six failure modes that take down integrations at scale and gives you the code to prevent each one.
**Key invariant:** HubSpot rate limits are portal-scoped, not app-scoped. Every private app and OAuth app in the same portal shares the same daily and per-10s buckets. There is no per-app isolation.
The six production failures this skill prevents:
1. **Daily quota burnout** — a naive sync of 5M contacts at 100 records/call requires 50,000 API calls. A misconfigured parallel worker pool exhausts the 500K/day quota in under 90 minutes, leaving the portal dark for 22 hours.
2. **Burst limit ignorance** — parallelizing 20 concurrent requests saturates the 100 req/10s window instantly. The 429 retry storm then burns the daily budget too. The burst limit and daily quota are two independent counters.
3. **Ignoring batch APIs** — `GET /contacts/{id}` costs 1 quota unit and returns 1 record. `POST /contacts/batch/read` with 100 IDs costs 1 unit and returns 100 records. Single-record reads waste 99% of available throughput.
4. **Retry-After ignored** — a 429 includes `Retry-After: N`. Backing off by 1s when N=30 produces 29 consecutive failures. Backing off by 30s when N=1 adds unnecessary latency. Always parse and honor the header exactly.
5. **Daily limit vs per-10s confusion** — these are two independent systems. Burning the burst window does not decrement the daily counter. Exhausting the daily counter does not care about the per-10s rate. Conflating them breaks rate-limit logic.
6. **Operations Hub Enterprise gating** — the 100 req/s sustained rate is gated on Ops Hub Enterprise. A Starter account assuming that throughput gets 429s at 10x the expected rate with no clear signal that the limit tier is wrong.
## Prerequisites
- Node.js 18+ or Python 3.10+
- HubSpot private app token or OAuth access token
- `@hubspot/api-client` (npm) or `hubspot` (pip) for SDK-based integrations
- For queue-based architecture: Redis 6+ with `bullmq` (npm) or `celery`+`redis-py` (Python)
- Portal Settings → Integrations → Private Apps to confirm the plan tier
**Auth:** Every API call requires `Authorization: Bearer {token}`. For token acquisition and caching, see `hubspot-auth` skill. This skill assumes a valid token is already available.
## Instructions
Build in this order. Steps 1–3 are mandatory. Steps 4–6 apply when volume exceeds ~50,000 calls/day or when multiple apps share the portal.
### Step 1: Read rate-limit headers on every response
Every HubSpot response carries both bucket states. Never fly blind.
```typescript
interface RateLimitState {
dailyLimit: number;
dailyRemaining: number;
windowMs: number;
windowMax: number;
windowRemaining: number;
}
let rl: RateLimitState = {
dailyLimit: 500_000, dailyRemaining: 500_000,
windowMs: 10_000, windowMax: 100, windowRemaining: 100,
};
function updateRateLimitState(headers: Headers): void {
if (headers.get("X-HubSpot-RateLimit-Daily"))
rl.dailyLimit = parseInt(headers.get("X-HubSpot-RateLimit-Daily")!, 10);
if (headers.get("X-HubSpot-RateLimit-Daily-Remaining"))
rl.dailyRemaining = parseInt(headers.get("X-HubSpot-RateLimit-Daily-Remaining")!, 10);
if (headers.get("X-HubSpot-RateLimit-Max"))
rl.windowMax = parseInt(headers.get("X-HubSpot-RateLimit-Max")!, 10);
if (headers.get("X-HubSpot-RateLimit-Remaining"))
rl.windowRemaining = parseInt(headers.get("X-HubSpot-RateLimit-Remaining")!, 10);
const pctUsed = 1 - rl.dailyRemaining / rl.dailyLimit;
console.log(JSON.stringify({
event: "hubspot_rate_limit_state",
daily_remaining: rl.dailyRemaining,
daily_pct_used: parseFloat(pctUsed.toFixed(4)),
window_remaining: rl.windowRemaining,
}));
}
```
### Step 2: Token bucket (neutralizes burst limit ignorance)
A token bucket is the correct primitive for the per-10s burst limit. Callers block until a slot is available instead of firing and failing.
```typescript
class TokenBucket {
private tokens: number;
private lastRefillAt: number;
constructor(
private readonly capacity: number,
private readonly refillRatePerMs: number,
) {
this.tokens = capacity;
this.lastRefillAt = Date.now();
}
consume(count = 1): number { // returns ms to wait; 0 = immediate
const now = Date.now();
this.tokens = Math.min(
this.capacity,
this.tokens + (now - this.lastRefillAt) * this.refillRatePerMs,
);
this.lastRefillAt = now;
if (this.tokens >= count) { this.tokens -= count; return 0; }
return Math.ceil((count - this.tokens) / this.refillRatePerMs);
}
}
// Private app, standard plan: 100 req/10s = 0.01 req/ms
// Ops Hub Enterprise: 100 req/s = 0.1 req/ms
const BUCKETS = {
starter: new TokenBucket(100, 10 / 10_000),
professional: new TokenBucket(150, 15 / 10_000),
ops_hub_enterprise: new TokenBucket(1000, 100 / 10_000),
};
type PlanTier = keyof typeof BUCKETS;
let activeBucket: TokenBucket = BUCKETS.starter;
export function configurePlanTier(tier: PlanTier): void {
activeBucket = BUCKETS[tier];
}
export async function acquireToken(): Promise<void> {
const waitMs = activeBucket.consume();
if (waitMs > 0) await new Promise((r) => setTimeout(r, waitMs));
}
```
### Step 3: Retry-After parser and hubspotFetch (neutralizes ignored 429s)
Honor the exact backoff value HubSpot specifies. Full implementation with mock-server test harness in [`implementation-guide.md`](references/implementation-guide.md).
```typescript
function parseRetryAfterMs(headers: Headers): number | null {
const raw = headers.get("Retry-After");
if (!raw) return null;
const s = parseInt(raw, 10);
return isNaN(s) ? null : s * 1_000;
}
export async function hubspotFetch(
path: string,
init: RequestInit = {},
maxAttempts = 5,
): Promise<Response> {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
await acquireToken();
const res = await fetch(`https://api.hubapi.com${path}`, {
...init,
headers: {
Authorization: `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN!}`,
"Content-Type": "application/json",
...init.headers,
},
});
updateRateLimitState(res.headers);
if (res.ok) return res;
const retryable = res.status === 429 || (res.status >= 500 && res.status < 600);
if (!retryable || attempt === maxAttempts) throw new Error(`HubSpot ${res.status}: ${path}`);
const delayMs = parseRetryAfterMs(res.headers)
?? Math.random() * Math.min(60_000, 500 * 2 ** attempt);
await new Promise((r) => setTimeout(r, delayMs));
}
throw new Error("unreachable");
}
```
### Step 4: Batch API wrapper (neutralizes single-record waste)
Auto-chunk any array of IDs into groups of 100. Each chunk costs 1 quota unit.
```typescript
const BATCH_SIZE = 100; // HubSpot hard limit
function chunk<T>(arr: T[], size = BATCH_SIZE): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < arr.length; i += size) chunks.push(arr.slice(i, i + size));
return chunks;
}
export async function batchRead(
objectType: "contacts" | "companies" | "deals" | "tickets",
ids: string[],
properties: string[],
): Promise<{ results: unknown[]; errors: unknown[] }> {
const results: unknown[] = [];
const errors: unknown[] = [];
for (const batch of chunk(ids)) {
const res = await hubspotFetch(`/crm/v3/objects/${objectType}/batch/read`, {
method: "POST",
body: JSON.stringify({ inputs: batch.map((id) => ({ id })), properties }),
});
const body = await res.json() as { results: unknown[]; errors?: unknown[] };
results.push(...body.results);
if (body.errors) errors.push(...body.errors);
}
return { results, errors };
}
```
### Step 5: Daily quota shutoff valve (neutralizes quota burnout)
Halt lower-priority work before the portal goes dark.
```typeRelated in Backend & APIs
jfrog
IncludedInteract with the JFrog Platform via the JFrog CLI and REST/GraphQL APIs. Use this skill when the user wants to manage Artifactory repositories, upload or download artifacts, manage builds, configure permissions, manage users and groups, work with access tokens, configure JFrog CLI servers, search artifacts, manage properties, set up replication, manage JFrog Projects, run security audits or scans, look up CVE details, query exposures scan results from JFrog Advanced Security, manage release bundles and lifecycle operations, aggregate or export platform data, or perform any JFrog Platform administration task. Also use when the user mentions jf, jfrog, artifactory, xray, distribution, evidence, apptrust, onemodel, graphql, workers, mission control, curation, advanced security, exposures, or any JFrog product name.
cupynumeric-migration-readiness
IncludedPre-migration readiness assessor for porting NumPy to cuPyNumeric. Use BEFORE substantial porting work begins when the user asks whether code will scale on GPU, whether they should migrate to cuPyNumeric, which NumPy patterns transfer cleanly, what must be refactored before porting, or mentions pre-port assessment, scaling analysis, or refactor planning. Inspect the user's source code, look up NumPy usage, cross-reference the cuPyNumeric API support manifest, and distinguish distributed-scaling-friendly patterns from blockers such as unsupported APIs, scalar synchronization, host round-trips, Python/object-heavy control flow, shape/data-dependent branching, and in-place mutation hazards. Produce a verdict of READY, LIGHT REFACTOR, SIGNIFICANT REFACTOR, or NOT RECOMMENDED, with concrete refactor pointers.
alibabacloud-data-agent-skill
IncludedInvoke Alibaba Cloud Apsara Data Agent for Analytics via CLI to perform natural language-driven data analysis on enterprise databases. Data Agent for Analytics is an intelligent data analysis agent developed by Alibaba Cloud Database team for enterprise users. It automatically completes requirement analysis, data understanding, analysis insights, and report generation based on natural language descriptions. This tool supports: discovering data resources (instances/databases/tables) managed in DMS, initiating query or deep analysis sessions, real-time progress tracking, and retrieving analysis conclusions and generated reports. Use this Skill when users need to query databases, analyze data trends, generate data reports, ask questions in natural language, or mention "Data Agent", "data analysis", "database query", "SQL analysis", "data insights".
token-optimizer
IncludedReduce OpenClaw token usage and API costs through smart model routing, heartbeat optimization, budget tracking, and native 2026.2.15 features (session pruning, bootstrap size limits, cache TTL alignment). Use when token costs are high, API rate limits are being hit, or hosting multiple agents at scale. The 4 executable scripts (context_optimizer, model_router, heartbeat_optimizer, token_tracker) are local-only — no network requests, no subprocess calls, no system modifications. Reference files (PROVIDERS.md, config-patches.json) document optional multi-provider strategies that require external API keys and network access if you choose to use them. See SECURITY.md for full breakdown.
resend-cli
IncludedUse this skill when the task is specifically about operating Resend from an AI agent, terminal session, or CI job via the official resend CLI: installing/authenticating the CLI, sending/listing/updating/cancelling emails, batch sends, domains and DNS, webhooks and local listeners, inbound receiving, contacts, topics, segments, broadcasts, templates, API keys, profiles, or debugging Resend CLI/API failures. Trigger on mentions of Resend CLI, `resend`, `resend doctor`, `resend emails send`, `resend domains`, `resend webhooks listen`, `resend emails receiving`, or agent-friendly terminal automation.
alibabacloud-odps-maxframe-coding
IncludedUse this skill for MaxFrame SDK development and documentation navigation on Alibaba Cloud MaxCompute (ODPS). Helps answer MaxFrame API, concept, official example, and supported pandas API questions; create data processing programs; read/write MaxCompute tables; debug jobs (remote or local); and build custom DPE runtime images. Trigger when users mention MaxFrame, MaxCompute with MaxFrame, ODPS table processing, DPE runtime, MaxFrame docs/examples, DataFrame/Tensor operations, or GPU runtime setup. Works for both English and Chinese queries about Alibaba Cloud data processing with MaxFrame.