> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.rhombus.community/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate Limits

> Understand Rhombus API rate limits and build resilient integrations that handle 429 throttled responses with exponential backoff and retry logic.

All API requests authenticated via **API key** or **OAuth token** are subject to rate limiting. Limits are enforced **per organization** — every API key and OAuth token issued to your organization shares one rate-limit budget. Creating additional credentials does not raise your limit.

| Authentication method | Header                | Applies to                               |
| --------------------- | --------------------- | ---------------------------------------- |
| API key               | `x-auth-apikey`       | Developer and Partner Developer accounts |
| OAuth token           | `x-auth-access-token` | OAuth-authorized applications            |

## How limits work

Rhombus uses a **token bucket** algorithm. Two values govern your throughput:

* A **sustained refill rate** (requests per second) — your steady-state ceiling.
* A **burst capacity**, roughly 10× the refill rate by default — headroom that absorbs short spikes.

Bursts succeed instantly while the bucket has tokens. Once the bucket drains, sustained traffic above the refill rate returns `429`s until the bucket refills.

Default rates apply to all organizations. To request a higher limit, contact [Rhombus support](mailto:support@rhombus.com).

## Throttled responses

When your request is rate limited, the API returns a `429` status with a `Retry-After` header:

```text HTTP 429 response theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Content-Type: text/plain

Too many api requests. Enhance your calm.
```

| Detail               | Value                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------- |
| Status code          | `429`                                                                                                     |
| `Retry-After` header | Seconds to wait before retrying. Computed from your organization's refill rate; always at least 1 second. |
| Body                 | `Too many api requests. Enhance your calm.`                                                               |

<Warning>
  Do **not** immediately retry on a `429`. Repeated requests while rate limited continue to be rejected and do not reset your limit window.
</Warning>

## Handling rate limits

<Steps>
  <Step title="Read the Retry-After header">
    The `Retry-After` header tells you exactly how many seconds to wait, computed from your organization's refill rate. Always prefer this value over hardcoded delays.
  </Step>

  <Step title="Pause requests">
    Stop sending requests for the duration specified in the header.
  </Step>

  <Step title="Retry your request">
    After the wait period, retry the original request.
  </Step>
</Steps>

## Retry strategy

Use **exponential backoff with jitter** for the most resilient integration:

```text Backoff formula theme={null}
delay = min(base_delay × 2^attempt + random_jitter, max_delay)
```

| Parameter          | Recommended value |
| ------------------ | ----------------- |
| Base delay         | 1 second          |
| Max delay          | 60 seconds        |
| Max retry attempts | 5–10              |
| Jitter             | Random 0–1 second |

