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
403with a plain-text body —Rate Limit Exceeded— not429, and not the JSON envelope.
Handle both as “slow down”.
The limits
| Limit | Behavior | Signal when exceeded |
|---|---|---|
| Concurrent connections | Capped per client — keep a small pool | HTTP 429 |
| Request rate | Sustained high rates are throttled; short bursts are tolerated | HTTP 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=100instead 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.
What to read next
- Errors — the full status code table and envelope.
- Pagination — fetch large datasets in fewer requests.
- API reference — every endpoint, parameter, and response shape.