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
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.