<Tabs>
  <Tab title="Python">
    ```python retry_with_backoff.py theme={null}
    import time
    import random
    import requests

    def call_api(url, headers, payload, max_retries=5):
        for attempt in range(max_retries):
            response = requests.post(url, headers=headers, json=payload)

            if response.status_code == 200:
                return response.json()

            if response.status_code == 429:
                wait = int(response.headers.get("Retry-After", 60))
                time.sleep(wait)
                continue

            if response.status_code >= 500:
                delay = min(1 * (2 ** attempt) + random.random(), 60)
                time.sleep(delay)
                continue

            # 4xx errors (other than 429) are not retryable
            response.raise_for_status()

        raise Exception("Max retries exceeded")

    # Usage
    result = call_api(
        url="https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList",
        headers={
            "x-auth-scheme": "api-token",
            "x-auth-apikey": "YOUR_API_KEY",
            "Content-Type": "application/json"
        },
        payload={}
    )
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript retryWithBackoff.js theme={null}
    async function callApi(url, headers, payload, maxRetries = 5) {
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        const response = await fetch(url, {
          method: "POST",
          headers: { "Content-Type": "application/json", ...headers },
          body: JSON.stringify(payload),
        });

        if (response.ok) {
          return await response.json();
        }

        if (response.status === 429) {
          const wait = parseInt(response.headers.get("Retry-After") || "60", 10);
          await new Promise((r) => setTimeout(r, wait * 1000));
          continue;
        }

        if (response.status >= 500) {
          const delay = Math.min(1 * 2 ** attempt + Math.random(), 60);
          await new Promise((r) => setTimeout(r, delay * 1000));
          continue;
        }

        // 4xx errors (other than 429) are not retryable
        throw new Error(`Request failed: ${response.status} ${response.statusText}`);
      }

      throw new Error("Max retries exceeded");
    }

    // Usage
    const result = await callApi(
      "https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList",
      {
        "x-auth-scheme": "api-token",
        "x-auth-apikey": "YOUR_API_KEY",
      },
      {}
    );
    ```
  </Tab>

  <Tab title="cURL">
    ```bash retry_loop.sh theme={null}
    #!/bin/bash
    URL="https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList"
    API_KEY="YOUR_API_KEY"
    MAX_RETRIES=5

    for attempt in $(seq 0 $((MAX_RETRIES - 1))); do
      HTTP_CODE=$(curl -s -o /tmp/response.json -D /tmp/response.headers -w "%{http_code}" \
        -X POST "$URL" \
        -H "Content-Type: application/json" \
        -H "x-auth-scheme: api-token" \
        -H "x-auth-apikey: $API_KEY" \
        -d '{}')

      if [ "$HTTP_CODE" -eq 200 ]; then
        cat /tmp/response.json
        exit 0
      elif [ "$HTTP_CODE" -eq 429 ]; then
        RETRY_AFTER=$(awk 'tolower($1) == "retry-after:" { gsub(/[^0-9]/, "", $2); print $2; exit }' /tmp/response.headers)
        : "${RETRY_AFTER:=60}"
        echo "Rate limited. Waiting ${RETRY_AFTER} seconds..." >&2
        sleep "$RETRY_AFTER"
      elif [ "$HTTP_CODE" -ge 500 ]; then
        DELAY=$(echo "1 * 2^$attempt" | bc)
        [ "$DELAY" -gt 60 ] && DELAY=60
        echo "Server error ($HTTP_CODE). Retrying in ${DELAY}s..." >&2
        sleep "$DELAY"
      else
        echo "Client error ($HTTP_CODE). Not retrying." >&2
        cat /tmp/response.json >&2
        exit 1
      fi
    done

    echo "Max retries exceeded." >&2
    exit 1
    ```
  </Tab>
</Tabs>

## When to retry

| Response        | Action                                                  |
| --------------- | ------------------------------------------------------- |
| `200–299`       | Success — no retry needed                               |
| `429`           | Wait for `Retry-After` seconds, then retry              |
| `5xx`           | Transient server error — retry with exponential backoff |
| `4xx` (not 429) | Client error — fix the request, do not retry            |

<Info>
  The rate limiting system is **fail-open**. If the rate-limit service or its backing store is unavailable, your request is allowed through. Do not rely on this behavior — always design your integration to respect limits.
</Info>

## What changed

<Note>
  Recent rate-limiter improvements are now live:

  * Limits are now pooled across all of an organization's credentials. Distributing traffic across multiple API keys no longer increases throughput.
  * Short bursts above your sustained rate are now allowed.
  * `Retry-After` reflects the true wait time computed from your refill rate, instead of a fixed 60 seconds.
</Note>

## Alert notification throttling

Alert notifications (distinct from API rate limits) have a configurable **minimum interval** between consecutive alerts of the same type for a given device. This prevents alert fatigue from high-frequency events like motion detection.

| Interval                | Seconds |
| ----------------------- | ------- |
| 1 minute                | 60      |
| 2 minutes               | 120     |
| **5 minutes (default)** | 300     |
| 10 minutes              | 600     |
| 20 minutes              | 1200    |
| 30 minutes              | 1800    |
| 1 hour                  | 3600    |

Configure this per policy and per activity type. During the backoff window, duplicate alerts for the same device and activity type are suppressed server-side.

<Tip>
  Alerts with **new identity information** — such as a different recognized face or license plate — bypass the backoff and are delivered immediately, even within the suppression window.
</Tip>
