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
| Window | Cap |
|---|---|
| Per second | 100 requests |
| Per hour | 10 000 requests |
| Per day | 200 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: 100X-RateLimit-Remaining-Second: 97X-RateLimit-Limit-Hour: 10000X-RateLimit-Remaining-Hour: 9821X-RateLimit-Limit-Day: 200000X-RateLimit-Remaining-Day: 191402Use 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:
{ "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.
Recommended back-off
For interactive requests (a single request in a user-facing flow):
- Read the
Retry-Aftervalue from the 429 response. - Sleep for
Retry-Afterseconds, then retry. - If the retry also 429s, multiply the delay by 2 and retry.
- Cap total retries at 3.
A Python helper that implements this loop:
import timeimport 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-Houracross 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.