Table of Contents
A Power BI refresh against HubSpot can fail for a dozen reasons, but one of the most common and least understood is rate limiting on HubSpot’s side. The refresh either times out, throws a vague connection error, or completes with a green checkmark while quietly dropping records. This guide covers HubSpot’s rate limit structure, how those limits surface as Power BI refresh failures, and what to change in Power Query code and refresh architecture to stop it recurring. The pattern is not unique to HubSpot. Teams running Power BI against Salesforce hit comparable refresh failures tied to API throttling and schema drift, since default connector patterns struggle with any high-volume CRM source.
HubSpot API Rate Limit Structure by Subscription Tier
HubSpot enforces two types of limits on every connected app: a rolling burst limit measured in ten-second windows, and a daily cap that resets at midnight in the account’s time zone. The numbers depend on the Hub tier and on whether the app is privately distributed or a publicly listed OAuth app. For privately distributed apps, covering most custom Power Query builds using a private app token, limits scale with tier:
| Product Tier | Burst Limit (per 10 seconds, per app) | Daily Cap (shared across all apps in the account) |
|---|---|---|
| Free / Starter | 100 | 250,000 |
| Professional | 190 | 625,000 |
| Enterprise | 190 | 1,000,000 |
| Any tier + one API Limit Increase add-on (max 2 stacked) | 250 | +1,000,000 per add-on |
Publicly distributed OAuth apps follow a different rule: 110 requests per 10 seconds per HubSpot account that installs the app, excluding the CRM Search API. The add-on does not raise limits for public apps, so a Marketplace-listed connector cannot buy its way past that ceiling. The burst window is rolling, evaluated continuously over any trailing 10-second span.
The daily cap only applies to private app tokens, with effectively no daily ceiling for OAuth integrations. Private apps share one daily quota across every private app in that account, so a burst-heavy internal tool can eat into quota another integration depends on. Batch endpoints help: most CRM batch operations accept up to 100 records per call, turning 100 individual requests into one.
The CRM Search API’s Separate, Stricter Pool
The Search API does not share the general burst pool. It caps at 5 requests per second per account, applied collectively across every user and integration on that HubID, not per token. Its responses also omit all rate-limit headers, so there is no proactive signal to poll before hitting the wall. Any single Search query can also only paginate through 10,000 total results, a hard cap that does not lift on any plan. If a Power BI model pulls a filtered list through Search, these two ceilings usually determine whether the refresh finishes cleanly, not the account tier.
Rate-Limit Error Responses Power BI Users See
Knowing what HubSpot sends back when a limit is exceeded is the first step toward catching it in Power Query instead of letting it crash a refresh silently. Once a limit is breached, every subsequent call returns an HTTP 429 with a JSON body identifying which policy triggered it, via a `policyName` field (`DAILY` or the burst policy) and a human-readable `message`. HubSpot’s Marketplace certification rules cap acceptable error rates at 5% of total daily requests for listed apps, so some background rate of 429s is expected even from well-behaved integrations.
Retry-After handling is inconsistent. Workflow custom-code actions get automatic HubSpot-side retries for up to 3 days after a 429 or 5xx, backing off to a maximum 8-hour gap. The core REST API that Power Query calls directly does not document a `Retry-After` guarantee on 429s, though parsing and honoring it anyway is standard defensive practice among HubSpot developers.
Two other codes show up under sustained load: a 502 or 504 when HubSpot’s internal processing protections activate, and a 503 when HubSpot is temporarily degraded. Both call for the same treatment: a brief pause and a retry. One developer running roughly 10,000 calls a day reported around three sporadic 502 errors per 10,000 calls, a rate HubSpot support confirmed as normal.
The response headers worth watching:
| Header | Meaning | Notes |
|---|---|---|
| `X-HubSpot-RateLimit-Daily` | Allowed requests per day | Omitted on OAuth responses |
| `X-HubSpot-RateLimit-Max` | Max requests allowed in the rolling window | |
| `X-HubSpot-RateLimit-Remaining` | Requests remaining in the current window | Best header for proactive throttling |
None of these appear on CRM Search API responses, which is why Search-based refreshes tend to fail without warning.
How HubSpot Throttling Breaks a Power BI Refresh
When Power Query’s `Web.Contents` function calls `api.hubapi.com` and gets a 429 back, Power BI typically surfaces it as a generic `DataSource.Error` or an “unable to connect” failure unless the M code inspects the response. That vague error is often the only clue that HubSpot, not Power BI, caused the failure.
Scheduled refresh runs on its own clock, independent of HubSpot’s rate limit. On shared Pro capacity, imported semantic model refreshes time out after 2 hours. Premium, Premium Per User, and Fabric capacity extend that to 5 hours, though calling the XMLA endpoint directly to run a TMSL refresh script bypasses that limit entirely. These constraints compound badly with rate limiting: if Power Query is quietly backing off against a throttled endpoint for close to two hours, the service can kill the refresh on its own timeout before retry logic exhausts itself. If a refresh fails four times in a row, Power BI automatically disables the schedule, and an unaddressed rate-limit problem turns into a silently stale dataset.
The failure mode that causes the most damage is not the loud one. A documented pattern in custom HubSpot connector code wraps each paginated page fetch inside a `try…otherwise` block, meant to keep one bad page from crashing the whole refresh, but in practice it hides the problem instead. When a page call returns a 429 or 5xx mid-pagination, the `otherwise` branch quietly returns only the records fetched so far, and the refresh reports success with a lower row count than the source actually has. One community thread described exactly this: contacts missing from Power BI with no error message anywhere in the log. Without the `try…otherwise` guard, a 429 or 5xx instead propagates as a raw error and halts the refresh, easier to diagnose but less forgiving.
Why Standard Power BI Connector Patterns Are Not Built for This
Power BI does not ship a first-party, Microsoft-built native connector for HubSpot in its Get Data source list. What people call “the HubSpot connector” is usually one of three things: a certified third-party Marketplace app that stages HubSpot data outside Power BI and exposes it through SQL Server, OData, or CSV; a community-maintained custom connector, several of which broke after HubSpot deprecated legacy API-key authentication; or hand-written Power Query code calling `Web.Contents` directly against the HubSpot API. None of these are built or maintained by Microsoft, and none come with rate-limit awareness by default. This practical guide to Power BI connection types breaks down how Import, DirectQuery, and custom connector modes differ. None of the three common HubSpot connection paths behave like a native, Microsoft-supported source.
The hand-rolled pattern is the most common and the most fragile. A typical recursive pagination function, widely shared across community forums, calls the API and recurses on the next page inside a `try…otherwise` wrapper. This code has zero native rate-limit awareness: it never reads the remaining-quota header, never checks for a 429 status, and never pauses between calls unless someone manually adds that logic. Some versions add a fixed half-second delay per page regardless of actual remaining quota, a blunt fix rather than an adaptive one.
Power Query Fixes for HubSpot Rate Limiting
The fix starts with making Power Query aware that a non-200 response exists before deciding what to do about it. By default, `Web.Contents` throws immediately on error codes, so `ManualStatusHandling` needs to be set explicitly to let custom retry logic inspect the status first. A recursive fetch function with 429-aware exponential backoff and jitter looks like this:
“`powerquery-m
FetchWithRetry = (url as text, optional attempt as number) as record =>
let
n = if attempt = null then 0 else attempt,
resp = Web.Contents(url, [Headers=[Authorization=”Bearer ” & Token],
ManualStatusHandling={429,500,502,503,504}]),
status = Value.Metadata(resp)[Response.Status],
result =
if status = 200 then Json.Document(resp)
else if n >= 5 then error “Max retries exceeded”
else
let delay = #duration(0,0,0, Number.Power(2,n) + Number.RandomBetween(0,0.5)),
_ = Function.InvokeAfter(() => null, delay)
in @FetchWithRetry(url, n + 1)
in result
“`
A base delay of half a second to a second, doubling with each attempt and capped between 32 and 64 seconds, with jitter on top, keeps concurrent refreshes from retrying in lockstep and re-triggering the same limit together.
Cursor pagination through standard CRM v3 endpoints uses `paging.next.after`, fetching a page, extracting `results`, and recursing with `try @LL & @GetPages(Next) otherwise @LL`. That fallback is the pattern responsible for silent partial refreshes described earlier. If a page fetch fails on a 429 or 5xx, it quietly returns only what was collected so far with no error surfaced. Replace it with the retry logic above, or at minimum log a flag when a page attempt fails.
Two smaller changes reduce call volume and payload size together. Requesting only the properties actually needed keeps responses smaller and avoids HubSpot’s roughly 8,000 to 8,190 character URI length limit. Exceeding it otherwise produces a `414 Request-URI Too Large` error. Filtering on `hs_lastmodifieddate` with `GTE` and `LTE` operators turns a full re-pull into an incremental one, pulling only records changed since the last successful refresh. This same filtering logic is the foundation of incremental refresh for large semantic models, and teams scaling past a single HubSpot object will recognize the pattern once refresh policies grow beyond one source.
Handling the Search Endpoint’s 10,000-Result Cap
Because the CRM Search API caps any single query at 10,000 results with no override on any plan, large exports need to be chunked rather than pulled in one pass.
Date-window chunking sorts results by `createdate` ascending, identifies the date at which the 10,000th record falls, and applies an `LTE` filter on that date for the first chunk. The next chunk starts with a `GT` filter just after that boundary date, repeating until the full range is covered. Treating earlier chunks as static and only the most recent chunk as a live refresh avoids re-pulling historical data every run. Object-ID re-indexing solves the edge case where records share the same boundary date: sort ascending by `hs_object_id` and reset the cursor with an ID-based filter once it would exceed 10,000. For ongoing syncs, filtering by recently updated records instead of re-chunking the whole dataset keeps call volume manageable.
Architecture-Level Fixes Beyond Power Query Code
Fixing the Power Query layer helps, but it does not remove the underlying constraint that Power BI’s refresh window and HubSpot’s rate limits run on the same clock. The more durable fix is architectural: stop calling the HubSpot API directly from inside the Power BI refresh.
The pattern recommended across nearly every community discussion on this topic is to replicate HubSpot data into a warehouse, such as Snowflake, BigQuery, Postgres, or SQL Server, on its own independent sync schedule, then point Power BI at that warehouse instead. This removes HubSpot’s rate limits from the refresh path entirely. The warehouse sync runs against the actual limits while Power BI reads from an already-populated source with no throttling risk. This piece on fragile ETL pipelines covers why hand-built sync jobs degrade over time, the same failure mode that eventually pushes teams toward a managed staging layer.
Several tools handle that staging layer with built-in throttle logic rather than requiring custom code. General-purpose ETL platforms queue requests, apply backoff, and retry automatically to stay under HubSpot’s ceilings, and dedicated HubSpot Marketplace connectors take a similar approach built around HubSpot’s object model. This is where a managed HubSpot integration for Power BI fits in. The Power BI Connector for HubSpot is a certified HubSpot Marketplace app that batches reads to stay under typical rate limits, and surfaces failures explicitly as `RATE_LIMITED` or `429 Too Many Requests` in its export history rather than as a generic connection error. When a large first refresh or an aggressive schedule pushes against the ceiling, the remedy is the same regardless of connector: wait out the reset window, reduce refresh frequency, or narrow the scope of data pulled.
Webhook-driven pipelines offer a third path that sidesteps polling almost entirely. HubSpot workflow webhook actions do not count toward API rate limits at all, so a workflow can push contact or deal changes to an external endpoint the moment they happen. The most resilient version combines webhooks for near-real-time updates with periodic incremental syncs, plus occasional full batch reconciliation.
Refresh Scheduling Patterns That Reduce Throttling Risk
Scheduling decisions affect throttling risk as much as the code making the calls. Refreshing during off-peak windows and confirming that multiple datasets are not all triggering against the same HubSpot portal at once reduces the odds of stacking concurrent calls into the same ten-second window. Staggering integrations and backups so they do not run simultaneously is advice that shows up repeatedly wherever the rolling burst error gets discussed.
There is also a ceiling on the Power BI side. Pro and shared capacity allow only 8 scheduled or API-triggered refreshes per dataset per day, while Premium, PPU, and Fabric allow 48. Manual “Refresh now” clicks do not count against either quota. A team hitting HubSpot’s daily cap while also running near Power BI’s own refresh-count ceiling has two separate constraints stacking on top of each other. Solving one does not solve the other.
Monitoring HubSpot API Usage Before It Becomes a Refresh Problem
Catching a rate-limit problem before it breaks a scheduled refresh is largely a matter of checking usage somewhere other than the refresh error log. HubSpot’s developer platform surfaces this under Development, then Monitoring, then API call usage, showing consumption across every installed app, including third-party ones.
A lightweight programmatic check is a GET request to the account-info endpoint, returning current rate-limit headers without consuming meaningful quota. Some older discussions reference a daily rolling header, but no such header exists in current documentation. The real headers are `X-HubSpot-RateLimit-Daily`, `X-HubSpot-RateLimit-Daily-Remaining`, and `X-HubSpot-RateLimit-Remaining`. The confusion traces back to the `TEN_SECONDLY_ROLLING` policy name inside 429 error bodies.
When Upgrading the HubSpot API Tier Helps
Not every throttling problem is solved by paying HubSpot more money.
| Trigger Signal | Recommended Response |
|---|---|
| Hitting the burst limit regularly on Free or Starter | Upgrade to Professional, nearly doubling burst headroom to 190 per 10 seconds |
| Hitting the daily cap regularly on Professional | Upgrade to Enterprise or purchase an API Limit Increase add-on |
| Still hitting limits on Enterprise | Purchase one or two API Limit Increase add-ons for extra headroom |
| Search API bottleneck at its fixed 5 requests per second | No tier upgrade helps. The ceiling is fixed platform-wide |
| Frequent silent partial refreshes or multi-hour pulls | Consider a warehouse-staging architecture instead |
The API Limit Increase add-on is a paid capacity pack purchasable up to twice, and it has no effect on public Marketplace-distributed OAuth apps. Teams building on that distribution model solve throttling through code and architecture rather than a purchase. If the bottleneck is the Search API’s fixed ceiling, only chunking, caching, and incremental sync fix it. If it is Power BI’s own refresh timeout or refresh-count quota, the fix is a licensing or architecture change.
Matching Refresh Failure Signatures to Their Root Cause
Diagnosing the problem is usually a matter of matching what appears on screen to a known cause.
| Failure Signature in Power BI | Likely Cause | Fix |
|---|---|---|
| Generic `DataSource.Error` after a long-running refresh | A 429 partway through paginated calls was not caught | Add `ManualStatusHandling` plus exponential backoff |
| Refresh “succeeds” but row counts are lower than source | A silent `otherwise @LL` fallback swallowed a failed page | Replace the fallback with retry-and-resume logic |
| Refresh fails after almost exactly 2 hours | Shared-capacity refresh ceiling hit, often extended by retries | Move to Premium/PPU, or stage data in a warehouse |
| Explicit `429` or `RATE_LIMITED` in refresh history | Large first sync, concurrent refreshes, or exhausted quota | Wait for the reset window; reduce frequency and scope |
| `414 Request-URI Too Large` | Too many custom properties pushed the URI past its limit | Reduce requested properties or split the call |
| Search query returns exactly 10,000 rows and stops | CRM Search API’s hard result cap | Implement date-window or object-ID chunking |
| Scheduled refresh auto-disabled after several days | Refresh failed four times in a row | Fix the rate-limit cause, re-enable, add alerting |
| Works in Desktop but fails only in the Service | The Service enforces a timeout that Desktop does not | Check gateway version and credentials |
Most of these signatures trace back to one issue. HubSpot’s rate limits are real and predictable, but the default Power Query patterns most teams start with were never built to respect them. Reading response headers, handling 429s explicitly, and deciding early whether to fix this in code or move the problem to a staging layer determines whether HubSpot data stays reliable as the account grows.