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 public endpointsGET /plans is not rate limited (the response is cached for 5 minutes); POST /contact and POST /signup are limited per IP address

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"
  }
}

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). Every Owner and Admin of the tenant is emailed once per month when usage crosses 80% and 100% of the cap (the check runs every 15 minutes), and the billing portal’s API usage section shows the live count.

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. Better yet, use push-style Webhooks instead of polling.