Limits and retries
The Click API is built for server-to-server use at storefront volume: a customer backend records acceptances from its own servers, often from a single outbound address. This page covers what limits those requests and how to retry safely.
For the platform-wide request-rate policy that applies to all Propper APIs, see the global rate limits guide.
Rate limits are per OAuth client
Click API requests are counted per OAuth client, not per IP address. Every backend that authenticates with its own client credentials gets its own budget, so two customers behind the same address never share one, and one client's traffic shares one budget across all the addresses it sends from.
Requests are counted in three groups, each with its own budget per window:
| Group | Requests |
|---|---|
| Accept | POST /v1/click/accept |
| Render | GET /v1/click/render/... |
| Everything else | Management, receipt and verification calls |
The default budgets suit typical storefront volume. If your integration needs more, contact support: limits can be raised for a single OAuth client without affecting any other.
Above the per-client limits there is also a per-address flood guard at the edge. On the Click API paths a burst over it is slowed with the same 429 and Retry-After rather than banned, so an address is never blocked for longer than the current window.
429 response shape
A request over its limit is not processed, and nothing is recorded. The API answers HTTP 429 Too Many Requests:
{
"errorCode": "CLICK_RATE_LIMITED",
"message": "Rate limit exceeded. Try again in 12 seconds.",
"requestId": "0b6f6e0e-2a4d-4f5e-9d1c-7f0b3b8f2c1a"
}
| Header | Description |
|---|---|
Retry-After | Seconds until the window resets. |
X-RateLimit-Limit | Requests allowed in the current window. |
X-RateLimit-Remaining | Requests left in the current window. 0 on a 429. |
X-RateLimit-Reset | Unix time, in seconds, at which the window resets. |
The X-RateLimit-* headers are also returned on successful responses, so a client can pace itself before it ever sees a 429.
Retrying an acceptance safely
Every POST /v1/click/accept carries an X-Idempotency-Key header, and that key is what makes a retry safe:
- Same key, same body: the API returns the receipt of the acceptance it already recorded, with the same
receiptId, and records nothing new. This holds however long after the first request the retry arrives. - Same key, different body: refused with
422anderrorCode: "CLICK_IDEMPOTENCY_KEY_REUSED". Nothing is recorded. Use a new key for each distinct acceptance. - Two requests with the same key at the same moment: exactly one acceptance is recorded, and both requests get its receipt.
Generate one key per acceptance (a UUID is ideal), store it with your own record of the order or sign-up, and reuse it for every retry of that acceptance, including after a timeout, a 429 or a 5xx. A retry after a 429 is always safe, because nothing was recorded.
Credentials for accepting
A backend that only records acceptances should use an OAuth client with the click:accept scope. It can record acceptances and nothing else: it cannot read or change templates, deployments or evidence. click:write and click:admin can also record acceptances; click:read cannot. A token without one of these scopes gets 403.