Skip to main content

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 message is "Rate limit exceeded".
  • This per-key 429 carries no Retry-After or X-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:

messageCause
"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, and X-RateLimit-Reset (seconds until the window resets).
  • When you exceed it, the response is 429 with message "Too many requests. Please try again later." and an additional Retry-After header (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

LayerStops…Status
Global IP backstopFloods / unauthenticated abuse (600/min/IP)429
HMAC signatureAnyone without the secret401
Timestamp windowReplayed captures older than 5 min401
Nonce storeReplaying the same request twice401
Per-key rate limitRunaway clients or compromised keys429
IP whitelistStolen secrets used from elsewhere403
Permission checkKeys without the right scope403

Failures at any layer return the standard error envelope documented on the Errors page.