Skip to content

API

Rate limits

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

Maintained by the OnePageCRM engineering team · 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.