Rate Limits
The Monogoto API enforces rate limits to ensure fair usage and platform stability. Understanding rate limits — and building integrations that respect them — is essential for running reliable production services at scale.
Overview
Two independent rate-limiting systems are in effect:
| Scope | Applied to | Keyed by |
|---|---|---|
| API rate limit | Rate-limited endpoints outside /v1/auth/* |
Authenticated User ID, per endpoint |
| Auth rate limit | /v1/auth/token and /v1/auth/refresh |
Username or client IP |
This means authentication failures and general API failures count against separate budgets. Exhausting an API endpoint's budget will not lock you out of refreshing your token, and vice versa.
Rate Limit Headers
Every API response includes headers that tell you exactly where you stand in the current window:
| Header | Type | Description |
|---|---|---|
X-RateLimit-Limit |
integer | Total requests allowed in the current window |
X-RateLimit-Remaining |
integer | Requests remaining before you hit the limit |
Retry-After |
integer | Seconds to wait before retrying — only present on 429 responses |
Example Response Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 43
Content-Type: application/json
Check these headers proactively in your integration. If X-RateLimit-Remaining drops near zero, slow down before you receive a 429.
How Limits Are Applied
Rate limits are per endpoint, not per account. Your plan does not affect them. Each rate-limited route has its own budget, and the budget is scoped to one caller on one route for one window:
{HTTP method} + {route} + {your user ID}
GET /v1/things and GET /v1/orders therefore never consume each other's budget, and exhausting one leaves every other endpoint untouched. Windows are fixed-length and start on your first request; they do not slide.
Not every endpoint is rate-limited. Where a limit applies, it is published on the operation in the API Reference as x-rate-limit, giving the exact max, ttl (window in seconds), and the key it counts against. Treat that as authoritative for any specific endpoint.
Limits in use
| Limit | Window | Applies to |
|---|---|---|
| 5 requests | 60s | Most rate-limited endpoints |
| 10 requests | 60s | Low-powered network and roaming reads |
| 15 requests | 60s | Inbox listing and event types |
| 30 requests | 60s | GET /v1/users |
| 60 requests | 60s | Reports, netflow, payments, filters, brand search, and the SIM, order, tag, role, scope and profile reads |
| 5 requests | 10 minutes | POST /v1/things/{iccid}/start-qos |
| 5 requests | 20 minutes | POST /v1/captures/data/create |
If a limit is too tight for your integration, contact support@monogoto.io with the endpoint and the volume you need.
Authentication Endpoints
The authentication endpoints are rate-limited independently of everything else:
| Endpoint | Limit | Counted per |
|---|---|---|
POST /v1/auth/token |
5 requests / 60 seconds | Submitted username |
POST /v1/auth/refresh |
5 requests / 60 seconds | Client IP address |
POST /v1/auth/token counts against the username in the request body, so one account's failed logins cannot lock out another account behind the same egress IP. POST /v1/auth/refresh carries no username and falls back to the client IP.
Note: Refresh calls from the same server share an IP-level budget. If you run multiple worker processes, coordinate so that only one performs token refresh at a time.
Handling 429 Too Many Requests
When you exceed the rate limit, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 47
Content-Type: application/json
{
"status_code": 429,
"message": "Rate limit exceeded. Retry after 47 seconds.",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Recommended Retry Strategy
- Read the
Retry-Afterresponse header (value in seconds) - Wait at least that long before retrying
- Apply exponential backoff with jitter if the
429persists across retries
async function withRateLimitRetry(fn, maxAttempts = 4) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fn();
if (res.status !== 429) return res;
if (attempt === maxAttempts - 1) {
throw new Error(`Rate limit exceeded after ${maxAttempts} attempts`);
}
const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10);
// Exponential backoff: retryAfter * 2^attempt + random jitter (0–1s)
const delay = retryAfter * 1000 * Math.pow(2, attempt) + Math.random() * 1000;
console.warn(`Rate limited. Retrying in ${(delay / 1000).toFixed(1)}s (attempt ${attempt + 1}/${maxAttempts})`);
await new Promise(r => setTimeout(r, delay));
}
}
def with_rate_limit_retry(fn, max_attempts=4):
"""
Calls fn() and retries on 429 using Retry-After + exponential backoff.
fn must return a requests.Response object.
"""
for attempt in range(max_attempts):
resp = fn()
if resp.status_code != 429:
return resp
if attempt == max_attempts - 1:
raise RuntimeError(f"Rate limit exceeded after {max_attempts} attempts")
retry_after = int(resp.headers.get("Retry-After", 60))
delay = retry_after * (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limited. Retrying in {delay:.1f}s (attempt {attempt + 1}/{max_attempts})")
time.sleep(delay)Proactive Rate Limit Tracking
Rather than waiting for a 429, track the X-RateLimit-Remaining header on every response and throttle your requests when the budget runs low:
class RateLimitAwareClient {
constructor(baseUrl, accessToken) {
this.baseUrl = baseUrl;
this.accessToken = accessToken;
this.remaining = Infinity;
}
async fetch(path, options = {}) {
// If budget is critically low, pause before sending
if (this.remaining < 5) {
console.warn(`Rate limit budget low (${this.remaining} remaining). Pausing 2s.`);
await new Promise(r => setTimeout(r, 2000));
}
const res = await fetch(`${this.baseUrl}${path}`, {
...options,
headers: {
Authorization: `Bearer ${this.accessToken}`,
...options.headers,
},
});
// Update budget from response headers
const remaining = res.headers.get('X-RateLimit-Remaining');
if (remaining !== null) this.remaining = parseInt(remaining, 10);
return res;
}
}
Bulk Operations
If you need to operate on many SIM cards at once, prefer bulk endpoints over individual per-resource calls. A single bulk request counts as one request against your rate limit, regardless of how many resources it modifies.
Available bulk operations are listed under the Things tag in the API Reference. Look for endpoints that accept an array of ICCIDs in the request body.
Example: Instead of 500 individual calls to activate SIM cards, a single bulk activation request uses 1 rate limit unit and completes faster due to reduced round-trip overhead.
Avoiding Rate Limit Issues
Spread requests over time. Instead of firing all requests at once, use a queue with a configurable throughput ceiling. Libraries like p-limit (Node.js) or asyncio.Semaphore (Python) make this straightforward.
Cache responses where possible. Static or slowly-changing data (rate plans, SIM profiles, tag lists) can be cached locally for seconds or minutes, dramatically reducing your request volume.
Use webhooks for state changes. Polling an endpoint every few seconds to detect a SIM status change wastes your rate limit budget. Where Monogoto offers webhook or event notifications, prefer those over polling.
Filter at the API level. Use query parameters to filter, sort, and paginate responses so you fetch only the data you need, rather than fetching everything and filtering client-side.
Related Guides
- Error Reference — Full
429error response format and general error handling patterns - Authentication — Auth endpoint rate limits and the refresh strategy to stay within them