Rate Limiting
Hubbl's public API enforces a per-account request limit, shared across both the REST API (api.hubbl.com) and the MCP server (mcp.hubbl.com). Every request your account makes — REST or MCP, authenticated with an API key or a user's OAuth token — draws from the same quota.
This page covers the limits, the headers on every response, what a rate-limited response looks like on each surface, how to check your remaining quota, and the patterns we recommend for staying under the limit.
How the limit works
- Per-account and shared. All requests across both surfaces and both auth methods count against one account-level quota. A request made via the MCP server draws down the same allowance as a REST request.
- Two windows, enforced together. Your account has a daily limit and a monthly limit, both active at once. Exceeding either throttles you until that window resets — the other window keeps counting independently.
- Calendar-aligned, in UTC. The daily window resets at
00:00 UTCeach day; the monthly window resets at00:00 UTCon the first day of each month. These are fixed calendar boundaries, not a rolling window — your full allowance is available again the moment the window resets. - Limits are account-specific and may change. Do not hardcode them. Read your current limit and remaining allowance from the response headers or the quota endpoint at runtime.
Response headers
Every REST response — success or 429 — carries these headers:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
The maximum requests allowed in the current window. |
X-RateLimit-Remaining |
Requests remaining before you are throttled. |
X-RateLimit-Reset |
Unix timestamp (seconds) when the window resets and the count clears. |
X-RateLimit-Limit: <N>
X-RateLimit-Remaining: <N>
X-RateLimit-Reset: <unix-timestamp>
Because two windows are in effect at once, these headers describe a single window — the most restrictive one for you at that moment: the limit you are closest to hitting, or, when you are throttled, the one currently blocking you. For a full per-window breakdown of both daily and monthly state, call the quota endpoint.
When you exceed the limit
REST
The request returns HTTP 429 Too Many Requests with a Retry-After header (seconds to wait) alongside the standard X-RateLimit-* headers, and this body:
{
"error": {
"status": 429,
"title": "Too Many Requests",
"detail": "Rate limit exceeded. Retry after <N> seconds."
}
}
MCP
MCP tool calls have no HTTP status to return to the client, so a throttled call comes back as an error tool result. Detect it programmatically via structuredContent.error === "rate_limited"; retryAfterSeconds tells you how long to wait. The text content carries the same message for display.
{
"isError": true,
"content": [{ "type": "text", "text": "Rate limit exceeded. Retry in <N> seconds." }],
"structuredContent": {
"error": "rate_limited",
"retryAfterSeconds": <N>,
"limit": <N>
}
}
Check your quota
GET /v1/orgs/{orgId}/quota returns your account's current state for both windows. Checking your quota does not count against it — poll it as often as you need. Authenticate with an API key or OAuth, the same as any other endpoint.
Quota is account-level, so every org under your account returns identical numbers; the orgId only scopes the access check.
{
"data": {
"rateLimit": {
"daily": { "limit": <N>, "remaining": <N>, "resetAt": "2026-06-09T00:00:00.000Z" },
"monthly": { "limit": <N>, "remaining": <N>, "resetAt": "2026-07-01T00:00:00.000Z" }
}
}
}
On MCP, the get_org_quota tool wraps this endpoint and returns the same data in its structuredContent. Use it as a pre-flight check before launching an ambitious fan-out workflow so you can pace your calls.
Recommended client patterns
Honor
Retry-After/X-RateLimit-Reset. On a429, wait the indicated time before retrying — don't retry immediately or in a tight loop. IfRetry-Afteris absent (see edge throttling), fall back to exponential backoff with jitter.Watch
X-RateLimit-Remainingand slow down before it reaches zero, rather than waiting for the429.Pre-flight large jobs with the quota endpoint so you know your headroom before starting.
Prefer bulk reads over by-id fan-out. Fetching records one ID at a time costs one request — and one unit of quota — per record. Use the list endpoint's
filterwith theinoperator to fetch many in a single request instead:# Instead of N requests: GET /v1/orgs/{orgId}/connectedApps/{id} (repeated per id) # Make one request: GET /v1/orgs/{orgId}/connectedApps?filter=in(id,1,2,3)One request, one unit of quota, the same data. This is the single biggest lever for staying under the limit when reading many records.
Edge throttling
There are two distinct sources of a 429, and they look different:
- Hubbl's per-account rate limit (everything above): a
429with the JSON error body and theX-RateLimit-*headers. - Edge throttling: at extreme request rates, requests may be shed at the network edge before reaching Hubbl. That response is a bare
429without theX-RateLimit-*headers or the JSON body, and reflects raw burst rate rather than your account quota.
Handle both the same way — back off and retry — but a headerless 429 means you are bursting too fast; spread your requests out rather than firing them in parallel.