HubSpot API rate limits.
HubSpot rate-limits every API token along two axes — a per-second burst and a per-day pool — and a 429 from either one halts your sync. HS-X's sync engine batches against the burst, tracks the daily pool, and backs off on Retry-After so you almost never see a 429 in your own code. This page is what the engine is doing and how to read the headers when something does slip through.
The 30-second answer
HubSpot enforces two limits on every API call: a burst limit (per-second) and a daily pool. Both are scoped to the access token, not the portal — so a private app and an OAuth app on the same portal don't share quota.
For most apps the practical ceilings are 150 requests per 10 seconds and 250,000 requests per day. Marketplace and enterprise tiers raise both. The full matrix is below.
HS-X's sync engine never issues a raw single-object write when a batch endpoint exists — every sync call routes through /batch/* (up to 100 records per call) and pauses on Retry-After. You'd have to do something unusual — a hand-written hubspot.fetch loop in a workflow action — to hit a 429 from inside an HS-X project.
Use batch endpoints; trust Retry-After; watch the daily pool, not the burst. The burst recovers in 10 seconds. Burning the daily pool means your sync stops shipping data until midnight UTC.
The limit matrix by tier
Every HubSpot account has a tier; every tier has a different ceiling. The numbers below are the public figures HubSpot publishes; private apps inherit the portal tier, public/marketplace apps get their own.
| Tier | Burst (per 10s) | Daily pool | Search API (per 10s) | Batch read (per 10s) |
|---|---|---|---|---|
| Free / Starter | 100 | 250,000 | 4 | 5 |
| Pro | 150 | 500,000 | 5 | 6 |
| Enterprise | 190 | 1,000,000 | 7 | 8 |
| Marketplace app (per install) | 150 | 500,000 | 5 | 6 |
| Marketplace · API Limit Increase | 200 | 1,000,000 | 10 | 10 |
A few notes that matter more than the numbers themselves:
- Search and Batch have their own buckets.
POST /crm/v3/objects/*/searchandPOST /crm/v3/objects/*/batch/*each count against a separate, much tighter per-10s bucket. A search-heavy workload runs out of search quota long before it runs out of general quota. - The daily pool resets at midnight UTC, not a rolling 24-hour window. A burst at 23:55 UTC followed by another at 00:05 UTC counts against two different days.
- OAuth apps and private apps don't share. Each token has its own counters. If your sync uses a private app token and a workflow action uses an OAuth token, they have independent quotas.
- The free tier of the Marketplace API Limit Increase is one credit per developer account. Beyond that it's a paid add-on; HubSpot rates the increase on a per-app basis.
What the response headers tell you
Every 2xx and every 429 from a HubSpot API call carries the same six headers. Read them once and you can debug any quota problem without leaving your terminal.
| Header | Meaning |
|---|---|
X-HubSpot-RateLimit-Daily | Your tier's daily ceiling. Constant per token. |
X-HubSpot-RateLimit-Daily-Remaining | Calls left until midnight UTC. The one to alert on. |
X-HubSpot-RateLimit-Interval-Milliseconds | Length of the burst window (always 10000). |
X-HubSpot-RateLimit-Max | Your tier's burst ceiling for this window. |
X-HubSpot-RateLimit-Remaining | Calls left in this 10-second window. |
X-HubSpot-RateLimit-Secondly-Remaining | Calls left in this one-second sub-window (Marketplace only). |
When you get a 429 Too Many Requests, HubSpot also returns:
| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before retrying. Integer, usually 1–10 for burst exhaustion, hours for daily exhaustion. |
$ curl -sI -H "Authorization: Bearer $TOKEN" \
https://api.hubapi.com/crm/v3/objects/contacts?limit=1
HTTP/2 200
x-hubspot-ratelimit-daily: 500000
x-hubspot-ratelimit-daily-remaining: 487211
x-hubspot-ratelimit-interval-milliseconds: 10000
x-hubspot-ratelimit-max: 150
x-hubspot-ratelimit-remaining: 148The two numbers that actually drive decisions are Daily-Remaining (capacity planning) and Retry-After on a 429 (operational behavior). Everything else is context.
What the sync engine does for you
You almost never need to think about any of the above when you're writing HS-X code. The sync engine sits between your pull function and HubSpot, and applies four behaviors automatically.
- Batching. Every write goes through
/crm/v3/objects/<type>/batch/{create,update,upsert,archive}in groups of 100. A 10,000-row sync issues 100 batch calls, not 10,000 single calls. This is the single biggest reason HS-X users don't see 429s. - Adaptive concurrency. The engine starts at 4 parallel batch calls and adjusts up or down based on
X-HubSpot-RateLimit-Remaining. If you drop below 20% headroom it halves concurrency; if you stay above 70% for two windows it doubles back up. Retry-Afterhonoring. A 429 pauses the entire sync (not just the failing batch) for the duration the header asks for, then resumes from the same cursor. Sub-second retries use the literal value; daily-exhaustion retries (Retry-After> 60) park the sync until next midnight UTC and emit adaily.exhaustedevent.- Search budget guard. Calls to
/searchendpoints are tracked separately. The engine reserves at least one search slot per window for record-level lookups (the kind you need for reconciliation) so a bulk search-driven backfill can't starve the rest of the sync.
Write a sync pull that calls hubspot.fetch('/crm/v3/objects/contacts/{id}', ...) per row instead of returning records for the engine to batch. The engine can only batch what you hand it; per-record fetches inside pull are outside the budget.
Capacity planning against the daily pool
The burst limit is self-healing — 10 seconds and it's back. The daily pool is the one that bites. A back-of-envelope sizing:
- A 5-minute sync of N records issues roughly
ceil(N/100)batch writes plus 1–2 reads per run. At N = 10,000 that's 102 calls × 288 runs/day = 29,376 calls/day for one sync. Well inside any tier. - A daily full-table refresh of N records issues
ceil(N/100)writes once. At N = 1,000,000 that's 10,000 calls/day. Also fine. - A search-driven reconciliation issues 1 search call per page (100 records). At 1M records that's 10,000 search calls/day — and the search bucket is much tighter, so this is where you start to plan around
search.budgetrather than the main pool.
The CLI's hs-x analyze quota command projects a 30-day quota burn from your current sync configurations against your portal's actual tier. Use it before turning on a new high-frequency sync; the output is usually enough to head off a quota incident a week before it happens.
$ hs-x analyze quota --window 30d
sync calls/day daily share p99 headroom
airtableContacts (5m) 29,376 5.9% 94%
stripeInvoices (1h) 2,304 0.5% 99%
salesforceDeals (15m) 12,672 2.5% 97%
─────── ─────
total 44,352 8.9% 91%p99 headroom is the engine's projection of "in the worst case, how much of the daily pool is left when the sync finishes for the day." Anything below 25% should prompt a conversation about cutting frequency or upgrading tier.
The four levers when you're hitting a limit
When hs-x analyze quota flags you, or when you start seeing daily.exhausted events in the dashboard, there are four moves — in order of cost.
- Move from individual to batch. Already done if you only use
worker.sync. If you have hand-writtenhubspot.fetchcalls in workflow actions, replacing them with/batch/*cuts request count by ~100×. Cheapest and almost always available. - Drop the frequency. A
5msync that mostly returns "no new rows" is mostly waste. Switch to a webhook-drivenhookif your source supports it, or back off to15m. Most syncs don't need the freshness their schedule implies. - Stretch the daily window with regional pinning. HubSpot's daily reset is UTC; if your team works US-Pacific, a heavy sync scheduled for 06:00 PT (= 13:00 UTC) leaves the rest of your day on a fresh pool. Set
schedule: { at: '13:00', tz: 'UTC' }to pin. - Request a Marketplace API Limit Increase. Available to listed marketplace apps and to enterprise portals on request. HubSpot reviews per-app and grants in 2× / 4× tiers. Submit via
developers.hubspot.com/marketplace/listings/api-limits. Approval typically takes 5–10 business days; you'll need a description of the workload and a sample sync configuration.
What to do when you see a 429 anyway
The engine emits a structured event on every 429. From the CLI:
$ hs-x logs --filter rate_limit --tail
2026-05-19T14:22:08Z sync=airtableContacts endpoint=POST /batch/upsert
status=429 retry_after=3s daily_remaining=412,003 attempt=1/5
2026-05-19T14:22:11Z sync=airtableContacts endpoint=POST /batch/upsert
status=200 daily_remaining=411,898 recovered_after=3.1sThree things to check, in order, before assuming the engine is misbehaving:
- Is the 429 from
Retry-After: <small>orRetry-After: <hours>? Small means burst; the engine handled it and the sync continued. Hours means daily exhaustion; checkhs-x analyze quotato see what burned the pool. - Was the 429 on
/searchor on a write endpoint? A/search429 with the main pool still healthy means you have a search-heavy workload and need to either index data into HubSpot properties (so you canGETinstead of/search) or add HS-X'ssearch.budget: 'reserve'mode. - Was the token a private app or OAuth? A 429 only on the private app, with OAuth still healthy, points at a workflow-action or hook handler doing per-row fetches outside the sync engine.
If the engine is misbehaving — concurrency stays high through a sustained 429 burst, or Retry-After is ignored — file an issue with the contents of hs-x logs --filter rate_limit --window 1h --format json. The structured event log is enough to reproduce locally; no portal credentials are needed.