# Rate Limits & Pagination

Understand the throughput limits, pagination patterns, and best practices for building reliable, high-volume integrations with the eCommunicate API.

Authentication Required
All API requests require your Developer Key in the `Authorization` header. No `Bearer` prefix — pass the key directly.
See [Authentication](/guides/authentication) for setup and security best practices.

## Rate limits

### WhatsApp Cloud

WhatsApp Cloud rate limits are managed by **Meta** and depend on your phone number's quality rating and messaging tier:

| Messaging tier | Messages per 24 hours | How to reach it |
|  --- | --- | --- |
| Tier 1 | 1,000 | New numbers start here |
| Tier 2 | 10,000 | Maintain high quality rating |
| Tier 3 | 100,000 | Consistently high delivery and read rates |
| Tier 4 | Unlimited | Enterprise-grade quality history |


Quality rating
Meta evaluates your quality based on delivery rates, read rates, and user block/report rates. Maintain quality by sending relevant, opted-in messages and honoring unsubscribes promptly.

### WhatsApp Lite

WhatsApp Lite throughput is controlled by your **message processing configuration**:

| Setting | Endpoint | Description |
|  --- | --- | --- |
| Batch size | `PUT /MessageProcessingConfig` | Number of messages processed per batch cycle |
| Throttle delay | `PUT /MessageProcessingConfig` | Delay between batch cycles (in milliseconds) |
| Daily schedule | `PUT /MessageProcessingConfig` | Start/end times for message processing |


Configure these per WhatsApp number to balance throughput with connection stability.

### SMS

SMS throughput depends on your account configuration and carrier agreements. Contact [support@ecommunicate.co.za](mailto:support@ecommunicate.co.za) for your specific limits.

### API rate limits

The eCommunicate API enforces rate limits on API calls to protect platform stability:

| Scenario | HTTP status | Response |
|  --- | --- | --- |
| Rate limit exceeded | `429 Too Many Requests` | Retry after the indicated delay |
| Server overloaded | `503 Service Unavailable` | Retry with exponential backoff |


## Handling rate limits

When you receive a `429` or `503` response, implement **exponential backoff**:

Node.js
```javascript
async function sendWithBackoff(payload, maxRetries = 5) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Authorization': API_KEY, 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });

    if (response.ok) return response.json();

    if (response.status === 429 || response.status >= 500) {
      if (attempt === maxRetries) throw new Error(`Failed after ${maxRetries} retries`);
      const delay = Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30s
      console.log(`Rate limited. Retrying in ${delay}ms (attempt ${attempt + 1})`);
      await new Promise(r => setTimeout(r, delay));
      continue;
    }

    // 4xx client errors — do not retry
    const error = await response.json();
    throw new Error(`API Error ${response.status}: ${error.message}`);
  }
}
```

Python
```python
import time

def send_with_backoff(payload, max_retries=5):
    for attempt in range(max_retries + 1):
        response = requests.post(
            url,
            headers={"Authorization": API_KEY, "Content-Type": "application/json"},
            json=payload,
        )

        if response.ok:
            return response.json()

        if response.status_code in (429, 500, 502, 503):
            if attempt == max_retries:
                raise Exception(f"Failed after {max_retries} retries")
            delay = min(2 ** attempt, 30)  # Max 30s
            print(f"Rate limited. Retrying in {delay}s (attempt {attempt + 1})")
            time.sleep(delay)
            continue

        # 4xx client errors — do not retry
        response.raise_for_status()
```

Java
```java
import java.net.http.*;
import java.net.URI;
import java.time.Duration;

public String sendWithBackoff(String payload, int maxRetries) throws Exception {
    HttpClient client = HttpClient.newHttpClient();

    for (int attempt = 0; attempt <= maxRetries; attempt++) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Authorization", API_KEY)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() >= 200 && response.statusCode() < 300) {
            return response.body();
        }

        if (response.statusCode() == 429 || response.statusCode() >= 500) {
            if (attempt == maxRetries) throw new RuntimeException("Failed after " + maxRetries + " retries");
            long delay = Math.min((long) Math.pow(2, attempt) * 1000, 30000); // Max 30s
            System.out.printf("Rate limited. Retrying in %dms (attempt %d)%n", delay, attempt + 1);
            Thread.sleep(delay);
            continue;
        }

        // 4xx client errors — do not retry
        throw new RuntimeException("API Error " + response.statusCode() + ": " + response.body());
    }
    return null;
}
```

Do not retry client errors
Only retry `429` and `5xx` responses. Client errors (`400`, `401`, `403`, `404`) indicate a problem with your request — retrying will produce the same result.

## Pagination

Most list endpoints in the eCommunicate API use **page-based pagination** with zero-indexed page numbers. A few return a flat, unpaginated array instead — those are called out where they appear, and are listed at the end of this section.

### Request parameters

| Parameter | Type | Default | Description |
|  --- | --- | --- | --- |
| `page` | integer | `0` | Page number (zero-based) |
| `size` | integer | `10` | Number of items per page. Most endpoints default to `10`; a few default to `20` — check the endpoint's reference page if the exact default matters |


