Rate limits
Limits are per API key, so one key's burst cannot starve another.
| Endpoint | Test key | Live key | Messages/min (live) |
|---|---|---|---|
POST /api/v1/sms/send | 10 req/min | 60 req/min | 600,000 |
POST /api/v1/otp/send | 10 req/min | 30 req/min | 30 — one code per request |
POST /api/v1/otp/verify | 60 req/min | — | |
POST /api/v1/voice/send | 5 req/min | 20 req/min | 200,000 calls |
| Everything else | 100 req/min | — | |
If you are sending 100 SMS a second — 6,000 a minute — that is one request per minute with all 6,000 recipients in it. You would be using about a sixtieth of the allowance.
Voice sending is limited hardest of all, for the same reason plus one more: an unwanted text is deleted, while an unwanted call interrupts someone, possibly at night. One request takes up to 10,000 recipients, so the low limit costs a legitimate integration nothing.
Sending in bulk
Do not loop. One request takes up to 10,000 recipients and is both faster and cheaper in round trips than 10,000 requests. This is also why the limits above are never the constraint in practice — the only integrations that hit them are the ones sending one message per request.
// Don't do this — one request per recipient, rate limited immediately.
for (const phone of numbers) {
await sendSms(phone, message);
}
// Do this — one request, up to 10,000 recipients.
await fetch("https://api.jkasms.com/api/v1/sms/send", {
method: "POST",
headers,
body: JSON.stringify({ sender: "JKASMS", message, recipients: numbers }),
});For more than 10,000, chunk into batches of 10,000 and give each its own idempotency key.
Why there is a limit at all
Not to ration what you have bought — your credits are yours, and how fast you spend them is your business. The limit exists for the key itself: a secret that leaks into a public repository or a client-side bundle can otherwise empty an entire bundle in seconds, before anyone sees an alert. A ceiling turns that into minutes, which is the difference between revoking a key and replacing a bundle.
You can also cap a key yourself. Set a monthly message limit on any key from the dashboard, which is the right tool for “this integration should never send more than 5,000 a month”.
When you are limited
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry in 37s."
}
}Returned with 429. Back off for the stated time — retrying immediately just consumes the next window.