Rate limiting
Customer 360 applies a per-key, per-minute sliding-window rate limit. Each API key has its own limit (default 120 requests per minute), tracked independently, so one caller's traffic never throttles another. When a key exceeds its limit, the request returns 429 Too Many Requests with rate-limit headers.
The per-key limit
Every key carries a rateLimitPerMinute, set at mint time and defaulting to 120. Requests are counted in a sliding 60-second window per key. When the count reaches the limit, further requests in that window are rejected until older entries age out.
If a high-throughput integration needs a higher ceiling, ask the admin to mint the key with a larger limit. Batching helps too: a single POST /v1/events carries up to 500 events, so a producer rarely needs many requests per minute.
Quick reference
| Detail | Value |
|---|---|
| Algorithm | Sliding window (60s) |
| Scope | Per API key |
| Default limit | 120 requests / minute |
| Response code | 429 Too Many Requests |
| Headers | X-RateLimit-Limit, X-RateLimit-Remaining |
The 429 response
When a key is over its limit, the service returns 429 with a detail message stating the limit, plus two response headers so a client can back off intelligently.
- Name
X-RateLimit-Limit- Type
- header
- Description
The key's configured per-minute limit.
- Name
X-RateLimit-Remaining- Type
- header
- Description
Requests remaining in the current window.
0on a 429.
429 response body
{
"detail": "Rate limit exceeded (120/min)"
}
429 response headers
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
A best-effort limit
The limiter is in-memory per instance. Customer 360 runs on Cloud Run, which scales to multiple instances, so the effective global limit is roughly rateLimitPerMinute times the number of running instances, not a hard fleet-wide cap. It is best-effort abuse-dampening, not an exact global throttle.
In practice, design around the published limit but do not rely on it for precise global throttling. If you need a hard, exact limit, that is a later phase; for now the interim guarantee is per-instance.
Do not rely on the rate limit for exact global throttling. It is per-instance and best-effort. Implement your own backoff on 429, and keep ingest idempotent so a retry is always safe.
Handling 429 responses
On a 429, back off and retry with increasing delays and a little jitter. Because ingest is idempotent on (source, eventId), a retry after a 429 can never double-store: if the first attempt did land, the retry comes back duplicate.
- First retry: wait 2 seconds
- Second retry: wait 4 seconds
- Third retry: wait 8 seconds
- Stop after a few attempts and surface the error
Add 0 to 500 ms of random jitter to each delay so many clients do not retry at the same instant.
Backoff on 429
import requests, time, random
def post_with_backoff(payload, headers, max_retries=4):
delay = 2
for _ in range(max_retries):
r = requests.post(
"https://c360-api.lioncapventures.com/v1/events",
json=payload, headers=headers,
)
if r.status_code != 429:
return r
time.sleep(delay + random.uniform(0, 0.5))
delay *= 2
raise Exception("Still rate limited after retries")