NimbusNexus

Rate limiting

Every API key gets a per-second and per-hour request budget. The limits protect both you and us โ€” they prevent a runaway loop from burning your credit card and prevent any one tenant from saturating a region.

The defaults

TierPer secondPer hour
Free51,000
Starter2510,000
Production100100,000
EnterpriseCustomCustom

These apply per API key, not per project โ€” if you have three keys in one project, each gets its own budget. Most teams never come close to the production-tier limit; if you do, the dashboard's "rate limit usage" panel surfaces the call sites burning quota fastest.

How limiting works

A token-bucket on every key. The bucket refills at the per-second rate; the bucket size is set so a brief burst that drains the budget recovers in ~10 seconds. The hourly limit is a hard ceiling โ€” once you hit it, no requests succeed until the rolling hour rolls forward.

Limits are evaluated at the gateway, before the request reaches the resource handler. A rate-limited request never touches your project's resources, so it never costs you anything beyond the failed-request log entry.

Response headers

Every API response carries four headers documenting your current budget:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1763410812
X-RateLimit-Window: 1
  • X-RateLimit-Limit โ€” total budget for the current window
  • X-RateLimit-Remaining โ€” requests left
  • X-RateLimit-Reset โ€” Unix epoch when the budget refills (or the hourly window rolls)
  • X-RateLimit-Window โ€” 1 for per-second, 3600 for per-hour. We surface whichever budget you're closer to consuming

When Remaining hits zero, the next request returns 429 Too Many Requests with a Retry-After header (in seconds) telling you when to retry.

Backoff strategy

The right pattern when you hit 429:

async function callWithBackoff(fn: () => Promise<Response>) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fn()
    if (res.status !== 429) return res

    const retryAfter = Number(res.headers.get('Retry-After') ?? '1')
    // Jitter prevents the thundering-herd problem if many clients
    // hit the limit at the same moment and all retry exactly together
    const jitter = Math.random() * 0.5 * retryAfter
    await new Promise(r => setTimeout(r, (retryAfter + jitter) * 1000))
  }
  throw new Error('Rate limit exceeded after 5 retries')
}

Two notes:

  • Always honor Retry-After. Don't retry sooner โ€” you'll just hit the limit again and burn your budget faster.
  • Add jitter if you're calling from multiple workers. Without jitter, all your workers retry on the same tick and all hit the limit again together.

What counts as a request

Every HTTP call to the API counts as one request, including:

  • GETs (yes, even GET /v1/regions)
  • Pagination calls (each page is a request)
  • Idempotent retries (the cache hit still counts as one request โ€” but it's cheap, and the dedupe means you don't pay for the underlying operation twice)

What doesn't count:

  • Static assets served from the marketing site (CDN, not the API)
  • Webhook deliveries to your side (we send them, not you)
  • Failed connection attempts (no gateway log entry)

Raising your limits

The starter and production tiers are usually generous enough; less than 5 % of accounts ever hit the per-hour cap. If you do, the path is the support contact form, not the API โ€” same as quota raises. Production migrations get same-day turnaround.

If you're hitting per-second limits but not per-hour, you usually don't need a raise โ€” you need to spread load (batch requests, use Idempotency-Key to dedupe retries, paginate with reasonable page sizes). The "rate limit usage" panel in the dashboard breaks down which endpoints are spiking.

What's next

  • Conventions โ€” request shape, status codes, the broader API patterns.
  • Idempotency โ€” safe retry pattern when network failures look like rate limits.
  • Quotas โ€” the resource limits (VMs, vCPU, storage), as distinct from request-rate limits.