#Rate limits
Requests are rate limited per API key. When you exceed your limit, the API returns 429 Too Many Requests with a Retry-After header telling you how long to wait. Build a small backoff loop and you will rarely notice the ceiling.
#How limits work
- Limits are applied per API key, so test and live keys are metered independently.
- Limits are measured as a request rate (requests per second / per minute) with short bursts allowed above the steady rate.
- When you go over, further requests return
429until the window refreshes.
Indicative steady-state limits are on the order of a few requests per second per key, with short bursts allowed. These figures are indicative, not guarantees — your account's effective limits depend on your plan and traffic history. Design for backoff rather than a fixed number.
#The 429 response
A throttled request returns 429 with the standard error object and a Retry-After header. Retry-After is the number of seconds to wait before retrying.
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Content-Type: application/json
{
"error": {
"type": "rate_limited",
"message": "too many requests — retry after 2 seconds"
}
}#Recommended client behavior
- Respect
Retry-Afterfirst. When present, wait at least that many seconds before retrying. - Fall back to exponential backoff when
Retry-Afteris absent (e.g. on a5xx): wait 1s, 2s, 4s, 8s, doubling each attempt. - Add jitter — a small random offset on each delay so concurrent clients do not retry in lockstep.
- Cap retries — give up after a few attempts and surface the failure rather than looping forever.
- Smooth your own send rate — spread bulk sends over time instead of firing them all at once.
Never retry a 4xx other than 429. A 400, 401, 404, or 422 will keep failing until
you fix the request — see Errors.
#Retry-on-429 loop
This loop retries on 429 and 5xx, honors Retry-After when present, and otherwise backs off exponentially with jitter.
async function sendWithRetry(payload, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch("https://api.netexem.com/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NETEXEM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (res.ok) return res.json();
// Only retry on rate limits and server errors.
if (res.status !== 429 && res.status < 500) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.type}: ${error.message}`);
}
if (attempt === maxRetries) {
throw new Error(`Gave up after ${maxRetries} retries (last status ${res.status})`);
}
// Honor Retry-After, else exponential backoff with jitter.
const retryAfter = Number(res.headers.get("Retry-After"));
const backoff = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: 2 ** attempt * 1000;
const jitter = Math.random() * 250;
await new Promise((r) => setTimeout(r, backoff + jitter));
}
}#Carrier and A2P limits
Request-rate limits are not the only ceiling on messaging. Outbound SMS/MMS throughput is also bounded by carrier and A2P (application-to-business) limits — per-number sending rates and registered campaign throughput set by the carriers, independent of the API.
- These limits apply even when you are well under your API request rate.
- They protect deliverability and keep your numbers in good standing.
- Honoring opt-outs is part of staying within them — see Opt-out handling.
For high-volume sending, queue messages on your side and drain the queue at a steady rate. This keeps you under both the API request limit and carrier throughput limits, and makes bursts easy to absorb.
#Next steps
- Errors — every status code and the error object shape
- Opt-out handling — staying compliant with carrier rules
- Webhooks — delivery receipts so you know what actually sent