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

# Rate Limits

> Per-API-key rate limit policies, response headers, and recommended strategies for high-volume campaigns.

The Connexease Gateway enforces rate limits per API key to align with Meta's Cloud API tier allowances and protect infrastructure stability. Limits are applied using a sliding window algorithm — not a fixed clock window — so bursts at interval boundaries are handled fairly. This page explains the default limits, how to read the response headers, what to expect when a limit is exceeded, and two practical strategies for high-volume campaigns.

## Default Limits

Limits are applied **per API key** (per application), not globally.

| Window     | Default       | Notes                                        |
| ---------- | ------------- | -------------------------------------------- |
| Per second | 80 req/s      | Aligned with Meta Cloud API Tier 1 MPS limit |
| Per minute | 4,800 req/min | = 80 × 60                                    |

<Note>
  These limits are configurable. Contact the Connexease team for Enterprise plan options.
</Note>

***

## Response Headers

Every successful request to `/v1/wa/message` includes remaining quota information:

```http theme={null}
HTTP/1.1 200 OK
X-RateLimit-sec-Limit: 80
X-RateLimit-sec-Remaining: 62
X-RateLimit-min-Limit: 4800
X-RateLimit-min-Remaining: 4745
```

| Header                      | Description                           |
| --------------------------- | ------------------------------------- |
| `X-RateLimit-sec-Limit`     | Maximum requests allowed per second   |
| `X-RateLimit-sec-Remaining` | Remaining quota in the current second |
| `X-RateLimit-min-Limit`     | Maximum requests allowed per minute   |
| `X-RateLimit-min-Remaining` | Remaining quota in the current minute |

***

## When a Limit Is Exceeded

```http theme={null}
HTTP/1.1 429 Too Many Requests
X-RateLimit-sec-Limit: 80
X-RateLimit-sec-Remaining: 0
```

```json theme={null}
{
  "isSuccess": false,
  "errors": {
    "code": "RATE_001",
    "group": "TOO_MANY_REQUESTS",
    "description": "Rate limit exceeded. Please try again later.",
    "params": {
      "limit_type": "sec",
      "retry_after": "1s"
    }
  }
}
```

**`params` Fields:**

| Field         | Values           | Description                      |
| ------------- | ---------------- | -------------------------------- |
| `limit_type`  | `"sec"`, `"min"` | Which window was exceeded        |
| `retry_after` | `"1s"`, `"60s"`  | How long to wait before retrying |

<Note>
  If you receive `limit_type: "min"`, your per-minute quota is exhausted even if the per-second limit is fine. Spread your traffic more evenly throughout the minute.
</Note>

***

## How It Works

The Gateway uses a **true sliding window** algorithm backed by Redis sorted sets. Each request adds a timestamped entry; expired entries are pruned before counting.

This means:

* No sudden spikes at the start of each clock second (unlike fixed windows).
* The limit reflects actual throughput over the trailing window.
* The per-second and per-minute windows are independent.

***

## Handling 429 Responses

### Strategy 1 — Exponential Backoff (Short Campaigns)

<CodeGroup>
  ```javascript Node.js theme={null}
  async function sendWithRetry(payload, maxRetries = 5) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      const res = await fetch('https://api.gateway.connexease.com/v1/wa/message', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload),
      });

      if (res.status !== 429) return res;

      const data = await res.json();
      const retryAfterMs = parseInt(data.errors.params.retry_after) * 1000;
      const waitMs = retryAfterMs * attempt; // exponential

      console.warn(`Rate limited (attempt ${attempt}/${maxRetries}). Waiting ${waitMs}ms`);
      await new Promise(r => setTimeout(r, waitMs));
    }
    throw new Error('Max retries exceeded');
  }
  ```

  ```python Python theme={null}
  import time, requests

  def send_with_retry(payload, api_key, max_retries=5):
      for attempt in range(1, max_retries + 1):
          res = requests.post(
              "https://api.gateway.connexease.com/v1/wa/message",
              headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
              json=payload,
          )

          if res.status_code != 429:
              return res

          data = res.json()
          retry_after = int(data["errors"]["params"]["retry_after"].replace("s", ""))
          wait = retry_after * attempt  # exponential
          print(f"Rate limited (attempt {attempt}/{max_retries}). Waiting {wait}s")
          time.sleep(wait)

      raise Exception("Max retries exceeded")
  ```

  ```go Go theme={null}
  func sendWithRetry(payload []byte, apiKey string) (*http.Response, error) {
      maxRetries := 5
      for attempt := 1; attempt <= maxRetries; attempt++ {
          req, _ := http.NewRequest("POST",
              "https://api.gateway.connexease.com/v1/wa/message",
              bytes.NewReader(payload),
          )
          req.Header.Set("Authorization", "Bearer "+apiKey)
          req.Header.Set("Content-Type", "application/json")

          resp, err := http.DefaultClient.Do(req)
          if err != nil || resp.StatusCode != 429 {
              return resp, err
          }

          wait := time.Duration(attempt) * time.Second
          time.Sleep(wait)
      }
      return nil, errors.New("max retries exceeded")
  }
  ```
</CodeGroup>

### Strategy 2 — Queue-Based Throttling (High-Volume Campaigns)

Proactive throttling keeps you safely below the limit without ever hitting 429.

<CodeGroup>
  ```javascript Node.js (p-queue) theme={null}
  import PQueue from 'p-queue'; // npm install p-queue

  // 72 req/s — 10% safety margin below the 80 req/s limit
  const queue = new PQueue({ intervalCap: 72, interval: 1000 });

  async function sendCampaign(recipients) {
    for (const recipient of recipients) {
      queue.add(() => sendMessage(recipient));
    }
    await queue.onIdle();
    console.log('Campaign complete');
  }
  ```

  ```python Python (asyncio) theme={null}
  import asyncio

  RATE_LIMIT = 72  # safe margin below 80

  async def send_campaign(recipients, api_key):
      semaphore = asyncio.Semaphore(RATE_LIMIT)

      async def send_one(recipient):
          async with semaphore:
              # async HTTP request here
              await asyncio.sleep(1 / RATE_LIMIT)

      await asyncio.gather(*[send_one(r) for r in recipients])
  ```
</CodeGroup>

***

## Relationship to Meta Cloud API Tiers

The Gateway's default 80 req/s limit aligns with Meta's **Tier 1** MPS (Messages Per Second) allowance.

| Meta Tier | Max MPS | Requirements                         |
| --------- | ------- | ------------------------------------ |
| Tier 1    | 80      | WhatsApp Business account approval   |
| Tier 2    | 250     | Tier 1 + 1,000 unique users reached  |
| Tier 3    | 1,000   | Tier 2 + 10,000 unique users reached |

<Note>
  As your Meta tier increases, your Gateway limit can be raised accordingly. Apply through Meta Business Manager first, then contact the Connexease team to update your limit.
</Note>

***

## Tips

<Tip>
  Monitor `X-RateLimit-sec-Remaining`. Start proactive throttling when it drops below 10.
</Tip>

<Tip>
  Before a bulk send, estimate your TPS: `total_messages / campaign_duration_seconds < 72` to stay within the safe margin.
</Tip>

<Warning>
  If you consistently receive `limit_type: "min"`, your per-minute quota is insufficient. Contact the Connexease team for an Enterprise plan.
</Warning>
