API ReferenceRate Limits

Rate Limits

The EmbayLMS API enforces two independent limits to ensure fair use and platform stability:

  1. A per-minute rate limit per API key
  2. A monthly usage cap per tenant, based on your subscription plan

Both return HTTP 429 when exceeded.


Per-Minute Rate Limit

Request typeLimit
Authenticated requests (per API key)1,000 requests per minute
Unauthenticated requests (per IP — e.g. GET /plans)60 requests per minute

The limit is a fixed one-minute window applied per API key, not per IP address. Multiple integrations using separate API keys each get their own limit.

429 Too Many Requests

When the per-minute limit is exceeded, the API returns HTTP 429 with a Retry-After header and the X-RateLimit-* headers describing the window:

HTTP/1.1 429 Too Many Requests
Retry-After: 14
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1748995260
HeaderDescription
Retry-AfterSeconds until the window resets — wait this long before retrying
X-RateLimit-LimitTotal requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window (0 when limited)
X-RateLimit-ResetUnix timestamp when the window resets

The response body uses the standard error envelope with a numeric code (see Errors):

{
  "data": null,
  "meta": null,
  "error": {
    "code": 429,
    "message": "Rate limit exceeded. Try again in 14 seconds."
  }
}

Monthly Usage Cap

Every authenticated API call also counts against a monthly usage cap tied to your subscription plan. When the cap is reached, requests return 429 with a Retry-After header until the next calendar month (or until you upgrade your plan). Your tenant admin receives an alert when usage crosses 80% and 100% of the cap.

To raise the cap, upgrade your plan from your tenant’s billing portal or contact support.


Handling Rate Limits in Code

Always respect the Retry-After header, and add jitter so parallel workers do not retry in lockstep.

Node.js with Exponential Backoff

async function apiRequestWithRetry(url, options, maxRetries = 5) {
  let attempt = 0;
 
  while (attempt <= maxRetries) {
    const response = await fetch(url, options);
 
    if (response.status !== 429) {
      return response;
    }
 
    const retryAfter = parseInt(response.headers.get('Retry-After') || '1', 10);
    const jitter = Math.random() * 1000; // add up to 1s jitter
    const delay = retryAfter * 1000 + jitter;
 
    console.warn(`Rate limited. Retrying in ${Math.round(delay / 1000)}s (attempt ${attempt + 1}/${maxRetries})`);
    await new Promise(resolve => setTimeout(resolve, delay));
 
    attempt++;
  }
 
  throw new Error('Max retries exceeded');
}

Python with httpx

import time
import httpx
 
def api_request_with_retry(client, url, max_retries=5, **kwargs):
    for attempt in range(max_retries + 1):
        response = client.get(url, **kwargs)
 
        if response.status_code != 429:
            response.raise_for_status()
            return response
 
        retry_after = int(response.headers.get("Retry-After", "1"))
        jitter = 0.5
        delay = retry_after + jitter
 
        print(f"Rate limited. Retrying in {delay}s (attempt {attempt + 1}/{max_retries})")
        time.sleep(delay)
 
    raise Exception("Max retries exceeded")

Tips for Staying Under Limits

  • Cache read responses — user lists change infrequently. Cache aggressively.
  • Use the maximum page size — one request with per_page=200 costs a fifth of five requests with per_page=40, against both the per-minute limit and the monthly cap.
  • Spread requests over time — if you need to pull thousands of records, pace the requests rather than firing them all at once.
  • Poll on a schedule, not a loop — a compliance sync that pulls GET /completions hourly is far cheaper than polling every few seconds. (Push-style webhooks are planned — see Webhooks.)