### Example request

```bash
curl -X GET \
  "https://api.payless4messaging.com/payless4messaging-service/WhatsApp/WhatsAppConversations/getAllWhatsAppConversationsForCurrentUser?page=0&size=20" \
  -H "Authorization: $ECOMM_API_KEY"
```

### Paginated response structure

Paginated endpoints return results within a standard wrapper:

```json
{
  "content": [ ... ],
  "totalElements": 150,
  "totalPages": 8,
  "size": 20,
  "number": 0,
  "first": true,
  "last": false
}
```

| Field | Type | Description |
|  --- | --- | --- |
| `content` | array | The items for the current page |
| `totalElements` | integer | Total number of items across all pages |
| `totalPages` | integer | Total number of pages |
| `size` | integer | Requested page size |
| `number` | integer | Current page number (zero-based) |
| `first` | boolean | `true` if this is the first page |
| `last` | boolean | `true` if this is the last page |


### Iterating through pages

Node.js
```javascript
async function fetchAllPages(path, size = 50) {
  const results = [];
  let page = 0;
  let hasMore = true;

  while (hasMore) {
    const data = await apiRequest('GET', `${path}?page=${page}&size=${size}`);
    results.push(...data.content);
    hasMore = !data.last;
    page++;
  }

  return results;
}

// Example: Fetch all conversations
const conversations = await fetchAllPages(
  '/payless4messaging-service/WhatsApp/WhatsAppConversations/getAllWhatsAppConversationsForCurrentUser'
);
```

Python
```python
def fetch_all_pages(path, size=50):
    results = []
    page = 0

    while True:
        data = api_request("GET", f"{path}?page={page}&size={size}")
        results.extend(data["content"])
        if data["last"]:
            break
        page += 1

    return results

# Example: Fetch all conversations
conversations = fetch_all_pages(
    "/payless4messaging-service/WhatsApp/WhatsAppConversations/getAllWhatsAppConversationsForCurrentUser"
)
```

Java
```java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;

public List<JsonNode> fetchAllPages(String path, int size) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    List<JsonNode> results = new ArrayList<>();
    int page = 0;
    boolean hasMore = true;

    while (hasMore) {
        String response = apiRequest("GET", path + "?page=" + page + "&size=" + size);
        JsonNode data = mapper.readTree(response);
        for (JsonNode item : data.get("content")) {
            results.add(item);
        }
        hasMore = !data.get("last").asBoolean();
        page++;
    }

    return results;
}

// Example: Fetch all conversations
List<JsonNode> conversations = fetchAllPages(
    "/payless4messaging-service/WhatsApp/WhatsAppConversations/getAllWhatsAppConversationsForCurrentUser", 50
);
```

## Endpoints that support pagination

| Category | Endpoint | Filterable by |
|  --- | --- | --- |
| **Conversations** | `GET /payless4messaging-service/WhatsApp/WhatsAppConversations/getAllWhatsAppConversationsForCurrentUser` | `page`, `size`, `searchString` |
| **Chat Participants** | `GET /pl4chat-service/ChatParticipant/get*` | `page`, `size`, `searchString`, `clientRef` |
| **Tickets** | `GET /pl4chat-service/ticket/search` | `page`, `size`, dates, `agentId`, `selectedStatus` |
| **SMS Reports** | `GET /SMSSendReceiveMessages/get*` | `page`, `size`, `direction`, `mode`, `searchString` |
| **Message Reports** | `GET /message/status/*` | `page`, `size`, dates |
| **Contacts** | `GET /WhatsApp/contacts` | `page`, `size` |


Not every list endpoint paginates
`GET /payless4messaging-service/WhatsApp/WhatsAppConversations/getAllWhatsAppConversationsByClientRef` returns a **flat, unpaginated array** of every conversation matching the `clientRef` — it accepts no `page` or `size`. Use `getAllWhatsAppConversationsForCurrentUser` when you need to page through a large conversation inventory.

## Best practices for high-volume messaging

### Throughput optimization

- **Batch recipients** — Include multiple recipients in a single API call rather than making one call per recipient
- **Use the batch system** — For large campaigns, let eCommunicate's batch system handle throttling and scheduling
- **Configure processing windows** — Set daily schedules to send during business hours when engagement is highest
- **Monitor batch status** — Use the Batches API to pause/resume/cancel batch jobs as needed


### Reliability

- **Implement exponential backoff** — For `429` and `5xx` responses
- **Set `ctId` on every message** — Enables end-to-end tracking and deduplication
- **Use webhooks for delivery tracking** — Don't poll the Message Reports API; let status updates come to you
- **Handle all failure codes** — See [Error Handling](/guides/error-handling) for the complete failure code reference
- **Log `requestId` from every response** — Essential for debugging with eCommunicate support


### Pagination

- **Use reasonable page sizes** — `20`-`50` items per page balances performance and memory
- **Check `last` field** — Don't rely on `totalPages` alone; use the `last` boolean to stop iterating
- **Add filters** — Use `searchString`, date ranges, and status filters to reduce result sets before paginating