API

Rate limits

Concurrency and throughput limits on the OnePageCRM API, and how to back off when you hit them.

Last updated Jul 8, 2026

The API enforces limits on concurrency and request rate. There is no per-day quota. Two different layers enforce them, and they signal differently:

  • Connection limits return HTTP 429 Too Many Requests.
  • The request-rate throttle returns HTTP 403 with a plain-text body — Rate Limit Exceeded — not 429, and not the JSON envelope.

Handle both as “slow down”.

The limits

LimitBehaviorSignal when exceeded
Concurrent connectionsCapped per client — keep a small poolHTTP 429
Request rateSustained high rates are throttled; short bursts are toleratedHTTP 403, text/plain body Rate Limit Exceeded

The exact thresholds are operational and can change without notice — treat the signals as the contract, not any specific number. The limits are generous for normal integrations: keep a few concurrent connections, avoid unbounded parallelism, and you will rarely see either signal. They exist to catch runaway loops.

Don’t confuse a throttle 403 with a permission 403. The throttle sends a text/plain body with exactly Rate Limit Exceeded; permission errors send the JSON envelope with an error_name. Check the content type before treating a 403 as a rate limit — see Errors.

Handling throttles: back off with jitter

When you get a 429, or a 403 with the plain-text Rate Limit Exceeded body, wait and retry. Double the wait on each consecutive failure, and add jitter so parallel clients don’t retry in lockstep.

async function isThrottled(res) {
  if (res.status === 429) return true;
  if (res.status !== 403) return false;
  // The rate throttle returns 403 with a plain-text body.
  const type = res.headers.get("content-type") ?? "";
  if (!type.includes("text/plain")) return false;
  return (await res.clone().text()).includes("Rate Limit Exceeded");
}

async function fetchWithBackoff(url, options, maxRetries = 5) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, options);
    if (!(await isThrottled(res)) || attempt >= maxRetries) return res;

    const base = Math.min(1000 * 2 ** attempt, 30_000); // 1s, 2s, 4s... cap 30s
    const delay = base / 2 + Math.random() * (base / 2); // add jitter
    await new Promise((resolve) => setTimeout(resolve, delay));
  }
}

The same shape in Python:

import random
import time

import requests

def is_throttled(response):
    if response.status_code == 429:
        return True
    if response.status_code != 403:
        return False
    # The rate throttle returns 403 with a plain-text body.
    content_type = response.headers.get("content-type", "")
    return "text/plain" in content_type and "Rate Limit Exceeded" in response.text

def get_with_backoff(url, max_retries=5, **kwargs):
    for attempt in range(max_retries + 1):
        response = requests.get(url, **kwargs)
        if not is_throttled(response) or attempt == max_retries:
            return response
        base = min(2 ** attempt, 30)  # 1s, 2s, 4s... cap 30s
        time.sleep(base / 2 + random.uniform(0, base / 2))

Tips for staying under the limits

  • Limit concurrency, not just rate. A pool of 2–3 connections is plenty for most integrations.
  • Use pagination with per_page=100 instead of many small requests.
  • Cache reference data. Statuses, deal stages, and custom field schemas change rarely — fetch them once and cache them, not per request.
  • Prefer webhooks over polling where you can — react to changes instead of asking for them.

503 during maintenance

During maintenance windows the API returns HTTP 503 with a maintenance JSON body (error_name: service_unavailable, error_message: "System offline for maintenance"). This is not a rate limit. Pause and retry later — hammering a 503 just delays your own recovery.

Treat throttle responses and 503 differently in code: a throttle means slow down, 503 means come back later.

Common questions

What are the API rate limits? Two layers: a cap on concurrent connections (signals with HTTP 429) and a request-rate throttle (signals with HTTP 403 and a plain-text Rate Limit Exceeded body). There is no per-day quota. Exact thresholds are operational and can change — treat the signals as the contract, not any specific number.

Does the API return 429 when I’m throttled? Only for the concurrency cap. The request-rate throttle returns 403 with a plain-text Rate Limit Exceeded body — not 429, and not the JSON envelope. Check the content type before treating a 403 as a permission error.

How should I handle a throttle response? Wait and retry with exponential backoff and jitter: double the wait on each consecutive failure and cap it at around 30 seconds. A 503 is maintenance, not a throttle — pause and come back later instead.

  • Errors — the full status code table and envelope.
  • Pagination — fetch large datasets in fewer requests.
  • API reference — every endpoint, parameter, and response shape.