Sb Sabee
Docs

API rate limits.

Every Sabee plan that includes API access has a request quota measured in requests per minute, enforced per API key. This page documents the limits by plan, the headers returned on every response, how to handle a 429, and how to design an integration that stays comfortably under quota.

Limits by plan

Rate limits are set per API key and scale with plan tier, matching the same Solo, Growth, Business and Enterprise tiers described on the pricing page. API access itself is available from Growth upward; Solo is shown below only for reference since Solo accounts do not have API keys.

PlanRequests per minuteAPI access
Solo60 rpmNot available on this tier
Growth300 rpmIncluded
Business1,200 rpmIncluded, priority queuing
EnterpriseCustomIncluded, dedicated quota negotiated per account

The 60 rpm figure is shown for context because it's the ceiling Sabee applies internally to any unauthenticated or minimally-scoped request pattern; in practice, every integration you build will run under a Growth-tier key or higher, so 300 rpm is the effective floor for real API usage.

Rate-limit headers

Every API response — successful or not — includes three headers describing your current quota state.

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 274
X-RateLimit-Reset: 1753524900

X-RateLimit-Limit is the total requests allowed in the current window for your plan. X-RateLimit-Remaining is how many requests you have left before the window resets. X-RateLimit-Reset is a Unix timestamp for when the current window ends and the count resets to the full limit. Reading these headers on every response — not just on a 429 — lets your integration slow down proactively before hitting the ceiling, rather than reactively after being throttled.

Handling a 429

When you exceed your plan's requests-per-minute limit, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before your next request is likely to succeed.

HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1753524900

{
  "error": {
    "code": "SB-1002",
    "message": "Rate limit exceeded. Retry after the indicated interval.",
    "status": 429
  }
}

The correct client behaviour is to honour Retry-After literally rather than immediately retrying — hammering the API again right after a 429 typically triggers the same response and wastes another request against your quota window. A minimal correct retry loop reads the header, sleeps for that duration (plus a small jitter to avoid synchronised retries if you're running multiple workers), and then retries once.

async function requestWithBackoff(url, options) {
  const res = await fetch(url, options);
  if (res.status === 429) {
    const retryAfter = Number(res.headers.get("Retry-After")) || 5;
    await new Promise(r => setTimeout(r, (retryAfter + Math.random()) * 1000));
    return fetch(url, options);
  }
  return res;
}

What counts against the limit

The rate limit is enforced per API key, across all endpoints combined — there is no separate quota per resource type. A key making 200 requests to /v1/reservations and 100 requests to /v1/invoices in the same minute on a Growth-tier account (300 rpm) has used its full quota for that window regardless of how the requests were split across endpoints. Webhook deliveries sent by Sabee to your endpoint do not count against your API rate limit at all — the limit only applies to requests your systems make to Sabee's API.

Burst behaviour

The limit is a rolling per-minute window rather than a strict fixed-clock minute — sending 300 requests in the first ten seconds of a minute on a Growth-tier key will trigger a 429 for the remainder of that rolling window, even though the fixed clock minute hasn't technically elapsed. If your integration needs to process a burst of updates (for example, syncing a large historical import), spread requests evenly across the available quota rather than sending them as fast as possible, or use the bulk-friendly list endpoints with a higher limit parameter to reduce the total request count instead of relying on burst capacity that doesn't exist.

Best practices

Cache data that doesn't change often — room types and rate plan structures typically change far less frequently than reservations, so polling them on the same schedule as reservation data wastes quota. Prefer webhooks over polling wherever an event-driven pattern is available; a webhook delivery costs nothing against your rate limit, while polling /v1/reservations every 30 seconds to detect new bookings burns through quota for information Sabee would happily push to you for free. When you do need to poll, read X-RateLimit-Remaining and slow your polling interval as it approaches zero rather than waiting for the first 429. Finally, if your integration architecture uses multiple processes or servers sharing one API key, coordinate their request budget between them — the limit is per key, not per process, so five workers each assuming they have the full 300 rpm allowance will collectively exceed it.

Enterprise & custom limits

Enterprise accounts negotiate a dedicated quota as part of onboarding, sized to the actual integration workload — a group running nightly batch synchronisation across two hundred properties has a very different traffic shape than a single hotel pushing live reservation updates to an internal dashboard, and Enterprise contracts are scoped accordingly. If you're on Business tier and consistently running close to the 1,200 rpm ceiling, talk to us through contact before you hit a wall in production — it's usually possible to either optimise the integration's request pattern or move to an Enterprise quota that fits the actual workload. In our experience, the majority of accounts approaching their limit are polling an endpoint that has a webhook equivalent, so the first question we ask is which events could arrive as a push notification instead, at effectively zero cost against your quota.

Rate limits apply identically across the sandbox and production environments described in the API reference, using the tier associated with the account the sandbox key belongs to — so load-testing an integration against the sandbox is a reliable way to confirm your retry and backoff logic behaves correctly before it ever touches a live property's data.

Building a high-volume integration?

Tell us your expected request volume and we'll help you design an efficient integration before you hit a rate limit in production.