Skip to content

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#

EndpointDefault limit per API key
POST /v1/messages100 requests per second
Every other endpoint, including POST /v1/messages/batch20 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.

HeaderDescription
RateLimit-LimitRequests allowed per second on this endpoint for this key.
RateLimit-RemainingRequests left in the current window.
RateLimit-ResetSeconds until the window resets.
Retry-AfterSeconds to wait before retrying. Only on 429 responses.
Response headers
HTTP/1.1 202 Accepted
RateLimit-Limit: 100
RateLimit-Remaining: 37
RateLimit-Reset: 1
X-Request-Id: req_0aB3cD6eF9gH2iJ5kL8m

When 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.

Response headers
HTTP/1.1 429 Too Many Requests
Retry-After: 1
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1
429 Too Many Requests
{
  "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.

    Loading