Rate limits & restrictions
Every API key carries three independent restrictions enforced at the edge by the HMAC middleware before your request reaches any business logic. Hit any of them and the request is rejected with the corresponding HTTP status. A fourth limit — a global per-IP backstop — runs even earlier, in front of authentication (see the last section).
Per-key rate limit
Each API key has its own rate_limit_per_min — the maximum number of authenticated requests it can make in a single rolling minute, set when the key is created (and adjustable from your dashboard's API Keys page).
The limiter buckets requests by epoch minute (int(unix_now) // 60). Each new epoch minute resets the counter. When you exceed the limit:
- The server responds with HTTP
429 Too Many Requests. - The error envelope's
messageis"Rate limit exceeded". - This per-key 429 carries no
Retry-AfterorX-RateLimit-*headers — compute the reset yourself (see below). (The global per-IP backstop, by contrast, does send those headers.)
What to do
- Back off until the next epoch-minute boundary (
unix_now - (unix_now % 60) + 60). - If you need more headroom, raise the limit on the key in your dashboard. Each key can have a different limit, so high-volume keys don't need to share quota with low-volume integrations.
Replay protection (300-second timestamp window)
The X-CPG-Timestamp header you sign with is checked against the server clock. Requests are rejected when:
- The timestamp is more than 300 seconds off the server's wall clock (either direction).
- The exact signature was already used (the server stores a short-lived nonce per signature).
Both cases return HTTP 401 Unauthorized:
message | Cause |
|---|---|
"Request timestamp is outside the allowed window" | Your clock is wrong, or the request took >300s in flight. |
"Request signature has already been used" | The same signature was already accepted — generate a fresh one. |
What to do
- Sync your clock with NTP (every production server should already do this).
- Never reuse a signature. Every request must regenerate the timestamp + signature, even retries.
IP whitelist (optional, per key)
If you configured an ip_whitelist on the API key (in your dashboard), requests from any other source IP are rejected with HTTP 403 Forbidden and message "Source IP is not whitelisted for this key".
The check uses the request's real client IP as resolved by the gateway. Keys with an empty whitelist accept requests from any IP.
What to do
- Add every outbound IP your integration uses (production servers, backup egress, NAT gateway).
- For dynamic IPs, consider leaving the whitelist empty and relying on the secret + rate limit alone.
Global per-IP backstop
Separate from the per-key limit, every request to the API — authenticated or not — passes through a global per-IP rate limit of 600 requests per 60-second window per source IP, applied in middleware before authentication. It exists to absorb floods and unauthenticated abuse; a normally-behaving integration never hits it.
Unlike the per-key 429, this layer does advertise its limits in response headers:
- Every response (success or 429) carries
X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset(seconds until the window resets). - When you exceed it, the response is
429withmessage"Too many requests. Please try again later."and an additionalRetry-Afterheader (seconds).
If the backstop's backing store is unavailable, the middleware fails closed: requests get 503 with message "Service temporarily unavailable. Please try again." until it recovers. (This is a different 503 from the authentication one — "Authentication service temporarily unavailable" — but both mean "retry with backoff".)
Defence-in-depth summary
| Layer | Stops… | Status |
|---|---|---|
| Global IP backstop | Floods / unauthenticated abuse (600/min/IP) | 429 |
| HMAC signature | Anyone without the secret | 401 |
| Timestamp window | Replayed captures older than 5 min | 401 |
| Nonce store | Replaying the same request twice | 401 |
| Per-key rate limit | Runaway clients or compromised keys | 429 |
| IP whitelist | Stolen secrets used from elsewhere | 403 |
| Permission check | Keys without the right scope | 403 |
Failures at any layer return the standard error envelope documented on the Errors page.