Cooldowns and rate limits

The embed API has several layered limits. Some are global (per-key, per-IP), some are session-scoped, and one is per-wallet. A single request can be rejected by any of these; the error code on the response tells you which bucket hit.

Summary

ScopeError codeApplies to
API keyrate_limit_key (429)All endpoints
Client IPrate_limit_ip (429)All endpoints
Session (nonce)rate_limit_session (429)POST /siws/nonce
Session (submit)rate_limit_session (429)POST /submissions
Wallet + pagecooldown_active (429)POST /submissions
Tracking pagequeue_full (429)POST /submissions

All 429 responses include a Retry-After header in seconds. Always honor it.

Global: per-key and per-IP

Every request to every /api/embed/* endpoint passes through these two buckets. The per-key limiter counts against your API key; the per-IP limiter counts against the client IP. They return different error codes (rate_limit_key vs. rate_limit_ip) so you can distinguish them when many keys share an egress IP.

HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>

{ "error": "rate_limit_key" }

Session-scoped

Both session limits require a valid embed_session cookie. Without the cookie, the session-scoped buckets are skipped (but per-key and per-IP still apply). The iframe sets the cookie automatically; custom UIs that never load the iframe won't have it.

POST /siws/nonce has a nonce-issuance cap per session, intended to stop a single visitor from exhausting nonce entropy or masking signature-verification spam. POST /submissions has a total-submissions cap per session, which bounds the volume of wallets one visitor can try to push independent of individual outcomes. Both return rate_limit_session with a Retry-After.

Per-wallet cooldown

When a submission for a wallet is rejected (manually by the owner or via a short-circuit like CA-gate failure), that wallet is placed on cooldown against the same tracking page for a short period. New submissions for that wallet on that page fail with:

HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>

{ "error": "cooldown_active" }

The cooldown is scoped to (walletAddress, trackingPageId). A rejected wallet can still submit to a different tracking page immediately. The cooldown doesn't apply to successful submissions — the duplicate check already prevents those from resubmitting.

Queue cap

POST /submissions checks the tracking page's pending-submission count against a configurable cap. If the queue is at capacity:

HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>

{ "error": "queue_full" }

The counter decrements when the owner approves or rejects. This cap is a policy setting — different pages can be tuned differently. See Verification policy.

Recovery strategies

ErrorStrategy
rate_limit_keyBack off and honor Retry-After. Sustained hits mean you're over budget — slow your polling.
rate_limit_ipBack off and honor Retry-After. Often means many users share one IP (corporate NAT).
rate_limit_session (nonce)Back off and honor Retry-After. Don't retry nonce issuance aggressively on transient errors.
rate_limit_session (submit)Back off and honor Retry-After.
cooldown_activeBack off and honor Retry-After. Fix whatever caused the rejection before retrying.
queue_fullWait or ask the page owner to clear the queue.

What isn't rate-limited

Related