Skip to main content

Rate Limits

The Mojeeb Public API is rate-limited per API key. The default is 60 requests per minute per key, with a fixed-window counter that resets every 60 seconds. The WhatsApp template list, whose answer is read live from Meta, and replies in a conversation have lower limits, each counted in a budget of its own.

What is the default limit?​

60 requests per minute per API key, across every endpoint that takes a key except those with a limit of their own. Each key has its own counter — multiple keys in the same organization don't share budget. A request rejected by the rate limiter never reaches the send pipeline, never charges quota, and never creates a conversation. GET /v1/me reports the default as rate_limit.requests_per_minute.

Do some endpoints have a lower limit?​

Yes. The WhatsApp template list, GET /v1/agents/{agentId}/connections/{connectionId}/whatsapp-templates, returns what it reads live from Meta, so it allows 10 requests per minute per key, in a budget of its own: its calls don't spend your 60, and your other calls don't spend its 10. Replying in a conversation, POST /v1/agents/{agentId}/conversations/{conversationId}/messages, allows 20 requests per minute per key (every call to it counts, including idempotent replays, 409s and refused replies), also in a budget of its own: each reply goes out to a customer and pauses the AI, so sending gets less room than reading, and your reads never wait for your sends. See Conversations and Messages. Testing a custom API tool also has a limit for your whole company, 30 tests a minute across its keys and the dashboard (429 test_rate_limited), on top of your key's budget; see Custom API Tools.

Each one's reference page shows its limit as x-rate-limit-per-minute, and its 429 reports it in X-RateLimit-Limit. The template list's limit protects the WhatsApp number's Meta quota, which your sends rely on: keep the list instead of reading it before every send, and read it again after your templates change. Each template send already reads the number's templates from Meta to check the name and language, so listing before every send doubles those reads. See List WhatsApp Templates.

Can I get a higher limit?​

Not from the dashboard. Every key gets 60 requests per minute today, 10 on the template list and 20 on replies. If a high-volume integration needs more, contact support with your expected peak rate and use case.

What happens when I hit the limit?​

The API responds with 429 Too Many Requests, the standard error envelope, and full rate-limit headers:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1730212354
Content-Type: application/json

{
"error": {
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"message": "Too many requests. Try again in 60 seconds.",
"correlation_id": "req_01HQZ..."
}
}
HeaderMeaning
Retry-AfterSeconds to wait before the next request
X-RateLimit-LimitThe per-minute limit of the endpoint you called (60, 10 on the template list, or 20 on replies)
X-RateLimit-RemainingRequests left in the current window — 0 on rejection
X-RateLimit-ResetUnix timestamp at which the window resets

How should I handle 429 responses?​

Honor Retry-After. Sleep for at least that many seconds, then retry the same request. Aggressively retrying without backing off will keep you throttled.

A solid retry loop:

  1. Send the request
  2. If 429, parse Retry-After
  3. Sleep for Retry-After seconds (plus a small jitter, e.g. random 0–2s, to avoid thundering-herd retries from many clients)
  4. Retry — pair the retry with the original Idempotency-Key if you used one, so the eventual success doesn't double-send

For sustained traffic, smooth your send rate to stay under the limit instead of bursting and backing off — predictable rate beats reactive throttling.

Are there per-success-response headers?​

Currently, no. X-RateLimit-* headers are emitted reliably on 429 rejections only. Per-2xx response counter headers are a documented gap in v1 and will land in a follow-up. Until then, rely on 429 + Retry-After for backoff signals.

Common questions​

Does the limit apply to status lookups?​

Yes — the default limit covers every /v1/ endpoint that takes a key, including GET /v1/whatsapp/messages/{id}; only the template list and replies in a conversation count apart. If you poll status frequently for many messages, factor that into your budget or batch your polling. GET /v1/openapi.yaml needs no key and is limited per IP address instead.

What happens if my organization has multiple API keys?​

Each key has its own independent budget. A 60/min limit on Key A doesn't reduce Key B's available requests. This makes it easy to give different integrations independent budgets — a marketing automation key can be exhausted without affecting a transactional notification key.

Is there an organization-wide ceiling?​

Not on requests; only custom API tool tests have a company-wide limit (see Custom API Tools). A customer with 10 keys at 60/min each can theoretically do 600/min organization-wide. We may add an org-level ceiling in a future release if needed; you'll be notified before any change.

What if I use the same key from multiple servers?​

The limit is per key, not per server. Multiple servers sharing one key share the budget. For independent budgets across servers, issue separate keys.

Does Meta's WhatsApp rate limit interact with this?​

Yes — Mojeeb's per-key limit is separate from Meta's per-business-number rate limit. A send accepted by Mojeeb (under the per-key budget) can still be throttled or rejected by Meta downstream. Meta-side throttling is never a 429 from us; it shows on GET /v1/whatsapp/messages/{id} as status: failed. For a template, and for any message Meta fails after accepting it, failure_reason is RateLimited and failure_code holds Meta's code (e.g. 130429, 131056). A free-form text that Meta refuses at send time currently reads failure_reason: Unknown with no failure_code, whatever the cause. When Meta throttles the template list itself, the list answers 503 meta_unavailable: retry with backoff.