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

# Rate limit

> Zapster API rate limit (3 requests per second per token), the RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers, the 429 rate_limited response, and how to handle it with backoff and a send queue.

The **rate limit** controls how many calls you can make to the Zapster API within a time window. When you go over the limit, the API responds with `429 Too Many Requests` instead of processing the request.

## Why the rate limit exists

The limit protects two things at once:

* **The platform**, keeping the API stable and fair for every customer, so a single integration cannot overload the service.
* **Your WhatsApp number**, preventing bursts that WhatsApp reads as spam behavior. Sending steadily is safer for your number's health than firing everything at once.

## Default limit

The default limit is **3 requests per second**, counted per access token (that is, per account). Since every API call is authenticated, the quota is always tied to your token.

<Note>
  The limit applies to **every** API route, not just message sending. Listing, recipient lookups, and instance management calls all consume the same quota.
</Note>

### Per-plan flexibility

The default limit fits most integrations. If your volume needs more, your account quota can be raised according to your plan or as a paid add-on. [Contact support](https://wa.me/5587999079455?text=Hi,%20I%20need%20to%20raise%20the%20rate%20limit%20on%20my%20account) so we can find the right limit and the best option for your case.

<Warning>
  Before asking for a higher limit, handle the rate limit on your side. The most robust way to integrate is to **never depend on the 429**: pace your outbound calls in your own code (queue and throttling) so you stay under the limit. A higher limit helps with spikes, but it does not replace client-side pacing.
</Warning>

## Rate limit headers

Every API response carries rate limit headers that report the current state of your quota. They follow the IETF [RateLimit header fields for HTTP](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/) standard.

| Header                | Description                                                                         |
| --------------------- | ----------------------------------------------------------------------------------- |
| `RateLimit-Limit`     | Maximum number of requests allowed in the current window (e.g. `3`).                |
| `RateLimit-Remaining` | How many requests are still left in the current window.                             |
| `RateLimit-Reset`     | How many **seconds** until the window resets (a relative time, not a timestamp).    |
| `RateLimit-Policy`    | The policy in `limit;w=window` form (e.g. `3;w=1` means 3 requests every 1 second). |
| `Retry-After`         | Present **only on the 429 response**. How many **seconds** to wait before retrying. |

### Example 200 response

A successful request, still within quota:

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
RateLimit-Policy: 3;w=1
RateLimit-Limit: 3
RateLimit-Remaining: 2
RateLimit-Reset: 1
```

### Example 429 response

When you exceed the limit, the API responds:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
RateLimit-Policy: 3;w=1
RateLimit-Limit: 3
RateLimit-Remaining: 0
RateLimit-Reset: 1
Retry-After: 1

{
  "errors": [
    {
      "code": "rate_limited",
      "messages": "You can only make 3 requests every 1 seconds."
    }
  ]
}
```

The `code` field is always `rate_limited`, and the `messages` text reflects your account's current limit and window size.

## How to handle the rate limit

The recommended strategy has two layers.

**1. Pace your calls before sending (preferred).** For high-volume integrations (batch sends, message queues), throttle your own outbound rate so you never exceed the limit. Use a queue with throttling (for example `bottleneck` or `p-queue` in Node.js, or `rate.Limiter` in Go) set to the same ceiling as your account. That way you never rely on receiving a 429.

**2. Handle the 429 when it happens (safety net).** Even with throttling, keep a retry:

* On a `429`, wait the number of seconds in `Retry-After` (or, if absent, in `RateLimit-Reset`) and try again.
* If no header is present, use **exponential backoff** with jitter (wait 1s, then 2s, 4s, and so on, up to a cap).
* Before sending, you can also read `RateLimit-Remaining`: if it is near zero, wait `RateLimit-Reset` seconds before the next call.

## Code examples

The examples below send a message and handle the `429` while respecting the headers. Replace `YOUR_API_TOKEN` with your token and `YOUR_INSTANCE_ID` with the instance ID.

<CodeGroup>
  ```javascript Node.js (client, fetch + backoff) theme={null}
  // Resilient client: respects Retry-After/RateLimit-Reset and backs off.
  async function sendWithRetry(payload, { maxRetries = 5 } = {}) {
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      const res = await fetch('https://api.zapsterapi.com/v1/wa/messages', {
        body: JSON.stringify(payload),
        headers: {
          Authorization: 'Bearer YOUR_API_TOKEN',
          'Content-Type': 'application/json',
          'X-Instance-ID': 'YOUR_INSTANCE_ID',
        },
        method: 'POST',
      })

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

      // Retry-After and RateLimit-Reset are in seconds.
      const hint =
        Number(res.headers.get('retry-after')) ||
        Number(res.headers.get('ratelimit-reset')) ||
        0
      // If the server sends no hint, fall back to exponential backoff (cap 30s).
      const backoff = Math.min(2 ** attempt, 30)
      const delayMs = (Math.max(hint, backoff) + Math.random() * 0.25) * 1000
      await new Promise((resolve) => setTimeout(resolve, delayMs))
    }

    throw new Error('Rate limit: retries exhausted')
  }

  const res = await sendWithRetry({
    recipient: '5511999999999',
    text: 'Hello! Your order is confirmed.',
  })
  console.log((await res.json()).message_id)
  ```

  ```javascript Node.js (server, throttled queue) theme={null}
  // For batch sends: cap the outbound rate at 3 req/s with bottleneck,
  // so you never hit the 429.
  const Bottleneck = require('bottleneck')

  const limiter = new Bottleneck({
    maxConcurrent: 3,
    minTime: 334, // ~1 request every 334ms = 3 per second
    reservoir: 3,
    reservoirRefreshAmount: 3,
    reservoirRefreshInterval: 1000,
  })

  async function sendMessage(payload) {
    const res = await fetch('https://api.zapsterapi.com/v1/wa/messages', {
      body: JSON.stringify(payload),
      headers: {
        Authorization: 'Bearer YOUR_API_TOKEN',
        'Content-Type': 'application/json',
        'X-Instance-ID': 'YOUR_INSTANCE_ID',
      },
      method: 'POST',
    })
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    return res.json()
  }

  // Every call goes through the queue, respecting the 3 req/s ceiling.
  const send = limiter.wrap(sendMessage)

  const recipients = ['5511999999999', '5511888888888', '5511777777777']
  await Promise.all(recipients.map((r) => send({ recipient: r, text: 'Hello!' })))
  ```

  ```python Python (requests + backoff) theme={null}
  import random
  import time

  import requests

  URL = "https://api.zapsterapi.com/v1/wa/messages"
  HEADERS = {
      "Authorization": "Bearer YOUR_API_TOKEN",
      "X-Instance-ID": "YOUR_INSTANCE_ID",
  }


  def send_with_retry(payload, max_retries=5):
      delay = 1.0  # backoff used only if the server sends no hint
      for _ in range(max_retries + 1):
          res = requests.post(URL, headers=HEADERS, json=payload, timeout=30)
          if res.status_code != 429:
              res.raise_for_status()
              return res.json()

          # Retry-After and RateLimit-Reset are in seconds.
          hint = res.headers.get("Retry-After") or res.headers.get("RateLimit-Reset")
          sleep_for = float(hint) if hint else delay
          time.sleep(sleep_for + random.uniform(0, 0.25))
          delay = min(delay * 2, 30)

      raise RuntimeError("Rate limit: retries exhausted")


  print(send_with_retry({"recipient": "5511999999999", "text": "Hello!"}))
  ```

  ```go Go (rate.Limiter + backoff) theme={null}
  package main

  import (
  	"bytes"
  	"context"
  	"fmt"
  	"net/http"
  	"strconv"
  	"time"

  	"golang.org/x/time/rate"
  )

  // 3 requests per second, matching the default account limit.
  var limiter = rate.NewLimiter(rate.Every(time.Second/3), 3)

  func send(ctx context.Context, payload []byte) (*http.Response, error) {
  	const maxRetries = 5
  	for attempt := 0; attempt <= maxRetries; attempt++ {
  		// Pace before sending: keep 3 req/s.
  		if err := limiter.Wait(ctx); err != nil {
  			return nil, err
  		}

  		req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
  			"https://api.zapsterapi.com/v1/wa/messages", bytes.NewReader(payload))
  		req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
  		req.Header.Set("X-Instance-ID", "YOUR_INSTANCE_ID")
  		req.Header.Set("Content-Type", "application/json")

  		res, err := http.DefaultClient.Do(req)
  		if err != nil {
  			return nil, err
  		}
  		if res.StatusCode != http.StatusTooManyRequests {
  			return res, nil
  		}
  		res.Body.Close()

  		// Retry-After and RateLimit-Reset are in seconds.
  		wait := time.Second
  		if v := res.Header.Get("Retry-After"); v != "" {
  			if s, convErr := strconv.Atoi(v); convErr == nil {
  				wait = time.Duration(s) * time.Second
  			}
  		}
  		select {
  		case <-time.After(wait):
  		case <-ctx.Done():
  			return nil, ctx.Err()
  		}
  	}
  	return nil, fmt.Errorf("rate limited: retries exhausted")
  }

  func main() {
  	payload := []byte(`{"recipient":"5511999999999","text":"Hello!"}`)
  	res, err := send(context.Background(), payload)
  	if err != nil {
  		panic(err)
  	}
  	defer res.Body.Close()
  	fmt.Println(res.Status)
  }
  ```

  ```bash cURL / bash theme={null}
  # Option A: curl retries on its own and honors Retry-After on the 429.
  curl --retry 5 --retry-all-errors --retry-delay 1 \
    -X POST https://api.zapsterapi.com/v1/wa/messages \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "X-Instance-ID: YOUR_INSTANCE_ID" \
    -H "Content-Type: application/json" \
    -d '{"recipient":"5511999999999","text":"Hello!"}'

  # Option B: manual control, reading the headers and sleeping until the window resets.
  url="https://api.zapsterapi.com/v1/wa/messages"
  for attempt in $(seq 1 5); do
    # -D saves the headers; -w returns the HTTP status.
    status=$(curl -sS -o response.json -D headers.txt -w '%{http_code}' \
      -X POST "$url" \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      -H "X-Instance-ID: YOUR_INSTANCE_ID" \
      -H "Content-Type: application/json" \
      -d '{"recipient":"5511999999999","text":"Hello!"}')

    if [ "$status" != "429" ]; then
      cat response.json
      break
    fi

    # RateLimit-Reset is the number of seconds until the window resets.
    reset=$(grep -i '^ratelimit-reset:' headers.txt | tr -d '\r' | awk '{print $2}')
    echo "Rate limited. Waiting ${reset:-1}s..."
    sleep "${reset:-1}"
  done
  ```
</CodeGroup>

## Summary

* The default is **3 requests per second** per access token, applied across the whole API.
* Every response carries `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`; the 429 also carries `Retry-After`. `RateLimit-Reset` and `Retry-After` are counted in seconds.
* The 429 returns `{ "errors": [{ "code": "rate_limited", ... }] }`.
* Pace your calls in your own code (queue and throttling) so you do not depend on the 429, and keep a retry with backoff as a safety net.
* Need more volume? [Contact support](https://wa.me/5587999079455?text=Hi,%20I%20need%20to%20raise%20the%20rate%20limit%20on%20my%20account) to evaluate a higher limit.
