Rate limits
Lisoloo applies per-API-key rate limits on every endpoint. Exceeding a
limit returns 429 Too Many Requests with a Retry-After header and
error_code: 1301 RATE_LIMIT_EXCEEDED in the body.
The buckets
Section titled “The buckets”| Endpoint | Limit | Window | Notes |
|---|---|---|---|
POST /send | 60 requests | per minute | Each request can hit many recipients — this counts requests, not SMS. |
GET /status/{message_id} | 120 requests | per minute | Higher cap so polling loops don’t get throttled. |
GET /balance | 30 requests | per minute | Read-heavy, but rarely needed more than once a minute. |
GET /health | unlimited | — | Unauthenticated, no bucket. |
Buckets are independent per key. Sandbox and production keys have the same caps.
The 429 response
Section titled “The 429 response”HTTP/1.1 429 Too Many RequestsContent-Type: application/jsonRetry-After: 27
{ "success": false, "status_code": 429, "error_code": "1301", "message": "Rate limit exceeded", "retry_after": 27}Retry-After is in seconds (the same value also appears in the
JSON body as retry_after). Wait at least that long before retrying.
Retrying earlier returns another 429 with a fresh Retry-After.
Honouring Retry-After
Section titled “Honouring Retry-After”The correct retry pattern is wait, then retry once. Don’t loop on
429s — at best you waste your bucket, at worst you trigger
1302 SUSPICIOUS_ACTIVITY and your key gets a temporary suspension.
import timeimport requests
def send_with_retry(payload, max_attempts=3): for attempt in range(max_attempts): r = requests.post(SEND_URL, headers=HEADERS, json=payload, timeout=10) if r.status_code != 429: return r delay = int(r.headers.get("Retry-After", 60)) time.sleep(delay) r.raise_for_status()async function sendWithRetry(payload, maxAttempts = 3) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const r = await fetch(SEND_URL, { method: "POST", headers: HEADERS, body: JSON.stringify(payload), }); if (r.status !== 429) return r; const delay = parseInt(r.headers.get("Retry-After") ?? "60", 10); await new Promise(res => setTimeout(res, delay * 1000)); } throw new Error("rate-limited after retries");}Bulk patterns
Section titled “Bulk patterns”If you are sending to thousands of recipients, batch into one call
rather than looping. A single POST /send with a 1 000-element to
array is one rate-limit hit; 1 000 separate calls is 1 000 hits.
Hard caps per request:
to: up to 1 000 recipients per call.scheduled_dates: up to 20 dates per call.recurring_schedule: pre-computed total ≤ 5 000 future SMS.
For larger volumes, batch into multiple calls spaced ≥1 second apart, or contact your Bloonio account manager for a per-merchant cap increase.
Concurrency
Section titled “Concurrency”The rate-limit window resets continuously, not on a fixed second
boundary. A naive “send 60 messages at 00:00:00” + “send 60 more at
00:01:00” pattern can still trigger 429 if the burst at :01 overlaps
the window. Spread the load over the minute or honour Retry-After.
See also
Section titled “See also”- Errors — full error envelope
- Send an instant SMS — multi-recipient batching