API conventions
Rate limits
Requests are limited per API key and per second. Responses report the remaining allowance, and 429 responses say how long to wait.
Limits#
| Endpoint | Default limit per API key |
|---|---|
POST /v1/messages | 100 requests per second |
Every other endpoint, including POST /v1/messages/batch | 20 requests per second |
- Limits are counted per API key. Two keys of the same account have separate allowances.
- Live and test keys have the same limits.
- A batch is one request, whatever the number of messages in it.
- These are the defaults. If you need a higher limit, contact support with your expected volume.
Rate limit headers#
Every response to a request whose API key was accepted reports the state of the limit that applied to it. Responses rejected before that point (401, and 403 with ip_not_allowed or account_suspended) carry no RateLimit-* headers.
| Header | Description |
|---|---|
RateLimit-Limit | Requests allowed per second on this endpoint for this key. |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Seconds until the window resets. |
Retry-After | Seconds to wait before retrying. Only on 429 responses. |
HTTP/1.1 202 Accepted
RateLimit-Limit: 100
RateLimit-Remaining: 37
RateLimit-Reset: 1
X-Request-Id: req_0aB3cD6eF9gH2iJ5kL8mWhen you exceed a limit#
The API answers 429 with a rate_limit_error. The request was not processed and nothing was charged, so it is always safe to send it again.
HTTP/1.1 429 Too Many Requests
Retry-After: 1
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1{
"error": {
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 1 second.",
"request_id": "req_0aB3cD6eF9gH2iJ5kL8m",
"doc_url": "https://omnimessage.co/docs/errors#rate_limit_exceeded"
}
}Handle 429 responses#
Wait for the number of seconds in Retry-After, add a little random jitter, and retry. Cap the number of attempts.
async function requestWithBackoff(url, init, maxAttempts = 5) {
for (let attempt = 1; ; attempt += 1) {
const response = await fetch(url, init);
if (response.status !== 429 || attempt === maxAttempts) return response;
const retryAfter = Number(response.headers.get('Retry-After')) || 1;
// Jitter keeps parallel workers from retrying in the same instant.
const delay = retryAfter * 1000 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}Stay under the limit#
Queue and pace#
Put outbound messages on a queue and drain it at a fixed rate below the limit, instead of sending in bursts from request handlers. Watch RateLimit-Remaining and slow down before it reaches zero.
Prefer webhooks to polling#
Polling GET /v1/messages/{id} for every message consumes the 20 requests per second allowance quickly. Webhooks deliver the same information without any requests from you.
Use batches for bulk sends#
POST /v1/messages/batch carries up to 100 messages per request. See Send a batch.