Rate limits

locco enforces rate limits per API key across three sliding windows. Every request counts against all three; a request that exceeds any one of them is rejected with HTTP 429.

Enterprise tier defaults

WindowCap
Per second100 requests
Per hour10 000 requests
Per day200 000 requests

These are the default caps for the ApiAccess entitlement on the Enterprise tier. Per-key overrides exist: partners on an adjusted plan may have custom caps configured by locco support. The X-RateLimit-Limit-* headers on every response always reflect the effective cap for the calling key, so clients that read the headers do not need to hard-code these values.

The three sliding windows

Each request increments three counters (per-second, per-hour, per-day) and is rejected if any one of them would exceed its cap. The windows slide: the per-hour counter, for example, covers the 3600 seconds preceding the current request, not a fixed hourly reset boundary.

Practical consequences:

  • A burst of 100 requests in one second consumes one second of headroom but only 100 of the hourly 10 000 quota.
  • A steady 80 requests per second saturates the per-second and per-hour windows long before the per-day cap matters.
  • A nightly batch job running 1000 requests per hour never hits the per-second cap but counts fully against the per-day 200 000.

Response headers

Every successful response includes six headers, one Limit and one Remaining per window:

X-RateLimit-Limit-Second: 100
X-RateLimit-Remaining-Second: 97
X-RateLimit-Limit-Hour: 10000
X-RateLimit-Remaining-Hour: 9821
X-RateLimit-Limit-Day: 200000
X-RateLimit-Remaining-Day: 191402

Use these to pace requests. A client that watches X-RateLimit-Remaining-Second and slows down when it drops below a threshold will never hit a 429 under normal load.

Handling 429

When any window is saturated the server returns HTTP 429 with the body:

json
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded."
}

The response also carries a Retry-After header whose value is the number of whole seconds until the affected window clears. Wait at least that long before retrying. Ignoring Retry-After and retrying immediately does not help: the counter has not moved.

For interactive requests (a single request in a user-facing flow):

  1. Read the Retry-After value from the 429 response.
  2. Sleep for Retry-After seconds, then retry.
  3. If the retry also 429s, multiply the delay by 2 and retry.
  4. Cap total retries at 3.

A Python helper that implements this loop:

python
import time
import requests
def get_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries + 1):
r = requests.get(url, headers=headers)
if r.status_code != 429:
r.raise_for_status()
return r
wait = int(r.headers.get("Retry-After", "1"))
# Exponential back-off on top of Retry-After.
wait *= 2 ** attempt
time.sleep(wait)
raise RuntimeError(f"Rate limited after {max_retries} retries")

For batch jobs (nightly sync, bulk import):

  • Pace to about 80 requests per second (roughly 80% of the per-second cap) to leave headroom for concurrent interactive traffic.
  • Watch X-RateLimit-Remaining-Hour across the run. If it approaches zero, pause until the window slides forward.
  • Prefer pagination with a large pageSize (where the endpoint supports it) over many small requests.