Skip to content
OmniChannels.ai

Errors, retries and rate limits

HTTP status codes and error bodies, which errors to retry with backoff and jitter, default rate limits, pagination, and how to avoid duplicate sends.

Base URL https://api.broadnet.meUpdated

The API reports errors with standard HTTP status codes and a JSON body. This page explains each code, which ones to retry, the default rate limits, pagination, and how to keep retries from sending a message twice.

HTTP status codes#

Code Meaning What to do
200 Success. Store the returned ID.
201 Created (for example an onboarding link). Use the returned resource.
400 Validation error or missing required field. Fix the request. Do not retry unchanged.
401 Missing or invalid Bearer token. Fix the token. Do not retry.
403 Token is valid but not allowed to call this operation. Ask us to enable the operation. Do not retry.
404 Resource not found, such as a template, sender or batch. Check the name or ID.
413 Uploaded file exceeds the size limit. Split the file or reduce media size.
429 Rate limit exceeded. Wait for Retry-After, then retry.
500 Unexpected server error. Retry with exponential backoff.
502 An upstream provider rejected the call. For WhatsApp, message carries Meta's error code and text. Read the message. Retry only if the upstream error is transient.
503, 504 Temporarily unavailable or timed out upstream. Retry with exponential backoff.

Error bodies#

Errors raised by the API use a five-field body:

400 Bad Request

{
  "status": 400,
  "error": "Bad Request",
  "message": "<what went wrong>",
  "errorCode": "<machine-readable code>",
  "requestUri": "/sms/sendSms"
}
Field Description
status The HTTP status code.
error The HTTP reason phrase.
message A human-readable explanation. Log it; do not parse it.
errorCode A machine-readable code. Branch on this, after the HTTP status.
requestUri The path that failed.

The message and errorCode values above are placeholders; log the real values you receive. A missing or invalid token is rejected earlier, at the API gateway, with a two-field body:

401 Unauthorized

{
  "error": "Unauthorized",
  "message": "Missing Authorization token"
}

Handle errors in this order: HTTP status first, then errorCode. Log message and requestUri with your own request ID. SMPP binds return standard SMPP command_status codes instead; see SMPP.

Retry with backoff and jitter#

Retry only what can succeed later: network errors, 429, and 5xx. Never retry 4xx validation or authentication errors unchanged.

Outcome Retry?
200 with an ID No. The message was accepted.
400, 401, 403, 404, 413 No. Fix the cause first.
429 Yes, after Retry-After.
500, 502, 503, 504 Yes, with exponential backoff and jitter.
Timeout or connection reset Only after checking that the first attempt was not accepted.

Wait longer after each attempt, add random jitter so many clients do not retry in step, and cap both the delay and the number of attempts.

Node.js

const RETRYABLE = new Set([429, 500, 502, 503, 504]);

async function sendWithRetry(body, { maxAttempts = 5, baseMs = 500, capMs = 30000 } = {}) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const res = await fetch("https://api.broadnet.me/sms/sendSms", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.BROADNET_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(15000),
    });
    // A thrown timeout or network error propagates: check status before resending.

    if (res.ok) return res.text(); // message ID
    if (!RETRYABLE.has(res.status) || attempt === maxAttempts) {
      throw new Error(`Send failed: ${res.status} ${await res.text()}`);
    }

    const retryAfter = Number(res.headers.get("Retry-After"));
    const backoff = Math.min(capMs, baseMs * 2 ** (attempt - 1));
    const delay = retryAfter > 0 ? retryAfter * 1000 : Math.random() * backoff; // full jitter
    await new Promise((r) => setTimeout(r, delay));
  }
}

Python

import os
import random
import time

import requests

RETRYABLE = {429, 500, 502, 503, 504}


def send_with_retry(body, max_attempts=5, base=0.5, cap=30.0):
    for attempt in range(1, max_attempts + 1):
        r = requests.post(
            "https://api.broadnet.me/sms/sendSms",
            headers={"Authorization": f"Bearer {os.environ['BROADNET_TOKEN']}"},
            json=body,
            timeout=15,
        )  # a timeout raises: check status before resending
        if r.ok:
            return r.text  # message ID
        if r.status_code not in RETRYABLE or attempt == max_attempts:
            raise RuntimeError(f"Send failed: {r.status_code} {r.text}")

        retry_after = r.headers.get("Retry-After")
        if retry_after and retry_after.isdigit():
            delay = int(retry_after)
        else:
            delay = random.uniform(0, min(cap, base * 2 ** (attempt - 1)))  # full jitter
        time.sleep(delay)

Avoid duplicate sends#

The API does not accept an idempotency key. A request can succeed on the server while its response is lost on the way back, and a blind retry then sends the message twice. Build duplicate protection into your integration:

  1. Record the attempt first. Before calling the API, write a row keyed by your own operation ID, such as order-8842-confirm, with status pending.
  2. Send, then store the ID. On 200, save the returned message ID (or bulkSessionId / batchId) and mark the row accepted. A stored ID means the send is done: never send it again.
  3. After a timeout, check before resending. Look for evidence that the first attempt was accepted: a webhook status for that number and text, a new entry in GET /sms/list for bulk jobs, or the batch list for other channels.
  4. Make retries go through the same record. A queue redelivery or a restarted worker finds the existing row instead of sending again.

Record before send (Node.js)

const opId = "order-8842-confirm";
const inserted = await db.sends.insertIfAbsent({ opId, status: "pending" });
if (!inserted) return; // another worker already owns this send

const messageId = await sendWithRetry({
  senderId: "OmniDemo",
  destinationNumber: "447700900123",
  message: "Your order 8842 is confirmed.",
  messageType: "TEXT",
});
await db.sends.update({ opId }, { status: "accepted", messageId });

For bulk sends, set allowDuplicate to false so duplicate numbers in one upload are removed.

Rate limits#

These are the documented default limits. Your account's limits are confirmed with your credentials, and higher ceilings can be agreed for production traffic.

Interface Default limit Notes
REST API 200 requests per second, sustained Bursts up to 500 requests per second for up to 60 seconds.
SMPP 1,000 messages per second per bind Window of 100 outstanding submit_sm. More throughput through more binds, by agreement.
Number lookup (HLR) 50 requests per second Applies to POST /sms/lookup.

When you exceed a limit, the API answers 429 with a Retry-After header. Wait at least that long before retrying, and slow your send rate.

Practical tips:

  • Spread large sends with the bulk endpoints (one request for many recipients) rather than thousands of single requests.
  • Use the delay field on bulk and channel sends to throttle delivery.
  • Run your own token bucket in front of the API so bursts from several workers stay within your limit.
  • Plan peaks (flash sales, alerts) with our team in advance.

Pagination#

List endpoints that can return many records take two query parameters:

Parameter Type Description
page integer Zero-based page number. Start at 0.
size integer Records per page, for example 50.

Request page 0, then increase page until a page returns fewer records than size.

cURL

curl "https://api.broadnet.me/whatsapp/template/list?page=0&size=50" \
  -H "Authorization: Bearer $BROADNET_TOKEN"

Some batch lists take page and size in the request body instead (for example POST /rcs/message/batches and POST /whatsapp/message/all-bulk), and many lists also accept date filters, which are faster than paging through everything. GET /sms/list is not paginated.

Next steps#

  • Webhooks: confirm delivery instead of resending.
  • SMS API: bulk endpoints and duplicate filtering.
  • SMPP: throughput, windowing and command_status codes.
  • Authentication: fixing 401 errors.

Ready to send your first message?

Request test credentials. An engineer sets up your account, and test sends go to your own verified numbers.

Request test access