SelanimDocs

Rate limits

Two separate ceilings: how fast you may call the API, and how fast each operator accepts traffic.

API limits

EndpointRate limitPayload limit
Every endpoint60 requests / minute, per key
POST /sms/send1,000 recipients per request
POST /contacts/import50,000 contacts per request

Every response carries your current window state:

text
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 41
X-RateLimit-Reset: 1755417600
Retry-After: 18          # only on 429

How the window works

The window is fixed, not rolling: the counter resets at the top of each minute, and X-RateLimit-Reset is the exact moment it will. That means a short burst inside one minute is fine, which is deliberate — the limit exists to stop a runaway loop, not to meter you like a utility.

Rotating a key resets its budget, because the budget belongs to the key. That is a useful escape hatch and a rare enough event to be harmless.

Operator throughput

Accepting a message is not the same as delivering it. A send returns 202 Accepted as soon as it is queued and paid for; the messages then drain into each network at the rate that operator has provisioned, so a large campaign takes minutes rather than seconds to actually go out.

The provisioned rate differs per network and per contract, so there is no single number worth publishing here. What matters for your code is that delivery is asynchronous: poll GET /v1/sms/{messageId} or, better, subscribe to delivery webhooks.

Handling 429

javascript
async function sendWithRetry(payload, attempt = 0) {
  const res = await fetch(`${BASE}/sms/send`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SBS_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": payload.reference,
    },
    body: JSON.stringify(payload),
  });

  if (res.status !== 429) return res.json();
  if (attempt >= 5) throw new Error("rate limited after 5 attempts");

  const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt);
  await new Promise((r) => setTimeout(r, wait * 1000));
  return sendWithRetry(payload, attempt + 1);
}

Need more headroom?

Committed-volume accounts can be provisioned with a dedicated operator route and a higher API ceiling. Talk to your account manager with your expected peak in messages per second. In the meantime, the cheapest way to raise your effective throughput is to send fewer, larger requests: one call with 1,000 recipients costs one request against the limit, and a thousand calls with one recipient each costs a thousand.