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.
Export limits
Evidence exports have their own limits, per organization and counted across all of its clients.
A request over one is refused with 429 CLICK_EXPORT_LIMIT_REACHED and a Retry-After header, except where the table says otherwise.
| Limit | Value |
|---|---|
| Date window | from to to at most 366 days apart (400 CLICK_VALIDATION_FAILED) |
| Rows per export | 100,000 (422 CLICK_EXPORT_TOO_LARGE; narrow the window or add a filter) |
| Creates | 20 per day, and a create refused as too large counts |
| Active exports | 5 that are READY and not expired |
| Concurrent downloads | 2 streams at a time |
| Downloads | 5 per file per export |
| Download volume | 10 GiB per day |
| Export lifetime | 7 days, then its files answer 410 CLICK_EXPORT_GONE |
The export limits are separate from the per-client request budgets above.
A 429 on a download means nothing was streamed: wait for Retry-After and ask again.
includeEvidence is not available yet; its limits will be listed here when it is.